fix: Meta creatividades — 5 titulares, 5 textos y descripción
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -16,19 +16,27 @@ export function proxiedMediaUrl(raw: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function adHeadlines(ad: AdCreativeCard): string[] {
|
function adHeadlines(ad: AdCreativeCard): string[] {
|
||||||
if (ad.headlines?.length) return ad.headlines;
|
if (ad.headlines?.length) return ad.headlines.slice(0, 5);
|
||||||
return (ad.headline || '')
|
return (ad.headline || '')
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map((s) => s.trim())
|
.map((s) => s.trim())
|
||||||
.filter(Boolean);
|
.filter(Boolean)
|
||||||
|
.slice(0, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
function adDescriptions(ad: AdCreativeCard): string[] {
|
function adBodies(ad: AdCreativeCard): string[] {
|
||||||
if (ad.descriptions?.length) return ad.descriptions;
|
if (ad.bodies?.length) return ad.bodies.slice(0, 5);
|
||||||
|
// Legacy: primaryText used to hold bodies
|
||||||
return (ad.primaryText || '')
|
return (ad.primaryText || '')
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map((s) => s.trim())
|
.map((s) => s.trim())
|
||||||
.filter(Boolean);
|
.filter(Boolean)
|
||||||
|
.slice(0, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
function adLinkDescriptions(ad: AdCreativeCard): string[] {
|
||||||
|
if (ad.descriptions?.length) return ad.descriptions.slice(0, 5);
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function adAssets(ad: AdCreativeCard): CreativeAsset[] {
|
function adAssets(ad: AdCreativeCard): CreativeAsset[] {
|
||||||
@@ -186,8 +194,9 @@ function CreativeCard({
|
|||||||
}) {
|
}) {
|
||||||
const preview = previewUrlFor(ad);
|
const preview = previewUrlFor(ad);
|
||||||
const isVideo = ad.mediaType === 'video' || Boolean(ad.youtubeId);
|
const isVideo = ad.mediaType === 'video' || Boolean(ad.youtubeId);
|
||||||
const headline = adHeadlines(ad)[0] || '';
|
const headlines = adHeadlines(ad);
|
||||||
const body = adDescriptions(ad)[0] || '';
|
const bodies = adBodies(ad);
|
||||||
|
const linkDescs = adLinkDescriptions(ad);
|
||||||
const assets = adAssets(ad);
|
const assets = adAssets(ad);
|
||||||
const extraCount = Math.max(0, assets.length - 1);
|
const extraCount = Math.max(0, assets.length - 1);
|
||||||
|
|
||||||
@@ -206,14 +215,19 @@ function CreativeCard({
|
|||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
) : ad.platform === 'google' && headline ? (
|
) : ad.platform === 'google' && headlines.length ? (
|
||||||
<div className="w-full h-full p-4 space-y-2 text-left bg-gradient-to-b from-[#1a2332] to-[#0d121f] flex flex-col justify-center">
|
<div className="w-full h-full p-4 space-y-2 text-left bg-gradient-to-b from-[#1a2332] to-[#0d121f] flex flex-col justify-center">
|
||||||
<p className="text-[10px] uppercase tracking-wider text-sky-400">
|
<p className="text-[10px] uppercase tracking-wider text-sky-400">
|
||||||
Google · {ad.mediaType || 'rsa'}
|
Google · {ad.mediaType || 'rsa'}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm font-semibold text-blue-300 leading-snug line-clamp-4">
|
{headlines.slice(0, 3).map((h) => (
|
||||||
{headline}
|
<p
|
||||||
</p>
|
key={h}
|
||||||
|
className="text-sm font-semibold text-blue-300 leading-snug line-clamp-2"
|
||||||
|
>
|
||||||
|
{h}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center text-gray-500 text-xs px-4">
|
<div className="text-center text-gray-500 text-xs px-4">
|
||||||
@@ -259,18 +273,55 @@ function CreativeCard({
|
|||||||
<p className="text-xs text-gray-400">Conjunto: {ad.adsetName}</p>
|
<p className="text-xs text-gray-400">Conjunto: {ad.adsetName}</p>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{(headline || body) && (
|
{(headlines.length > 0 || bodies.length > 0 || linkDescs.length > 0) && (
|
||||||
<div className="space-y-1.5 text-xs border border-gray-200 rounded-xl p-3 bg-gray-50">
|
<div className="space-y-2.5 text-xs border border-gray-200 rounded-xl p-3 bg-gray-50">
|
||||||
{headline ? (
|
{headlines.length > 0 ? (
|
||||||
<p className="text-gray-900 font-medium leading-snug line-clamp-2">
|
<div>
|
||||||
{headline}
|
<p className="text-[9px] uppercase tracking-wider text-gray-500 mb-1">
|
||||||
</p>
|
Titulares ({headlines.length})
|
||||||
|
</p>
|
||||||
|
<ul className="space-y-1">
|
||||||
|
{headlines.map((h) => (
|
||||||
|
<li
|
||||||
|
key={h}
|
||||||
|
className="text-gray-900 font-medium leading-snug"
|
||||||
|
>
|
||||||
|
{h}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{body ? (
|
{bodies.length > 0 ? (
|
||||||
<p className="text-gray-500 leading-relaxed line-clamp-3">{body}</p>
|
<div>
|
||||||
|
<p className="text-[9px] uppercase tracking-wider text-gray-500 mb-1">
|
||||||
|
Textos ({bodies.length})
|
||||||
|
</p>
|
||||||
|
<ul className="space-y-1.5">
|
||||||
|
{bodies.map((b) => (
|
||||||
|
<li key={b} className="text-gray-600 leading-relaxed">
|
||||||
|
{b}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{linkDescs.length > 0 ? (
|
||||||
|
<div>
|
||||||
|
<p className="text-[9px] uppercase tracking-wider text-gray-500 mb-1">
|
||||||
|
Descripción
|
||||||
|
</p>
|
||||||
|
<ul className="space-y-1">
|
||||||
|
{linkDescs.map((d) => (
|
||||||
|
<li key={d} className="text-gray-500 leading-relaxed">
|
||||||
|
{d}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{ad.cta ? (
|
{ad.cta ? (
|
||||||
<p className="text-[10px] text-brand-700 uppercase tracking-wide pt-1">
|
<p className="text-[10px] text-brand-700 uppercase tracking-wide pt-0.5">
|
||||||
CTA · {ad.cta}
|
CTA · {ad.cta}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -298,7 +349,8 @@ export function CreativeZoomModal({
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}) {
|
}) {
|
||||||
const headlines = adHeadlines(ad);
|
const headlines = adHeadlines(ad);
|
||||||
const bodies = adDescriptions(ad);
|
const bodies = adBodies(ad);
|
||||||
|
const linkDescs = adLinkDescriptions(ad);
|
||||||
const longH = ad.longHeadlines || [];
|
const longH = ad.longHeadlines || [];
|
||||||
const assets = adAssets(ad);
|
const assets = adAssets(ad);
|
||||||
const images = assets.filter((a) => a.kind === 'image' || a.kind === 'logo');
|
const images = assets.filter((a) => a.kind === 'image' || a.kind === 'logo');
|
||||||
@@ -494,10 +546,10 @@ export function CreativeZoomModal({
|
|||||||
{headlines.length > 0 ? (
|
{headlines.length > 0 ? (
|
||||||
<section>
|
<section>
|
||||||
<h3 className="text-sm font-semibold text-gray-900 mb-2">
|
<h3 className="text-sm font-semibold text-gray-900 mb-2">
|
||||||
Titular
|
Titulares ({headlines.length})
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="space-y-1.5 text-sm text-gray-800">
|
<ul className="space-y-1.5 text-sm text-gray-800">
|
||||||
{headlines.slice(0, 2).map((h) => (
|
{headlines.map((h) => (
|
||||||
<li
|
<li
|
||||||
key={h}
|
key={h}
|
||||||
className="rounded-lg border border-gray-200 bg-gray-50 px-3 py-2"
|
className="rounded-lg border border-gray-200 bg-gray-50 px-3 py-2"
|
||||||
@@ -512,10 +564,10 @@ export function CreativeZoomModal({
|
|||||||
{longH.length > 0 ? (
|
{longH.length > 0 ? (
|
||||||
<section>
|
<section>
|
||||||
<h3 className="text-sm font-semibold text-gray-900 mb-2">
|
<h3 className="text-sm font-semibold text-gray-900 mb-2">
|
||||||
Titulares largos
|
Titulares largos ({longH.length})
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="space-y-1.5 text-sm text-gray-800">
|
<ul className="space-y-1.5 text-sm text-gray-800">
|
||||||
{longH.slice(0, 2).map((h) => (
|
{longH.slice(0, 5).map((h) => (
|
||||||
<li
|
<li
|
||||||
key={h}
|
key={h}
|
||||||
className="rounded-lg border border-gray-200 bg-gray-50 px-3 py-2"
|
className="rounded-lg border border-gray-200 bg-gray-50 px-3 py-2"
|
||||||
@@ -530,10 +582,10 @@ export function CreativeZoomModal({
|
|||||||
{bodies.length > 0 ? (
|
{bodies.length > 0 ? (
|
||||||
<section>
|
<section>
|
||||||
<h3 className="text-sm font-semibold text-gray-900 mb-2">
|
<h3 className="text-sm font-semibold text-gray-900 mb-2">
|
||||||
Texto
|
Textos ({bodies.length})
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="space-y-2 text-sm text-gray-600">
|
<ul className="space-y-2 text-sm text-gray-600">
|
||||||
{bodies.slice(0, 2).map((b) => (
|
{bodies.map((b) => (
|
||||||
<li
|
<li
|
||||||
key={b}
|
key={b}
|
||||||
className="rounded-lg border border-gray-200 bg-gray-50 px-3 py-2 leading-relaxed whitespace-pre-wrap"
|
className="rounded-lg border border-gray-200 bg-gray-50 px-3 py-2 leading-relaxed whitespace-pre-wrap"
|
||||||
@@ -544,6 +596,24 @@ export function CreativeZoomModal({
|
|||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{linkDescs.length > 0 ? (
|
||||||
|
<section>
|
||||||
|
<h3 className="text-sm font-semibold text-gray-900 mb-2">
|
||||||
|
Descripción
|
||||||
|
</h3>
|
||||||
|
<ul className="space-y-2 text-sm text-gray-600">
|
||||||
|
{linkDescs.map((d) => (
|
||||||
|
<li
|
||||||
|
key={d}
|
||||||
|
className="rounded-lg border border-gray-200 bg-gray-50 px-3 py-2 leading-relaxed whitespace-pre-wrap"
|
||||||
|
>
|
||||||
|
{d}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -107,6 +107,9 @@ export default function DriveConnectPanel() {
|
|||||||
<strong className="text-gray-800">Google Ads</strong>.
|
<strong className="text-gray-800">Google Ads</strong>.
|
||||||
Sirve para carpetas Drive, GA4, GSC y creatividades Google Ads.
|
Sirve para carpetas Drive, GA4, GSC y creatividades Google Ads.
|
||||||
Si ves error de token caducado en Ads, pulsa Reconectar Google.
|
Si ves error de token caducado en Ads, pulsa Reconectar Google.
|
||||||
|
En Google Cloud el consent screen debe estar en{' '}
|
||||||
|
<strong className="text-gray-800">In production</strong> (si está
|
||||||
|
en Testing el refresh token caduca a los 7 días).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ function cardsFromDaily(
|
|||||||
headline: agg.campaignName || agg.adName,
|
headline: agg.campaignName || agg.adName,
|
||||||
primaryText: description,
|
primaryText: description,
|
||||||
headlines: [agg.campaignName || agg.adName].filter(Boolean),
|
headlines: [agg.campaignName || agg.adName].filter(Boolean),
|
||||||
|
bodies: [],
|
||||||
descriptions: [description],
|
descriptions: [description],
|
||||||
assets: [] as CreativeAsset[],
|
assets: [] as CreativeAsset[],
|
||||||
status: undefined,
|
status: undefined,
|
||||||
@@ -497,8 +498,9 @@ WHERE ${dateWhere}
|
|||||||
headline: [...headlines, ...longHeadlines].join('\n'),
|
headline: [...headlines, ...longHeadlines].join('\n'),
|
||||||
primaryText: descriptions.join('\n'),
|
primaryText: descriptions.join('\n'),
|
||||||
headlines,
|
headlines,
|
||||||
|
bodies: descriptions,
|
||||||
longHeadlines,
|
longHeadlines,
|
||||||
descriptions,
|
descriptions: [],
|
||||||
assets: entry.assets,
|
assets: entry.assets,
|
||||||
linkUrl: entry.finalUrls[0],
|
linkUrl: entry.finalUrls[0],
|
||||||
cta: uniq(entry.ctas)[0],
|
cta: uniq(entry.ctas)[0],
|
||||||
@@ -553,8 +555,9 @@ WHERE ${dateWhere}
|
|||||||
headline: [...headlines, ...longHeadlines].join('\n'),
|
headline: [...headlines, ...longHeadlines].join('\n'),
|
||||||
primaryText: descriptions.join('\n'),
|
primaryText: descriptions.join('\n'),
|
||||||
headlines,
|
headlines,
|
||||||
|
bodies: descriptions,
|
||||||
longHeadlines,
|
longHeadlines,
|
||||||
descriptions,
|
descriptions: [],
|
||||||
assets,
|
assets,
|
||||||
linkUrl: (ad?.finalUrls || []).map(str).filter(Boolean)[0],
|
linkUrl: (ad?.finalUrls || []).map(str).filter(Boolean)[0],
|
||||||
cta: str(rda?.callToActionText) || undefined,
|
cta: str(rda?.callToActionText) || undefined,
|
||||||
@@ -562,8 +565,7 @@ WHERE ${dateWhere}
|
|||||||
status: enumName(row.adGroupAd?.status),
|
status: enumName(row.adGroupAd?.status),
|
||||||
});
|
});
|
||||||
if (paths.length) {
|
if (paths.length) {
|
||||||
// encode paths into descriptions note for zoom
|
cards[cards.length - 1].bodies = [
|
||||||
cards[cards.length - 1].descriptions = [
|
|
||||||
...descriptions,
|
...descriptions,
|
||||||
`Path: /${paths.join('/')}`,
|
`Path: /${paths.join('/')}`,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -48,10 +48,14 @@ export type AdCreativeCard = {
|
|||||||
/** Best preview URL (prefer vertical / portrait). */
|
/** Best preview URL (prefer vertical / portrait). */
|
||||||
thumbnailUrl: string;
|
thumbnailUrl: string;
|
||||||
imageUrl: string;
|
imageUrl: string;
|
||||||
/** Joined for backwards compat; prefer headlines/descriptions arrays. */
|
/** Joined for backwards compat; prefer headlines/bodies/descriptions arrays. */
|
||||||
headline: string;
|
headline: string;
|
||||||
primaryText: string;
|
primaryText: string;
|
||||||
|
/** Titulares (Meta titles / Google headlines), máx. 5. */
|
||||||
headlines: string[];
|
headlines: string[];
|
||||||
|
/** Textos principales (Meta bodies), máx. 5. */
|
||||||
|
bodies: string[];
|
||||||
|
/** Descripción del enlace (Meta descriptions), suele ser 1. */
|
||||||
descriptions: string[];
|
descriptions: string[];
|
||||||
longHeadlines?: string[];
|
longHeadlines?: string[];
|
||||||
assets: CreativeAsset[];
|
assets: CreativeAsset[];
|
||||||
@@ -122,6 +126,7 @@ function detectMediaType(creative: Record<string, unknown>): string {
|
|||||||
|
|
||||||
type Extracted = {
|
type Extracted = {
|
||||||
headlines: string[];
|
headlines: string[];
|
||||||
|
bodies: string[];
|
||||||
descriptions: string[];
|
descriptions: string[];
|
||||||
assets: CreativeAsset[];
|
assets: CreativeAsset[];
|
||||||
linkUrl: string;
|
linkUrl: string;
|
||||||
@@ -131,6 +136,7 @@ type Extracted = {
|
|||||||
|
|
||||||
function extractCreativePack(creative: Record<string, unknown>): Extracted {
|
function extractCreativePack(creative: Record<string, unknown>): Extracted {
|
||||||
const headlines: string[] = [];
|
const headlines: string[] = [];
|
||||||
|
const bodies: string[] = [];
|
||||||
const descriptions: string[] = [];
|
const descriptions: string[] = [];
|
||||||
const assets: CreativeAsset[] = [];
|
const assets: CreativeAsset[] = [];
|
||||||
const hashes: string[] = [];
|
const hashes: string[] = [];
|
||||||
@@ -155,50 +161,56 @@ function extractCreativePack(creative: Record<string, unknown>): Extracted {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Primary serving copy = object_story_spec (what Ads Manager shows as the ad).
|
|
||||||
// Do NOT dump every asset_feed_spec A/B variant into the card.
|
|
||||||
const oss = creative.object_story_spec as Record<string, unknown> | undefined;
|
const oss = creative.object_story_spec as Record<string, unknown> | undefined;
|
||||||
const link = oss?.link_data as Record<string, unknown> | undefined;
|
const link = oss?.link_data as Record<string, unknown> | undefined;
|
||||||
const video = oss?.video_data as Record<string, unknown> | undefined;
|
const video = oss?.video_data as Record<string, unknown> | undefined;
|
||||||
const photo = oss?.photo_data as Record<string, unknown> | undefined;
|
const photo = oss?.photo_data as Record<string, unknown> | undefined;
|
||||||
|
|
||||||
|
// Story (anuncio publicado): 1 titular + texto + descripción de enlace.
|
||||||
if (link?.name) headlines.push(str(link.name));
|
if (link?.name) headlines.push(str(link.name));
|
||||||
if (link?.message) descriptions.push(str(link.message));
|
if (link?.message) bodies.push(str(link.message));
|
||||||
else if (link?.description) descriptions.push(str(link.description));
|
if (link?.description) descriptions.push(str(link.description));
|
||||||
pushImg(link?.picture, '4:5 story', 1080, 1350);
|
pushImg(link?.picture, '4:5 story', 1080, 1350);
|
||||||
pushImg(link?.image_url, '4:5 story', 1080, 1350);
|
pushImg(link?.image_url, '4:5 story', 1080, 1350);
|
||||||
pushHash(link?.image_hash);
|
pushHash(link?.image_hash);
|
||||||
|
|
||||||
if (video?.title) headlines.push(str(video.title));
|
if (video?.title) headlines.push(str(video.title));
|
||||||
if (video?.message) descriptions.push(str(video.message));
|
if (video?.message) bodies.push(str(video.message));
|
||||||
else if (video?.link_description) descriptions.push(str(video.link_description));
|
if (video?.link_description) descriptions.push(str(video.link_description));
|
||||||
pushImg(video?.image_url, 'video_poster 9:16', 1080, 1920);
|
pushImg(video?.image_url, 'video_poster 9:16', 1080, 1920);
|
||||||
pushHash(video?.image_hash);
|
pushHash(video?.image_hash);
|
||||||
|
|
||||||
pushImg(photo?.url, 'photo 4:5', 1080, 1350);
|
pushImg(photo?.url, 'photo 4:5', 1080, 1350);
|
||||||
pushHash(photo?.image_hash);
|
pushHash(photo?.image_hash);
|
||||||
|
|
||||||
if (!headlines.length && creative.title) headlines.push(str(creative.title));
|
if (creative.title) headlines.push(str(creative.title));
|
||||||
if (!descriptions.length && creative.body) descriptions.push(str(creative.body));
|
if (creative.body) bodies.push(str(creative.body));
|
||||||
pushImg(creative.image_url, 'image 4:5', 1080, 1350);
|
pushImg(creative.image_url, 'image 4:5', 1080, 1350);
|
||||||
pushImg(creative.thumbnail_url, 'thumbnail 9:16', 1080, 1920);
|
pushImg(creative.thumbnail_url, 'thumbnail 9:16', 1080, 1920);
|
||||||
pushHash(creative.image_hash);
|
pushHash(creative.image_hash);
|
||||||
|
|
||||||
// Carousel: keep images; only first card's name as optional secondary headline.
|
|
||||||
if (Array.isArray(link?.child_attachments)) {
|
if (Array.isArray(link?.child_attachments)) {
|
||||||
(link!.child_attachments as Record<string, unknown>[]).forEach((c, i) => {
|
(link!.child_attachments as Record<string, unknown>[]).forEach((c, i) => {
|
||||||
pushImg(c.picture || c.image_url, `carousel-${i + 1} 4:5`, 1080, 1350);
|
pushImg(c.picture || c.image_url, `carousel-${i + 1} 4:5`, 1080, 1350);
|
||||||
pushHash(c.image_hash);
|
pushHash(c.image_hash);
|
||||||
if (i === 0 && c.name && !headlines.length) headlines.push(str(c.name));
|
if (c.name) headlines.push(str(c.name));
|
||||||
if (i === 0 && c.description && !descriptions.length) {
|
if (c.message) bodies.push(str(c.message));
|
||||||
descriptions.push(str(c.description));
|
if (c.description) descriptions.push(str(c.description));
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dynamic creative / Advantage+: hasta 5 titles, 5 bodies, descriptions.
|
||||||
const feed = creative.asset_feed_spec as Record<string, unknown> | undefined;
|
const feed = creative.asset_feed_spec as Record<string, unknown> | undefined;
|
||||||
if (feed) {
|
if (feed) {
|
||||||
// Images/videos from dynamic creative — label formats; texts only if still empty.
|
for (const t of (feed.titles as { text?: string }[]) || []) {
|
||||||
|
if (t?.text) headlines.push(str(t.text));
|
||||||
|
}
|
||||||
|
for (const b of (feed.bodies as { text?: string }[]) || []) {
|
||||||
|
if (b?.text) bodies.push(str(b.text));
|
||||||
|
}
|
||||||
|
for (const d of (feed.descriptions as { text?: string }[]) || []) {
|
||||||
|
if (d?.text) descriptions.push(str(d.text));
|
||||||
|
}
|
||||||
for (const img of (feed.images as Record<string, unknown>[]) || []) {
|
for (const img of (feed.images as Record<string, unknown>[]) || []) {
|
||||||
const labels = Array.isArray(img.adlabels)
|
const labels = Array.isArray(img.adlabels)
|
||||||
? (img.adlabels as { name?: string }[])
|
? (img.adlabels as { name?: string }[])
|
||||||
@@ -206,10 +218,9 @@ function extractCreativePack(creative: Record<string, unknown>): Extracted {
|
|||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(' ')
|
.join(' ')
|
||||||
: '';
|
: '';
|
||||||
const tag = labels || 'feed';
|
|
||||||
pushImg(
|
pushImg(
|
||||||
img.url || img.permalink_url,
|
img.url || img.permalink_url,
|
||||||
tag,
|
labels || 'feed',
|
||||||
n(img.width) || undefined,
|
n(img.width) || undefined,
|
||||||
n(img.height) || undefined
|
n(img.height) || undefined
|
||||||
);
|
);
|
||||||
@@ -227,16 +238,6 @@ function extractCreativePack(creative: Record<string, unknown>): Extracted {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!headlines.length) {
|
|
||||||
const t = (feed.titles as { text?: string }[] | undefined)?.[0]?.text;
|
|
||||||
if (t) headlines.push(str(t));
|
|
||||||
}
|
|
||||||
if (!descriptions.length) {
|
|
||||||
const b =
|
|
||||||
(feed.bodies as { text?: string }[] | undefined)?.[0]?.text ||
|
|
||||||
(feed.descriptions as { text?: string }[] | undefined)?.[0]?.text;
|
|
||||||
if (b) descriptions.push(str(b));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const linkUrl =
|
const linkUrl =
|
||||||
@@ -248,8 +249,9 @@ function extractCreativePack(creative: Record<string, unknown>): Extracted {
|
|||||||
const cta = str(creative.call_to_action_type) || str(ctaRaw);
|
const cta = str(creative.call_to_action_type) || str(ctaRaw);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
headlines: uniq(headlines).slice(0, 3),
|
headlines: uniq(headlines).slice(0, 5),
|
||||||
descriptions: uniq(descriptions).slice(0, 3),
|
bodies: uniq(bodies).slice(0, 5),
|
||||||
|
descriptions: uniq(descriptions).slice(0, 5),
|
||||||
assets,
|
assets,
|
||||||
linkUrl,
|
linkUrl,
|
||||||
cta,
|
cta,
|
||||||
@@ -549,6 +551,7 @@ function cardFromBundle(
|
|||||||
str(creative.image_url) || str(creative.thumbnail_url) || '';
|
str(creative.image_url) || str(creative.thumbnail_url) || '';
|
||||||
const preview = pickVerticalPreview(assets, fallback);
|
const preview = pickVerticalPreview(assets, fallback);
|
||||||
const headlines = pack.headlines;
|
const headlines = pack.headlines;
|
||||||
|
const bodies = pack.bodies;
|
||||||
const descriptions = pack.descriptions;
|
const descriptions = pack.descriptions;
|
||||||
return {
|
return {
|
||||||
platform: 'meta',
|
platform: 'meta',
|
||||||
@@ -565,8 +568,9 @@ function cardFromBundle(
|
|||||||
thumbnailUrl: preview,
|
thumbnailUrl: preview,
|
||||||
imageUrl: preview,
|
imageUrl: preview,
|
||||||
headline: headlines.join('\n'),
|
headline: headlines.join('\n'),
|
||||||
primaryText: descriptions.join('\n'),
|
primaryText: bodies.join('\n') || descriptions.join('\n'),
|
||||||
headlines,
|
headlines,
|
||||||
|
bodies,
|
||||||
descriptions,
|
descriptions,
|
||||||
assets,
|
assets,
|
||||||
linkUrl: pack.linkUrl || undefined,
|
linkUrl: pack.linkUrl || undefined,
|
||||||
@@ -751,6 +755,7 @@ export function buildGoogleCardsFromDaily(
|
|||||||
headline: agg.adName,
|
headline: agg.adName,
|
||||||
primaryText: agg.campaignName,
|
primaryText: agg.campaignName,
|
||||||
headlines: agg.adName ? [agg.adName] : [],
|
headlines: agg.adName ? [agg.adName] : [],
|
||||||
|
bodies: [],
|
||||||
descriptions: agg.campaignName ? [agg.campaignName] : [],
|
descriptions: agg.campaignName ? [agg.campaignName] : [],
|
||||||
assets: [],
|
assets: [],
|
||||||
status: undefined,
|
status: undefined,
|
||||||
|
|||||||
Reference in New Issue
Block a user