fix: reklam kreatiflerini Apify verisinden goster
This commit is contained in:
+1
-1
@@ -11,4 +11,4 @@ GOOGLE_CLIENT_SECRET=
|
|||||||
|
|
||||||
# Apify Meta Ads ingestion (runtime secret; do not expose to the browser)
|
# Apify Meta Ads ingestion (runtime secret; do not expose to the browser)
|
||||||
APIFY_TOKEN=
|
APIFY_TOKEN=
|
||||||
APIFY_ACTOR_ID=solidcode~meta-ads-library-scraper
|
APIFY_ACTOR_ID=aiscraperdev~facebook-meta-ads-library-scraper
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ GOOGLE_CLIENT_SECRET=
|
|||||||
|
|
||||||
# Apify Meta Ads ingestion (runtime secret; do not expose to the browser)
|
# Apify Meta Ads ingestion (runtime secret; do not expose to the browser)
|
||||||
APIFY_TOKEN=
|
APIFY_TOKEN=
|
||||||
APIFY_ACTOR_ID=solidcode~meta-ads-library-scraper
|
APIFY_ACTOR_ID=aiscraperdev~facebook-meta-ads-library-scraper
|
||||||
|
|
||||||
# For docker-compose
|
# For docker-compose
|
||||||
POSTGRES_PASSWORD="change-this-long-random-password"
|
POSTGRES_PASSWORD="change-this-long-random-password"
|
||||||
|
|||||||
+1
-1
@@ -30,7 +30,7 @@ services:
|
|||||||
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
|
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
|
||||||
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
|
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
|
||||||
APIFY_TOKEN: ${APIFY_TOKEN:-}
|
APIFY_TOKEN: ${APIFY_TOKEN:-}
|
||||||
APIFY_ACTOR_ID: ${APIFY_ACTOR_ID:-solidcode~meta-ads-library-scraper}
|
APIFY_ACTOR_ID: ${APIFY_ACTOR_ID:-aiscraperdev~facebook-meta-ads-library-scraper}
|
||||||
ports:
|
ports:
|
||||||
- "${PORT:-3000}:3000"
|
- "${PORT:-3000}:3000"
|
||||||
command: sh -c "npx prisma db push && npm run start"
|
command: sh -c "npx prisma db push && npm run start"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { requireUser } from "@/lib/auth/current-user";
|
|||||||
import { maskAdForPlan } from "@/lib/locked-response";
|
import { maskAdForPlan } from "@/lib/locked-response";
|
||||||
import { planFromUser } from "@/lib/plans";
|
import { planFromUser } from "@/lib/plans";
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
|
import { AdCreativeMedia } from "@/components/ad-creative-media";
|
||||||
|
|
||||||
export default async function AdsPage({ searchParams }: { searchParams: Promise<Record<string, string | undefined>> }) {
|
export default async function AdsPage({ searchParams }: { searchParams: Promise<Record<string, string | undefined>> }) {
|
||||||
const user = await requireUser();
|
const user = await requireUser();
|
||||||
@@ -48,7 +49,7 @@ export default async function AdsPage({ searchParams }: { searchParams: Promise<
|
|||||||
<Card key={ad.id} className="relative overflow-hidden">
|
<Card key={ad.id} className="relative overflow-hidden">
|
||||||
{ad.isLocked && <div className="absolute inset-0 z-10 grid place-items-center bg-white/70 backdrop-blur-[2px]"><Link href="/pricing" className="rounded-2xl bg-violet-700 px-5 py-3 font-black text-white">Start now — Unlock winners</Link></div>}
|
{ad.isLocked && <div className="absolute inset-0 z-10 grid place-items-center bg-white/70 backdrop-blur-[2px]"><Link href="/pricing" className="rounded-2xl bg-violet-700 px-5 py-3 font-black text-white">Start now — Unlock winners</Link></div>}
|
||||||
<div className={ad.isLocked ? "locked-blur" : ""}>
|
<div className={ad.isLocked ? "locked-blur" : ""}>
|
||||||
<img src={ad.creatives[0]?.thumbnailUrl || "https://placehold.co/640x480"} alt="" className="mb-4 h-44 w-full rounded-2xl object-cover" />
|
<AdCreativeMedia creative={ad.creatives[0]} className="mb-4 h-44 w-full rounded-2xl object-cover" />
|
||||||
<div className="mb-2 flex items-center justify-between">
|
<div className="mb-2 flex items-center justify-between">
|
||||||
<div className="font-black">{ad.headline}</div>
|
<div className="font-black">{ad.headline}</div>
|
||||||
<div className="rounded-full bg-emerald-50 px-2 py-1 text-xs font-bold text-emerald-700">Top %{Math.round(ad.rankPercentile || 0)}</div>
|
<div className="rounded-full bg-emerald-50 px-2 py-1 text-xs font-bold text-emerald-700">Top %{Math.round(ad.rankPercentile || 0)}</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { prisma } from "@/lib/db";
|
import { prisma } from "@/lib/db";
|
||||||
import { requireUser } from "@/lib/auth/current-user";
|
import { requireUser } from "@/lib/auth/current-user";
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
|
import { AdCreativeMedia } from "@/components/ad-creative-media";
|
||||||
|
|
||||||
export default async function SavedAdsPage() {
|
export default async function SavedAdsPage() {
|
||||||
const user = await requireUser();
|
const user = await requireUser();
|
||||||
@@ -15,7 +16,7 @@ export default async function SavedAdsPage() {
|
|||||||
<div className="space-y-2"><div className="rounded-xl bg-violet-50 p-3 font-bold text-violet-700">All Saved Ads ({saved.length})</div>{folders.map((f) => <div key={f.id} className="rounded-xl bg-slate-50 p-3 font-bold">{f.name}</div>)}</div>
|
<div className="space-y-2"><div className="rounded-xl bg-violet-50 p-3 font-bold text-violet-700">All Saved Ads ({saved.length})</div>{folders.map((f) => <div key={f.id} className="rounded-xl bg-slate-50 p-3 font-bold">{f.name}</div>)}</div>
|
||||||
</Card>
|
</Card>
|
||||||
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
||||||
{saved.map((s) => <Card key={s.id}><img src={s.ad.creatives[0]?.thumbnailUrl || ""} className="mb-4 h-40 w-full rounded-2xl object-cover" /><div className="font-black">{s.ad.headline}</div><div className="text-sm text-slate-500">{s.ad.brandPage?.name} · {s.folder?.name || "All"}</div><p className="mt-2 line-clamp-2 text-sm">{s.ad.primaryText}</p></Card>)}
|
{saved.map((s) => <Card key={s.id}><AdCreativeMedia creative={s.ad.creatives[0]} className="mb-4 h-40 w-full rounded-2xl object-cover" /><div className="font-black">{s.ad.headline}</div><div className="text-sm text-slate-500">{s.ad.brandPage?.name} · {s.folder?.name || "All"}</div><p className="mt-2 line-clamp-2 text-sm">{s.ad.primaryText}</p></Card>)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { notFound } from "next/navigation";
|
|||||||
import { prisma } from "@/lib/db";
|
import { prisma } from "@/lib/db";
|
||||||
import { requireUser } from "@/lib/auth/current-user";
|
import { requireUser } from "@/lib/auth/current-user";
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
|
import { AdCreativeMedia } from "@/components/ad-creative-media";
|
||||||
|
|
||||||
export default async function StoreDetailPage({ params }: { params: Promise<{ id: string }> }) {
|
export default async function StoreDetailPage({ params }: { params: Promise<{ id: string }> }) {
|
||||||
const resolvedParams = await params;
|
const resolvedParams = await params;
|
||||||
@@ -47,7 +48,7 @@ export default async function StoreDetailPage({ params }: { params: Promise<{ id
|
|||||||
<Card className="mt-5">
|
<Card className="mt-5">
|
||||||
<h2 className="mb-4 text-xl font-black">Top Meta Ads</h2>
|
<h2 className="mb-4 text-xl font-black">Top Meta Ads</h2>
|
||||||
<div className="grid gap-4 md:grid-cols-3">
|
<div className="grid gap-4 md:grid-cols-3">
|
||||||
{ads.map((ad) => <div key={ad.id} className="rounded-2xl border border-slate-100 p-3"><img src={ad.creatives[0]?.thumbnailUrl || ""} className="mb-3 h-32 w-full rounded-xl object-cover" /><div className="font-black">{ad.headline}</div><div className="text-sm text-slate-500">Top %{ad.rankPercentile} · {ad.daysRunning} gün</div></div>)}
|
{ads.map((ad) => <div key={ad.id} className="rounded-2xl border border-slate-100 p-3"><AdCreativeMedia creative={ad.creatives[0]} className="mb-3 h-32 w-full rounded-xl object-cover" /><div className="font-black">{ad.headline}</div><div className="text-sm text-slate-500">Top %{ad.rankPercentile} · {ad.daysRunning} gün</div></div>)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className="mt-5">
|
<Card className="mt-5">
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { MediaType } from "@prisma/client";
|
||||||
|
|
||||||
|
type Creative = {
|
||||||
|
type: MediaType;
|
||||||
|
url: string;
|
||||||
|
thumbnailUrl?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function AdCreativeMedia({ creative, className }: { creative?: Creative | null; className?: string }) {
|
||||||
|
const classes = className || "h-44 w-full rounded-2xl object-cover";
|
||||||
|
if (!creative) {
|
||||||
|
return <div className={`${classes} grid place-items-center bg-slate-100 text-sm font-semibold text-slate-400`}>Kreatif bulunamadı</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (creative.type === MediaType.VIDEO) {
|
||||||
|
return (
|
||||||
|
<video controls playsInline preload="metadata" poster={creative.thumbnailUrl || undefined} className={classes}>
|
||||||
|
<source src={creative.url} />
|
||||||
|
Tarayıcınız video oynatmayı desteklemiyor.
|
||||||
|
</video>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <img src={creative.thumbnailUrl || creative.url} alt="Reklam kreatifi" loading="lazy" className={classes} />;
|
||||||
|
}
|
||||||
+110
-41
@@ -2,7 +2,7 @@ import { AdSource, AdStatus, MediaType, Prisma } from "@prisma/client";
|
|||||||
import { prisma } from "@/lib/db";
|
import { prisma } from "@/lib/db";
|
||||||
|
|
||||||
const APIFY_API_BASE = "https://api.apify.com/v2";
|
const APIFY_API_BASE = "https://api.apify.com/v2";
|
||||||
const DEFAULT_ACTOR_ID = "solidcode~meta-ads-library-scraper";
|
const DEFAULT_ACTOR_ID = "aiscraperdev~facebook-meta-ads-library-scraper";
|
||||||
|
|
||||||
export type ApifyIngestInput = {
|
export type ApifyIngestInput = {
|
||||||
searchTerms: string[];
|
searchTerms: string[];
|
||||||
@@ -43,6 +43,55 @@ function stringArray(record: JsonRecord, ...keys: string[]) {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function nestedRecords(record: JsonRecord) {
|
||||||
|
const queue: unknown[] = [record];
|
||||||
|
const records: JsonRecord[] = [];
|
||||||
|
const seen = new Set<object>();
|
||||||
|
|
||||||
|
while (queue.length && records.length < 500) {
|
||||||
|
const value = queue.shift();
|
||||||
|
if (!value || typeof value !== "object" || seen.has(value as object)) continue;
|
||||||
|
seen.add(value as object);
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
queue.push(...value);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const current = value as JsonRecord;
|
||||||
|
records.push(current);
|
||||||
|
queue.push(...Object.values(current));
|
||||||
|
}
|
||||||
|
|
||||||
|
return records;
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeMediaUrl(value: unknown) {
|
||||||
|
if (typeof value !== "string" || !value.trim()) return null;
|
||||||
|
try {
|
||||||
|
const url = new URL(value.trim());
|
||||||
|
return url.protocol === "https:" || url.protocol === "http:" ? url.toString() : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deepUrlValue(record: JsonRecord, ...keys: string[]) {
|
||||||
|
const records = nestedRecords(record);
|
||||||
|
for (const key of keys) {
|
||||||
|
for (const current of records) {
|
||||||
|
const value = current[key];
|
||||||
|
const direct = safeMediaUrl(value);
|
||||||
|
if (direct) return direct;
|
||||||
|
if (!Array.isArray(value)) continue;
|
||||||
|
for (const item of value) {
|
||||||
|
const nested = safeMediaUrl(item);
|
||||||
|
if (nested) return nested;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function dateValue(value: unknown) {
|
function dateValue(value: unknown) {
|
||||||
if (typeof value !== "string" || !value) return null;
|
if (typeof value !== "string" || !value) return null;
|
||||||
const date = new Date(value);
|
const date = new Date(value);
|
||||||
@@ -50,74 +99,79 @@ function dateValue(value: unknown) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function firstMediaUrl(record: JsonRecord) {
|
function firstMediaUrl(record: JsonRecord) {
|
||||||
const arrays = ["videoUrls", "videos", "imageUrls", "images", "videoPreviewImageUrls"];
|
return deepUrlValue(
|
||||||
for (const key of arrays) {
|
record,
|
||||||
const values = stringArray(record, key);
|
"videoHdUrl", "video_hd_url", "videoSdUrl", "video_sd_url", "videoUrl", "video_url", "videoUrls",
|
||||||
if (values[0]) return values[0];
|
"originalImageUrl", "original_image_url", "resizedImageUrl", "resized_image_url", "imageUrl", "image_url", "imageUrls",
|
||||||
}
|
"videoPreviewImageUrl", "video_preview_image_url", "videoPreviewImageUrls"
|
||||||
return stringValue(record, "videoHdUrl", "videoUrl", "imageUrl", "adSnapshotUrl", "snapshotUrl");
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function firstThumbnailUrl(record: JsonRecord) {
|
function firstThumbnailUrl(record: JsonRecord) {
|
||||||
for (const key of ["videoPreviewImageUrls", "imageUrls", "images"]) {
|
return deepUrlValue(
|
||||||
const values = stringArray(record, key);
|
record,
|
||||||
if (values[0]) return values[0];
|
"videoPreviewImageUrl", "video_preview_image_url", "videoPreviewImageUrls",
|
||||||
}
|
"thumbnailUrl", "thumbnail_url",
|
||||||
return stringValue(record, "thumbnailUrl", "imageUrl", "pageProfilePictureURL");
|
"originalImageUrl", "original_image_url", "resizedImageUrl", "resized_image_url", "imageUrl", "image_url", "imageUrls"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mappedMediaType(record: JsonRecord): MediaType {
|
function mappedMediaType(record: JsonRecord): MediaType {
|
||||||
const raw = stringValue(record, "mediaType", "media_type", "adFormat")?.toUpperCase();
|
const raw = stringValue(record, "mediaType", "media_type", "adFormat", "ad_format")?.toUpperCase();
|
||||||
if (raw === "VIDEO") return MediaType.VIDEO;
|
if (raw === "VIDEO") return MediaType.VIDEO;
|
||||||
if (raw === "IMAGE" || raw === "MEME") return MediaType.IMAGE;
|
if (raw === "IMAGE" || raw === "MEME") return MediaType.IMAGE;
|
||||||
if (raw === "CAROUSEL" || raw === "DPA") return MediaType.CAROUSEL;
|
if (raw === "CAROUSEL" || raw === "DPA") return MediaType.CAROUSEL;
|
||||||
if (stringArray(record, "videoUrls", "videos").length) return MediaType.VIDEO;
|
if (deepUrlValue(record, "videoHdUrl", "video_hd_url", "videoSdUrl", "video_sd_url", "videoUrl", "video_url", "videoUrls")) return MediaType.VIDEO;
|
||||||
if (stringArray(record, "imageUrls", "images").length) return MediaType.IMAGE;
|
if (deepUrlValue(record, "originalImageUrl", "original_image_url", "resizedImageUrl", "resized_image_url", "imageUrl", "image_url", "imageUrls")) return MediaType.IMAGE;
|
||||||
return MediaType.UNKNOWN;
|
return MediaType.UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeApifyAd(record: JsonRecord) {
|
export function normalizeApifyAd(record: JsonRecord) {
|
||||||
const externalAdId = stringValue(record, "adArchiveID", "adArchiveId", "ad_archive_id", "id");
|
const externalAdId = stringValue(record, "adArchiveID", "adArchiveId", "ad_archive_id", "ad_id", "id");
|
||||||
if (!externalAdId) return null;
|
if (!externalAdId) return null;
|
||||||
const pageId = stringValue(record, "pageID", "pageId", "page_id");
|
const explicitPageId = stringValue(record, "pageID", "pageId", "page_id");
|
||||||
const pageName = stringValue(record, "pageName", "page_name", "advertiserName") || "Bilinmeyen reklamveren";
|
const pageName = stringValue(record, "pageName", "page_name", "advertiserName") || "Bilinmeyen reklamveren";
|
||||||
|
const pageId = explicitPageId || (pageName === "Bilinmeyen reklamveren" ? null : `name:${pageName.toLocaleLowerCase("en-US")}`);
|
||||||
const firstSeenAt = dateValue(record.startDate ?? record.start_date ?? record.adDeliveryStartTime);
|
const firstSeenAt = dateValue(record.startDate ?? record.start_date ?? record.adDeliveryStartTime);
|
||||||
const endDate = dateValue(record.endDate ?? record.end_date ?? record.adDeliveryStopTime);
|
const endDate = dateValue(record.endDate ?? record.end_date ?? record.adDeliveryStopTime);
|
||||||
const referenceDate = endDate || new Date();
|
const referenceDate = endDate || new Date();
|
||||||
const daysRunning = firstSeenAt ? Math.max(1, Math.ceil((referenceDate.getTime() - firstSeenAt.getTime()) / 86_400_000)) : null;
|
const daysRunning = firstSeenAt ? Math.max(1, Math.ceil((referenceDate.getTime() - firstSeenAt.getTime()) / 86_400_000)) : null;
|
||||||
const statusText = stringValue(record, "adStatus", "status", "ad_active_status")?.toUpperCase();
|
const statusText = stringValue(record, "adStatus", "status", "ad_active_status", "ad_status")?.toUpperCase();
|
||||||
const status = statusText === "ACTIVE" ? AdStatus.ACTIVE : statusText === "INACTIVE" ? AdStatus.INACTIVE : AdStatus.UNKNOWN;
|
const status = statusText === "ACTIVE" ? AdStatus.ACTIVE : statusText === "INACTIVE" ? AdStatus.INACTIVE : AdStatus.UNKNOWN;
|
||||||
const primaryText = stringValue(record, "adText", "primaryText", "bodyText") || stringArray(record, "adCreativeBodies", "ad_creative_bodies")[0] || null;
|
const primaryText = stringValue(record, "adText", "primaryText", "bodyText", "ad_body_text") || stringArray(record, "adCreativeBodies", "ad_creative_bodies")[0] || null;
|
||||||
const countries = stringArray(record, "countries", "reachedCountries", "ad_reached_countries");
|
const countries = stringArray(record, "countries", "reachedCountries", "ad_reached_countries");
|
||||||
const country = stringValue(record, "country", "pageCountry");
|
const country = stringValue(record, "country", "pageCountry", "page_country");
|
||||||
if (!countries.length && country) countries.push(country);
|
if (!countries.length && country) countries.push(country);
|
||||||
|
|
||||||
|
const thumbnailUrl = firstThumbnailUrl(record);
|
||||||
|
const creativeUrl = firstMediaUrl(record) || thumbnailUrl;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
externalAdId,
|
externalAdId,
|
||||||
pageId,
|
pageId,
|
||||||
pageName,
|
pageName,
|
||||||
pageUrl: stringValue(record, "pageURL", "pageUrl"),
|
pageUrl: stringValue(record, "pageURL", "pageUrl", "page_url"),
|
||||||
pageLogoUrl: stringValue(record, "pageProfilePictureURL", "pageProfilePictureUrl"),
|
pageLogoUrl: stringValue(record, "pageProfilePictureURL", "pageProfilePictureUrl", "page_profile_picture_url"),
|
||||||
pageLikes: numberValue(record.pageLikes),
|
pageLikes: numberValue(record.pageLikes),
|
||||||
pageFollowers: numberValue(record.pageInstagramFollowers),
|
pageFollowers: numberValue(record.pageInstagramFollowers),
|
||||||
status,
|
status,
|
||||||
mediaType: mappedMediaType(record),
|
mediaType: mappedMediaType(record),
|
||||||
primaryText,
|
primaryText,
|
||||||
headline: stringValue(record, "ctaHeadline", "headline", "title"),
|
headline: stringValue(record, "ctaHeadline", "headline", "title", "ad_headline"),
|
||||||
description: stringValue(record, "ctaDescription", "description"),
|
description: stringValue(record, "ctaDescription", "description", "ad_description"),
|
||||||
ctaText: stringValue(record, "ctaText", "ctaType", "callToAction"),
|
ctaText: stringValue(record, "ctaText", "ctaType", "callToAction", "cta_text"),
|
||||||
landingUrl: stringValue(record, "ctaUrl", "landingUrl", "linkUrl"),
|
landingUrl: stringValue(record, "ctaUrl", "landingUrl", "linkUrl", "landing_page_url"),
|
||||||
productUrl: stringValue(record, "adLibraryURL", "adLibraryUrl"),
|
productUrl: stringValue(record, "adLibraryURL", "adLibraryUrl", "ad_library_url"),
|
||||||
language: stringValue(record, "language"),
|
language: stringValue(record, "language"),
|
||||||
countries,
|
countries,
|
||||||
firstSeenAt,
|
firstSeenAt,
|
||||||
lastSeenAt: endDate,
|
lastSeenAt: endDate,
|
||||||
createdAtSource: dateValue(record.adCreationTime ?? record.ad_creation_time),
|
createdAtSource: dateValue(record.adCreationTime ?? record.ad_creation_time),
|
||||||
daysRunning,
|
daysRunning: numberValue(record.ad_active_duration_days) ?? daysRunning,
|
||||||
estimatedReachMin: numberValue(record.reachEstimate),
|
estimatedReachMin: numberValue(record.reachEstimate),
|
||||||
estimatedSpendMin: numberValue(record.spend),
|
estimatedSpendMin: numberValue(record.spend),
|
||||||
creativeUrl: firstMediaUrl(record),
|
creativeUrl,
|
||||||
thumbnailUrl: firstThumbnailUrl(record),
|
thumbnailUrl,
|
||||||
raw: record as Prisma.InputJsonValue
|
raw: record as Prisma.InputJsonValue
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -144,16 +198,7 @@ export async function runApifyActor(input: ApifyIngestInput) {
|
|||||||
const response = await fetch(`${APIFY_API_BASE}/actors/${actorId}/run-sync-get-dataset-items?${query}`, {
|
const response = await fetch(`${APIFY_API_BASE}/actors/${actorId}/run-sync-get-dataset-items?${query}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { authorization: `Bearer ${token}`, "content-type": "application/json", accept: "application/json" },
|
headers: { authorization: `Bearer ${token}`, "content-type": "application/json", accept: "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(actorInput(actorId, input)),
|
||||||
searchTerms: input.searchTerms,
|
|
||||||
country: input.country,
|
|
||||||
adActiveStatus: input.adActiveStatus,
|
|
||||||
mediaType: input.mediaType,
|
|
||||||
adType: "ALL",
|
|
||||||
maxResults: input.maxResults,
|
|
||||||
scrapeAdDetails: input.scrapeAdDetails,
|
|
||||||
includeAboutPage: input.includeAboutPage
|
|
||||||
}),
|
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
cache: "no-store"
|
cache: "no-store"
|
||||||
});
|
});
|
||||||
@@ -169,6 +214,31 @@ export async function runApifyActor(input: ApifyIngestInput) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function actorInput(actorId: string, input: ApifyIngestInput) {
|
||||||
|
if (actorId === "aiscraperdev~facebook-meta-ads-library-scraper") {
|
||||||
|
return {
|
||||||
|
searchQueries: input.searchTerms,
|
||||||
|
countryCode: input.country,
|
||||||
|
adStatus: input.adActiveStatus.toLowerCase(),
|
||||||
|
adType: "all",
|
||||||
|
mediaType: input.mediaType.toLowerCase(),
|
||||||
|
platform: "all",
|
||||||
|
maxResults: input.maxResults
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
searchTerms: input.searchTerms,
|
||||||
|
country: input.country,
|
||||||
|
adActiveStatus: input.adActiveStatus,
|
||||||
|
mediaType: input.mediaType,
|
||||||
|
adType: "ALL",
|
||||||
|
maxResults: input.maxResults,
|
||||||
|
scrapeAdDetails: input.scrapeAdDetails,
|
||||||
|
includeAboutPage: input.includeAboutPage
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function importApifyAds(records: JsonRecord[]) {
|
export async function importApifyAds(records: JsonRecord[]) {
|
||||||
let imported = 0;
|
let imported = 0;
|
||||||
let failed = 0;
|
let failed = 0;
|
||||||
@@ -212,4 +282,3 @@ export async function importApifyAds(records: JsonRecord[]) {
|
|||||||
}
|
}
|
||||||
return { imported, failed };
|
return { imported, failed };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user