diff --git a/app/Enums/SocialAccount/Platform.php b/app/Enums/SocialAccount/Platform.php
index 1d9d207c8..8b640c7b4 100644
--- a/app/Enums/SocialAccount/Platform.php
+++ b/app/Enums/SocialAccount/Platform.php
@@ -167,10 +167,11 @@ public function supportsAltText(): bool
* - LinkedIn UGC: 3000 (`commentary` field)
* - X standard tweet: 280 (X Premium accepts 25K — ignored, conservative)
* - TikTok caption: 2200
- * - YouTube Shorts: title=100, description=5000. We feed `content` to both
- * (publisher derives title from the first line via `buildTitle`), and
- * Shorts UX only shows ~100 chars before "more" — capping at 100 keeps
- * posts appropriate for the format.
+ * - YouTube Shorts: description=5000 (the cap here). The title never
+ * overflows: it comes from `meta.title` (validated at 100) or is
+ * derived from the first content line and truncated by `buildTitle`;
+ * the full content goes to the description unless `meta.description`
+ * overrides it.
* - Facebook text status: 10000 (API allows 63206; we cap below
* that — 63k-char posts are unrealistic and emoji-heavy content
* risks overflowing the TEXT column's 65535-byte ceiling)
@@ -188,7 +189,7 @@ public function maxContentLength(): int
self::LinkedIn, self::LinkedInPage => 3000,
self::X => 280,
self::TikTok => 2200,
- self::YouTube => 100,
+ self::YouTube => 5000,
self::Facebook => 10000,
self::Instagram, self::InstagramFacebook => 2200,
self::Threads => 500,
diff --git a/app/Http/Controllers/Auth/InstagramController.php b/app/Http/Controllers/Auth/InstagramController.php
index 5a6fdbea7..dfc00e71e 100644
--- a/app/Http/Controllers/Auth/InstagramController.php
+++ b/app/Http/Controllers/Auth/InstagramController.php
@@ -25,6 +25,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 d20d32712..e2e2a834a 100644
--- a/app/Http/Controllers/Auth/InstagramFacebookController.php
+++ b/app/Http/Controllers/Auth/InstagramFacebookController.php
@@ -49,6 +49,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 106a9f92e..a5f08f3df 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\TikTokPhotoDerivativeCleaner;
@@ -125,6 +126,13 @@ public function handle(): void
$publisher = $this->getPublisher();
$result = $publisher->publish($this->postPlatform);
$this->postPlatform->markAsPublished(data_get($result, 'id'), data_get($result, 'url'));
+
+ // The post is live; a failed first comment must never fail it —
+ // FirstCommentPoster logs and swallows its own errors.
+ if (($externalId = (string) data_get($result, 'id')) !== '') {
+ app(FirstCommentPoster::class)->post($this->postPlatform, $externalId);
+ }
+
break;
} catch (PlatformUnavailableException $e) {
$this->rescheduleForRetry($e);
diff --git a/app/Mcp/Tools/Post/CreatePostTool.php b/app/Mcp/Tools/Post/CreatePostTool.php
index a90e40305..372c6f21d 100644
--- a/app/Mcp/Tools/Post/CreatePostTool.php
+++ b/app/Mcp/Tools/Post/CreatePostTool.php
@@ -79,7 +79,7 @@ public function schema(JsonSchema $schema): array
->items($schema->object(fn ($p) => [
'social_account_id' => $p->string()->required()->description('UUID of the connected social account.'),
'content_type' => $p->string()->required()->description('Format for this platform (e.g. linkedin_post, x_post, instagram_feed).'),
- 'meta' => $p->object()->description('Per-platform metadata. Instagram/Facebook: aspect_ratio (1:1|4:5|16:9|original). TikTok: privacy_level (required to publish) + flags (allow_comments, allow_duet, allow_stitch, disclose, brand_content_toggle, brand_organic_toggle, is_aigc, auto_add_music). Pinterest: board_id (required to publish — call ListPinterestBoardsTool first), title (≤100), link (destination URL). Pin description comes from the post content. Discord: channel_id (required to publish — call ListDiscordChannelsTool first), mentions ([{token,label}]), embeds ([{title,description,url,image,color}]).'),
+ 'meta' => $p->object()->description('Per-platform metadata. Instagram/Facebook: aspect_ratio (1:1|4:5|16:9|original). TikTok: privacy_level (required to publish) + flags (allow_comments, allow_duet, allow_stitch, disclose, brand_content_toggle, brand_organic_toggle, is_aigc, auto_add_music). Pinterest: board_id (required to publish — call ListPinterestBoardsTool first), title (≤100), link (destination URL). Pin description comes from the post content. Discord: channel_id (required to publish — call ListDiscordChannelsTool first), mentions ([{token,label}]), embeds ([{title,description,url,image,color}]). YouTube: description (≤5000, clickable links; falls back to post content). YouTube/Instagram: first_comment (≤2200, posted by the account right after publish).'),
]))
->description('Platforms to publish on. Accounts not listed remain available but disabled.'),
];
diff --git a/app/Mcp/Tools/Post/UpdatePostTool.php b/app/Mcp/Tools/Post/UpdatePostTool.php
index 3f0ae0e62..aa63e02dd 100644
--- a/app/Mcp/Tools/Post/UpdatePostTool.php
+++ b/app/Mcp/Tools/Post/UpdatePostTool.php
@@ -119,7 +119,7 @@ public function schema(JsonSchema $schema): array
->items($schema->object(fn ($p) => [
'id' => $p->string()->required()->description('UUID of the post_platform row (from get-post-tool / list-posts-tool).'),
'content_type' => $p->string()->description('New content_type for this platform.'),
- 'meta' => $p->object()->description('Per-platform metadata override. Instagram/Facebook: aspect_ratio. TikTok: privacy_level (required to publish) + flags. Pinterest: board_id (required to publish — call ListPinterestBoardsTool first), title (≤100), link (destination URL). Pin description comes from the post content. Discord: channel_id (required to publish — call ListDiscordChannelsTool first), mentions, embeds. Merged with existing meta.'),
+ 'meta' => $p->object()->description('Per-platform metadata override. Instagram/Facebook: aspect_ratio. TikTok: privacy_level (required to publish) + flags. Pinterest: board_id (required to publish — call ListPinterestBoardsTool first), title (≤100), link (destination URL). Pin description comes from the post content. Discord: channel_id (required to publish — call ListDiscordChannelsTool first), mentions, embeds. YouTube: description (≤5000, clickable links; falls back to post content). YouTube/Instagram: first_comment (≤2200, posted by the account right after publish). Merged with existing meta.'),
]))
->description('Platforms to enable for publishing. Any platform NOT listed will be disabled. Pass an empty array to disable all.'),
];
diff --git a/app/Services/Social/FirstCommentPoster.php b/app/Services/Social/FirstCommentPoster.php
new file mode 100644
index 000000000..b0a7b32a4
--- /dev/null
+++ b/app/Services/Social/FirstCommentPoster.php
@@ -0,0 +1,102 @@
+meta, 'first_comment'));
+
+ if ($comment === '') {
+ return;
+ }
+
+ try {
+ match ($postPlatform->platform) {
+ Platform::YouTube => $this->postYouTubeComment($postPlatform, $externalId, $comment),
+ Platform::Instagram, Platform::InstagramFacebook => $this->postInstagramComment($postPlatform, $externalId, $comment),
+ default => Log::warning('First comment is not supported for this platform', [
+ 'platform' => $postPlatform->platform->value,
+ 'post_platform_id' => $postPlatform->id,
+ ]),
+ };
+ } catch (\Throwable $e) {
+ Log::warning('First comment failed (post itself is published)', [
+ 'platform' => $postPlatform->platform->value,
+ 'post_platform_id' => $postPlatform->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(PostPlatform $postPlatform, string $videoId, string $comment): void
+ {
+ $account = $postPlatform->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_platform_id' => $postPlatform->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(PostPlatform $postPlatform, string $mediaId, string $comment): void
+ {
+ $account = $postPlatform->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_platform_id' => $postPlatform->id,
+ 'status' => $response->status(),
+ 'body' => $this->redactResponseBody($response->body()),
+ ]);
+ }
+ }
+}
diff --git a/app/Services/Social/YouTubePublisher.php b/app/Services/Social/YouTubePublisher.php
index f3f6e4438..c1868c6fb 100644
--- a/app/Services/Social/YouTubePublisher.php
+++ b/app/Services/Social/YouTubePublisher.php
@@ -93,7 +93,7 @@ private function publishShort(PostPlatform $postPlatform, $media, SocialAccount
}
$title = $this->buildTitle($content);
- $description = $content;
+ $description = $this->resolveDescription($postPlatform, $content);
$tempFile = tempnam(sys_get_temp_dir(), 'yt_upload_');
$handle = null;
@@ -221,6 +221,18 @@ private function buildTitle(string $content): string
return $title.$shortsTag;
}
+ /**
+ * The video description: the per-platform `meta.description` when the user
+ * provided one (YouTube allows 5000 chars and renders links clickable),
+ * otherwise the post content — the pre-meta behavior.
+ */
+ private function resolveDescription(PostPlatform $postPlatform, string $content): string
+ {
+ $description = trim((string) data_get($postPlatform->meta, 'description'));
+
+ return $description !== '' ? $description : $content;
+ }
+
private function handleGoogleError(Exception $e): never
{
throw YouTubePublishException::fromGoogleException($e);
diff --git a/app/Support/PostPlatformMetaRules.php b/app/Support/PostPlatformMetaRules.php
index 15cc71046..7cd4b32ec 100644
--- a/app/Support/PostPlatformMetaRules.php
+++ b/app/Support/PostPlatformMetaRules.php
@@ -59,6 +59,16 @@ public static function rules(): array
'platforms.*.meta.brand_content_toggle' => ['sometimes', 'boolean'],
'platforms.*.meta.brand_organic_toggle' => ['sometimes', 'boolean'],
+ // YouTube — full video description (YouTube allows 5000 chars with
+ // clickable links; without it the publisher falls back to the post
+ // content, which is capped at the 100-char Shorts title).
+ 'platforms.*.meta.description' => ['sometimes', 'nullable', 'string', 'max:5000'],
+
+ // First comment posted right after a successful publish (YouTube and
+ // Instagram). Capped at Instagram's comment limit, the stricter of
+ // the two.
+ 'platforms.*.meta.first_comment' => ['sometimes', 'nullable', 'string', 'max:2200'],
+
// Pinterest
'platforms.*.meta.board_id' => ['sometimes', 'nullable', 'string'],
'platforms.*.meta.title' => ['sometimes', 'nullable', 'string', 'max:100'],
@@ -90,6 +100,8 @@ public static function messages(): array
'platforms.*.meta.link.url' => __('posts.form.pinterest.link_invalid'),
'platforms.*.meta.link.max' => __('posts.form.pinterest.link_max'),
'platforms.*.meta.title.max' => __('posts.form.pinterest.title_max'),
+ 'platforms.*.meta.description.max' => __('posts.form.youtube.description_max'),
+ 'platforms.*.meta.first_comment.max' => __('posts.form.first_comment.max'),
];
}
@@ -103,6 +115,8 @@ public static function attributes(): array
return [
'platforms.*.meta.title' => __('posts.form.pinterest.title'),
'platforms.*.meta.link' => __('posts.form.pinterest.link'),
+ 'platforms.*.meta.description' => __('posts.form.youtube.description'),
+ 'platforms.*.meta.first_comment' => __('posts.form.first_comment.label'),
];
}
diff --git a/lang/ar/posts.php b/lang/ar/posts.php
index 77c703250..209e29373 100644
--- a/lang/ar/posts.php
+++ b/lang/ar/posts.php
@@ -142,6 +142,21 @@
'document_title' => 'عنوان المستند',
'document_title_placeholder' => 'يظهر على منشور مستند PDF الخاص بك',
],
+ 'youtube' => [
+ 'settings' => 'إعدادات YouTube',
+ 'posting_to' => 'النشر في',
+ 'description' => 'الوصف',
+ 'description_placeholder' => 'الوصف الكامل للفيديو: معلومات وروابط ووسوم…',
+ 'description_hint' => 'حتى 5000 حرف، والروابط قابلة للنقر. إذا تُرك فارغًا يُستخدم نص المنشور.',
+ 'description_max' => 'لا يمكن أن يتجاوز الوصف 5000 حرف.',
+ ],
+ 'first_comment' => [
+ 'label' => 'التعليق الأول',
+ 'placeholder' => 'رابط وإضافات للتعليق الأول…',
+ 'hint' => 'يُنشر باسم قناتك فور نشر الفيديو.',
+ 'hint_instagram' => 'يُنشر فور النشر — المكان الكلاسيكي للروابط التي لا تكون قابلة للنقر في وصف Instagram. أعد ربط الحساب مرة واحدة لمنح إذن التعليقات.',
+ 'max' => 'لا يمكن أن يتجاوز التعليق الأول 2200 حرف.',
+ ],
'pinterest' => [
'settings' => 'إعدادات Pinterest',
'posting_to' => 'النشر إلى',
diff --git a/lang/de/posts.php b/lang/de/posts.php
index a082a28d3..5f7117e78 100644
--- a/lang/de/posts.php
+++ b/lang/de/posts.php
@@ -144,6 +144,21 @@
'document_title' => 'Dokumenttitel',
'document_title_placeholder' => 'Wird bei deinem PDF-Dokument-Beitrag angezeigt',
],
+ 'youtube' => [
+ 'settings' => 'YouTube-Einstellungen',
+ 'posting_to' => 'Veröffentlichen auf',
+ 'description' => 'Beschreibung',
+ 'description_placeholder' => 'Vollständige Videobeschreibung: Fakten, Links, Hashtags…',
+ 'description_hint' => 'Bis zu 5000 Zeichen, Links sind klickbar. Wenn leer, wird der Beitragstext verwendet.',
+ 'description_max' => 'Die Beschreibung darf 5000 Zeichen nicht überschreiten.',
+ ],
+ '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.',
+ ],
'pinterest' => [
'settings' => 'Pinterest-Einstellungen',
'posting_to' => 'Veröffentlichen auf',
diff --git a/lang/el/posts.php b/lang/el/posts.php
index a4592be22..bf3618356 100644
--- a/lang/el/posts.php
+++ b/lang/el/posts.php
@@ -142,6 +142,21 @@
'document_title' => 'Τίτλος εγγράφου',
'document_title_placeholder' => 'Εμφανίζεται στη δημοσίευση εγγράφου PDF σας',
],
+ 'youtube' => [
+ 'settings' => 'Ρυθμίσεις YouTube',
+ 'posting_to' => 'Δημοσίευση σε',
+ 'description' => 'Περιγραφή',
+ 'description_placeholder' => 'Πλήρης περιγραφή βίντεο: πληροφορίες, σύνδεσμοι, hashtags…',
+ 'description_hint' => 'Έως 5000 χαρακτήρες, οι σύνδεσμοι είναι κλικαρίσιμοι. Αν είναι κενό, χρησιμοποιείται το κείμενο της ανάρτησης.',
+ 'description_max' => 'Η περιγραφή δεν μπορεί να υπερβαίνει τους 5000 χαρακτήρες.',
+ ],
+ 'first_comment' => [
+ 'label' => 'Πρώτο σχόλιο',
+ 'placeholder' => 'Σύνδεσμος και πρόσθετα για το πρώτο σχόλιο…',
+ 'hint' => 'Δημοσιεύεται από το κανάλι σας αμέσως μετά τη δημοσίευση του βίντεο.',
+ 'hint_instagram' => 'Δημοσιεύεται αμέσως μετά την ανάρτηση — το κλασικό σημείο για συνδέσμους που οι λεζάντες του Instagram δεν κάνουν κλικαρίσιμους. Επανασυνδέστε τον λογαριασμό μία φορά για να δώσετε το δικαίωμα σχολίων.',
+ 'max' => 'Το πρώτο σχόλιο δεν μπορεί να υπερβαίνει τους 2200 χαρακτήρες.',
+ ],
'pinterest' => [
'settings' => 'Ρυθμίσεις Pinterest',
'posting_to' => 'Δημοσίευση σε',
diff --git a/lang/en/posts.php b/lang/en/posts.php
index ffdf6cecf..539b77a90 100644
--- a/lang/en/posts.php
+++ b/lang/en/posts.php
@@ -142,6 +142,21 @@
'document_title' => 'Document title',
'document_title_placeholder' => 'Shown on your PDF document post',
],
+ 'youtube' => [
+ 'settings' => 'YouTube settings',
+ 'posting_to' => 'Posting to',
+ 'description' => 'Description',
+ 'description_placeholder' => 'Full video description: facts, links, hashtags…',
+ 'description_hint' => 'Up to 5000 characters, links are clickable. When empty, the post text is used.',
+ 'description_max' => 'Description may not exceed 5000 characters.',
+ ],
+ '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.',
+ ],
'pinterest' => [
'settings' => 'Pinterest Settings',
'posting_to' => 'Posting to',
diff --git a/lang/es/posts.php b/lang/es/posts.php
index 566e78ecc..71a230820 100644
--- a/lang/es/posts.php
+++ b/lang/es/posts.php
@@ -142,6 +142,21 @@
'document_title' => 'Título del documento',
'document_title_placeholder' => 'Se muestra en tu publicación de documento PDF',
],
+ 'youtube' => [
+ 'settings' => 'Ajustes de YouTube',
+ 'posting_to' => 'Publicando en',
+ 'description' => 'Descripción',
+ 'description_placeholder' => 'Descripción completa del vídeo: datos, enlaces, hashtags…',
+ 'description_hint' => 'Hasta 5000 caracteres, los enlaces son clicables. Si está vacío, se usa el texto de la publicación.',
+ 'description_max' => 'La descripción no puede superar los 5000 caracteres.',
+ ],
+ '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.',
+ ],
'pinterest' => [
'settings' => 'Configuración de Pinterest',
'posting_to' => 'Publicando en',
diff --git a/lang/fr/posts.php b/lang/fr/posts.php
index b246551f2..6eeeb3e41 100644
--- a/lang/fr/posts.php
+++ b/lang/fr/posts.php
@@ -142,6 +142,21 @@
'document_title' => 'Titre du document',
'document_title_placeholder' => 'Affiché sur votre publication de document PDF',
],
+ 'youtube' => [
+ 'settings' => 'Paramètres YouTube',
+ 'posting_to' => 'Publication sur',
+ 'description' => 'Description',
+ 'description_placeholder' => 'Description complète de la vidéo : infos, liens, hashtags…',
+ 'description_hint' => 'Jusqu\'à 5000 caractères, les liens sont cliquables. Si vide, le texte de la publication est utilisé.',
+ 'description_max' => 'La description ne peut pas dépasser 5000 caractères.',
+ ],
+ '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.',
+ ],
'pinterest' => [
'settings' => 'Paramètres Pinterest',
'posting_to' => 'Publier sur',
diff --git a/lang/it/posts.php b/lang/it/posts.php
index 8bb0645e9..e8e40d8b9 100644
--- a/lang/it/posts.php
+++ b/lang/it/posts.php
@@ -142,6 +142,21 @@
'document_title' => 'Titolo del documento',
'document_title_placeholder' => 'Mostrato sul tuo post con documento PDF',
],
+ 'youtube' => [
+ 'settings' => 'Impostazioni YouTube',
+ 'posting_to' => 'Pubblicazione su',
+ 'description' => 'Descrizione',
+ 'description_placeholder' => 'Descrizione completa del video: dettagli, link, hashtag…',
+ 'description_hint' => 'Fino a 5000 caratteri, i link sono cliccabili. Se vuoto, viene usato il testo del post.',
+ 'description_max' => 'La descrizione non può superare i 5000 caratteri.',
+ ],
+ '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.',
+ ],
'pinterest' => [
'settings' => 'Impostazioni Pinterest',
'posting_to' => 'Pubblicazione su',
diff --git a/lang/ja/posts.php b/lang/ja/posts.php
index f0092fb3f..27dfb3a98 100644
--- a/lang/ja/posts.php
+++ b/lang/ja/posts.php
@@ -142,6 +142,21 @@
'document_title' => 'ドキュメントタイトル',
'document_title_placeholder' => 'PDF ドキュメント投稿に表示されます',
],
+ 'youtube' => [
+ 'settings' => 'YouTube設定',
+ 'posting_to' => '投稿先',
+ 'description' => '説明',
+ 'description_placeholder' => '動画の詳細説明:情報、リンク、ハッシュタグなど…',
+ 'description_hint' => '最大5000文字、リンクはクリック可能です。空の場合は投稿テキストが使われます。',
+ 'description_max' => '説明は5000文字を超えられません。',
+ ],
+ 'first_comment' => [
+ 'label' => '最初のコメント',
+ 'placeholder' => '最初のコメントに入れるリンクや補足…',
+ 'hint' => '動画公開直後にチャンネルから投稿されます。',
+ 'hint_instagram' => '投稿直後にコメントされます。Instagramのキャプションでクリックできないリンクの定番の置き場所です。コメント権限を付与するには一度アカウントを再連携してください。',
+ 'max' => '最初のコメントは2200文字を超えられません。',
+ ],
'pinterest' => [
'settings' => 'Pinterest 設定',
'posting_to' => '投稿先',
diff --git a/lang/ko/posts.php b/lang/ko/posts.php
index 2ec32c9a4..0af8502ee 100644
--- a/lang/ko/posts.php
+++ b/lang/ko/posts.php
@@ -142,6 +142,21 @@
'document_title' => '문서 제목',
'document_title_placeholder' => 'PDF 문서 게시물에 표시됩니다',
],
+ 'youtube' => [
+ 'settings' => 'YouTube 설정',
+ 'posting_to' => '게시 위치',
+ 'description' => '설명',
+ 'description_placeholder' => '동영상 전체 설명: 정보, 링크, 해시태그…',
+ 'description_hint' => '최대 5000자, 링크는 클릭 가능합니다. 비워두면 게시물 텍스트가 사용됩니다.',
+ 'description_max' => '설명은 5000자를 초과할 수 없습니다.',
+ ],
+ 'first_comment' => [
+ 'label' => '첫 댓글',
+ 'placeholder' => '첫 댓글에 넣을 링크와 추가 내용…',
+ 'hint' => '동영상 게시 직후 채널 명의로 작성됩니다.',
+ 'hint_instagram' => '게시 직후 작성됩니다 — Instagram 캡션에서 클릭되지 않는 링크를 넣는 고전적인 방법입니다. 댓글 권한을 부여하려면 계정을 한 번 다시 연결하세요.',
+ 'max' => '첫 댓글은 2200자를 초과할 수 없습니다.',
+ ],
'pinterest' => [
'settings' => 'Pinterest 설정',
'posting_to' => '게시 대상',
diff --git a/lang/nl/posts.php b/lang/nl/posts.php
index 2e06df707..5316d63e8 100644
--- a/lang/nl/posts.php
+++ b/lang/nl/posts.php
@@ -142,6 +142,21 @@
'document_title' => 'Documenttitel',
'document_title_placeholder' => 'Getoond op je PDF-documentpost',
],
+ 'youtube' => [
+ 'settings' => 'YouTube-instellingen',
+ 'posting_to' => 'Publiceren op',
+ 'description' => 'Beschrijving',
+ 'description_placeholder' => 'Volledige videobeschrijving: feiten, links, hashtags…',
+ 'description_hint' => 'Tot 5000 tekens, links zijn klikbaar. Indien leeg wordt de berichttekst gebruikt.',
+ 'description_max' => 'De beschrijving mag niet langer zijn dan 5000 tekens.',
+ ],
+ '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.',
+ ],
'pinterest' => [
'settings' => 'Pinterest-instellingen',
'posting_to' => 'Posten naar',
diff --git a/lang/pl/posts.php b/lang/pl/posts.php
index d47721c06..d4b89a039 100644
--- a/lang/pl/posts.php
+++ b/lang/pl/posts.php
@@ -142,6 +142,21 @@
'document_title' => 'Tytuł dokumentu',
'document_title_placeholder' => 'Wyświetlany w Twoim poście z dokumentem PDF',
],
+ 'youtube' => [
+ 'settings' => 'Ustawienia YouTube',
+ 'posting_to' => 'Publikowanie na',
+ 'description' => 'Opis',
+ 'description_placeholder' => 'Pełny opis filmu: informacje, linki, hasztagi…',
+ 'description_hint' => 'Do 5000 znaków, linki są klikalne. Gdy pusto, używany jest tekst posta.',
+ 'description_max' => 'Opis nie może przekraczać 5000 znaków.',
+ ],
+ '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.',
+ ],
'pinterest' => [
'settings' => 'Ustawienia Pinterest',
'posting_to' => 'Publikowanie na',
diff --git a/lang/pt-BR/posts.php b/lang/pt-BR/posts.php
index 3e4c0a0ec..15ec6852b 100644
--- a/lang/pt-BR/posts.php
+++ b/lang/pt-BR/posts.php
@@ -142,6 +142,21 @@
'document_title' => 'Título do documento',
'document_title_placeholder' => 'Aparece no seu post de documento PDF',
],
+ 'youtube' => [
+ 'settings' => 'Configurações do YouTube',
+ 'posting_to' => 'Publicando em',
+ 'description' => 'Descrição',
+ 'description_placeholder' => 'Descrição completa do vídeo: informações, links, hashtags…',
+ 'description_hint' => 'Até 5000 caracteres, os links são clicáveis. Se vazio, o texto da publicação é usado.',
+ 'description_max' => 'A descrição não pode exceder 5000 caracteres.',
+ ],
+ '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.',
+ ],
'pinterest' => [
'settings' => 'Configurações do Pinterest',
'posting_to' => 'Publicando em',
diff --git a/lang/ru/posts.php b/lang/ru/posts.php
index 19439375f..abe0d4be7 100644
--- a/lang/ru/posts.php
+++ b/lang/ru/posts.php
@@ -142,6 +142,21 @@
'document_title' => 'Название документа',
'document_title_placeholder' => 'Отображается в вашем посте с PDF-документом',
],
+ 'youtube' => [
+ 'settings' => 'Настройки YouTube',
+ 'posting_to' => 'Публикация в',
+ 'description' => 'Описание',
+ 'description_placeholder' => 'Полное описание ролика: факты, ссылки, хэштеги…',
+ 'description_hint' => 'До 5000 знаков, ссылки кликабельны. Если пусто — используется текст поста.',
+ 'description_max' => 'Описание не может быть длиннее 5000 знаков.',
+ ],
+ 'first_comment' => [
+ 'label' => 'Первый комментарий',
+ 'placeholder' => 'Ссылка и дополнения для первого комментария…',
+ 'hint' => 'Публикуется от имени канала сразу после выхода ролика.',
+ 'hint_instagram' => 'Публикуется сразу после поста — классическое место для ссылок, которые в описании Instagram некликабельны. Требуется один раз переподключить аккаунт, чтобы выдать право на комментарии.',
+ 'max' => 'Первый комментарий не может быть длиннее 2200 знаков.',
+ ],
'pinterest' => [
'settings' => 'Настройки Pinterest',
'posting_to' => 'Публикация в',
diff --git a/lang/tr/posts.php b/lang/tr/posts.php
index f19d6c8a1..a7d8387ab 100644
--- a/lang/tr/posts.php
+++ b/lang/tr/posts.php
@@ -144,6 +144,21 @@
'document_title' => 'Belge başlığı',
'document_title_placeholder' => 'PDF belge gönderinizde gösterilir',
],
+ 'youtube' => [
+ 'settings' => 'YouTube ayarları',
+ 'posting_to' => 'Şurada yayınlanıyor',
+ 'description' => 'Açıklama',
+ 'description_placeholder' => 'Videonun tam açıklaması: bilgiler, bağlantılar, etiketler…',
+ 'description_hint' => 'En fazla 5000 karakter, bağlantılar tıklanabilir. Boşsa gönderi metni kullanılır.',
+ 'description_max' => 'Açıklama 5000 karakteri aşamaz.',
+ ],
+ '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.',
+ ],
'pinterest' => [
'settings' => 'Pinterest Ayarları',
'posting_to' => 'Şuraya paylaşılıyor',
diff --git a/lang/uk/posts.php b/lang/uk/posts.php
index bcd9b2505..363233c8e 100644
--- a/lang/uk/posts.php
+++ b/lang/uk/posts.php
@@ -142,6 +142,21 @@
'document_title' => 'Назва документа',
'document_title_placeholder' => 'Відображається у вашому пості з PDF-документом',
],
+ 'youtube' => [
+ 'settings' => 'Налаштування YouTube',
+ 'posting_to' => 'Публікація в',
+ 'description' => 'Опис',
+ 'description_placeholder' => 'Повний опис відео: факти, посилання, хештеги…',
+ 'description_hint' => 'До 5000 знаків, посилання клікабельні. Якщо порожньо — використовується текст поста.',
+ 'description_max' => 'Опис не може перевищувати 5000 знаків.',
+ ],
+ 'first_comment' => [
+ 'label' => 'Перший коментар',
+ 'placeholder' => 'Посилання та доповнення для першого коментаря…',
+ 'hint' => 'Публікується від імені каналу одразу після виходу відео.',
+ 'hint_instagram' => 'Публікується одразу після поста — класичне місце для посилань, які в описі Instagram неклікабельні. Потрібно один раз перепідключити акаунт, щоб надати право на коментарі.',
+ 'max' => 'Перший коментар не може перевищувати 2200 знаків.',
+ ],
'pinterest' => [
'settings' => 'Налаштування Pinterest',
'posting_to' => 'Публікація в',
diff --git a/lang/zh/posts.php b/lang/zh/posts.php
index af5dc2750..e5383d7f6 100644
--- a/lang/zh/posts.php
+++ b/lang/zh/posts.php
@@ -142,6 +142,21 @@
'document_title' => '文档标题',
'document_title_placeholder' => '显示在你的 PDF 文档帖子上',
],
+ 'youtube' => [
+ 'settings' => 'YouTube 设置',
+ 'posting_to' => '发布到',
+ 'description' => '描述',
+ 'description_placeholder' => '视频完整描述:信息、链接、话题标签…',
+ 'description_hint' => '最多 5000 个字符,链接可点击。留空则使用帖子文本。',
+ 'description_max' => '描述不能超过 5000 个字符。',
+ ],
+ 'first_comment' => [
+ 'label' => '首条评论',
+ 'placeholder' => '首条评论中的链接和补充内容…',
+ 'hint' => '视频发布后立即以频道名义发表。',
+ 'hint_instagram' => '发布后立即评论——这是放置 Instagram 描述中无法点击的链接的经典位置。需重新连接一次账号以授予评论权限。',
+ 'max' => '首条评论不能超过 2200 个字符。',
+ ],
'pinterest' => [
'settings' => 'Pinterest 设置',
'posting_to' => '发布到',
diff --git a/resources/js/components/ChannelConfigurator.vue b/resources/js/components/ChannelConfigurator.vue
index ce3d00e87..95c69f1df 100644
--- a/resources/js/components/ChannelConfigurator.vue
+++ b/resources/js/components/ChannelConfigurator.vue
@@ -8,6 +8,7 @@ import InstagramSettings from '@/components/posts/editor/InstagramSettings.vue';
import LinkedInSettings from '@/components/posts/editor/LinkedInSettings.vue';
import PinterestSettings from '@/components/posts/editor/PinterestSettings.vue';
import TikTokSettings from '@/components/posts/editor/TikTokSettings.vue';
+import YouTubeSettings from '@/components/posts/editor/YouTubeSettings.vue';
import { Avatar } from '@/components/ui/avatar';
import { Badge } from '@/components/ui/badge';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
@@ -178,6 +179,15 @@ const selectedChannels = computed(() => props.channels.filter((channel) => isSel
:preview-only="previewOnly"
@update:meta="emit('update:meta', channel.id, $event)"
/>
+
{{ $t('posts.form.first_comment.label') }}
+ + {{ firstComment.length }}/{{ FIRST_COMMENT_MAX }} + +{{ $t('posts.form.first_comment.hint_instagram') }}
+
+import { IconChevronDown, IconChevronUp } from '@tabler/icons-vue';
+import { computed, ref } from 'vue';
+
+import { Avatar } from '@/components/ui/avatar';
+import { Textarea } from '@/components/ui/textarea';
+import { getPlatformLogo } from '@/composables/usePlatformLogo';
+
+interface SocialAccount {
+ id: string;
+ platform: string;
+ display_name: string;
+ username: string;
+ display_label: string;
+ avatar_url: string | null;
+}
+
+interface Props {
+ socialAccount: SocialAccount | null;
+ platform: string;
+ meta?: Record {{ $t('posts.form.youtube.posting_to') }}
+ {{ socialAccount.display_label }}
+ @{{ socialAccount.username }}
+ {{ $t('posts.form.youtube.description') }} {{ $t('posts.form.youtube.description_hint') }} {{ $t('posts.form.first_comment.label') }} {{ $t('posts.form.first_comment.hint') }}