varg.ai-sdk 0.1.1 → 0.4.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +1 -1
- package/.env.example +3 -0
- package/.github/workflows/ci.yml +23 -0
- package/.husky/README.md +102 -0
- package/.husky/commit-msg +6 -0
- package/.husky/pre-commit +9 -0
- package/.husky/pre-push +6 -0
- package/.size-limit.json +8 -0
- package/.test-hooks.ts +5 -0
- package/CLAUDE.md +10 -3
- package/CONTRIBUTING.md +150 -0
- package/LICENSE.md +53 -0
- package/README.md +56 -209
- package/SKILLS.md +26 -10
- package/biome.json +7 -1
- package/bun.lock +1286 -0
- package/commitlint.config.js +22 -0
- package/docs/index.html +1130 -0
- package/docs/prompting.md +326 -0
- package/docs/react.md +834 -0
- package/docs/sdk.md +812 -0
- package/ffmpeg/CLAUDE.md +68 -0
- package/package.json +43 -10
- package/pipeline/cookbooks/scripts/animate-frames-parallel.ts +84 -0
- package/pipeline/cookbooks/scripts/combine-scenes.sh +53 -0
- package/pipeline/cookbooks/scripts/generate-frames-parallel.ts +99 -0
- package/pipeline/cookbooks/scripts/still-to-video.sh +37 -0
- package/pipeline/cookbooks/text-to-tiktok.md +669 -0
- package/pipeline/cookbooks/trendwatching.md +156 -0
- package/plan.md +281 -0
- package/scripts/.gitkeep +0 -0
- package/src/ai-sdk/cache.ts +142 -0
- package/src/ai-sdk/examples/cached-generation.ts +53 -0
- package/src/ai-sdk/examples/duet-scene-4.ts +53 -0
- package/src/ai-sdk/examples/duet-scene-5-audio.ts +32 -0
- package/src/ai-sdk/examples/duet-video.ts +56 -0
- package/src/ai-sdk/examples/editly-composition.ts +63 -0
- package/src/ai-sdk/examples/editly-test.ts +57 -0
- package/src/ai-sdk/examples/editly-video-test.ts +52 -0
- package/src/ai-sdk/examples/fal-lipsync.ts +43 -0
- package/src/ai-sdk/examples/higgsfield-image.ts +61 -0
- package/src/ai-sdk/examples/music-generation.ts +19 -0
- package/src/ai-sdk/examples/openai-sora.ts +34 -0
- package/src/ai-sdk/examples/replicate-bg-removal.ts +52 -0
- package/src/ai-sdk/examples/simpsons-scene.ts +61 -0
- package/src/ai-sdk/examples/talking-lion.ts +55 -0
- package/src/ai-sdk/examples/video-generation.ts +39 -0
- package/src/ai-sdk/examples/workflow-animated-girl.ts +104 -0
- package/src/ai-sdk/examples/workflow-before-after.ts +114 -0
- package/src/ai-sdk/examples/workflow-character-grid.ts +112 -0
- package/src/ai-sdk/examples/workflow-slideshow.ts +161 -0
- package/src/ai-sdk/file-cache.ts +112 -0
- package/src/ai-sdk/file.ts +238 -0
- package/src/ai-sdk/generate-element.ts +92 -0
- package/src/ai-sdk/generate-music.ts +46 -0
- package/src/ai-sdk/generate-video.ts +165 -0
- package/src/ai-sdk/index.ts +72 -0
- package/src/ai-sdk/music-model.ts +110 -0
- package/src/ai-sdk/providers/editly/editly.test.ts +1108 -0
- package/src/ai-sdk/providers/editly/ffmpeg.ts +60 -0
- package/src/ai-sdk/providers/editly/index.ts +817 -0
- package/src/ai-sdk/providers/editly/layers.ts +776 -0
- package/src/ai-sdk/providers/editly/plan.md +144 -0
- package/src/ai-sdk/providers/editly/types.ts +328 -0
- package/src/ai-sdk/providers/elevenlabs-provider.ts +255 -0
- package/src/ai-sdk/providers/fal-provider.ts +512 -0
- package/src/ai-sdk/providers/higgsfield.ts +379 -0
- package/src/ai-sdk/providers/openai.ts +251 -0
- package/src/ai-sdk/providers/replicate.ts +16 -0
- package/src/ai-sdk/video-model.ts +185 -0
- package/src/cli/commands/find.tsx +137 -0
- package/src/cli/commands/help.tsx +85 -0
- package/src/cli/commands/index.ts +6 -0
- package/src/cli/commands/list.tsx +238 -0
- package/src/cli/commands/render.tsx +71 -0
- package/src/cli/commands/run.tsx +511 -0
- package/src/cli/commands/which.tsx +253 -0
- package/src/cli/index.ts +114 -0
- package/src/cli/quiet.ts +44 -0
- package/src/cli/types.ts +32 -0
- package/src/cli/ui/components/Badge.tsx +29 -0
- package/src/cli/ui/components/DataTable.tsx +51 -0
- package/src/cli/ui/components/Header.tsx +23 -0
- package/src/cli/ui/components/HelpBlock.tsx +44 -0
- package/src/cli/ui/components/KeyValue.tsx +33 -0
- package/src/cli/ui/components/OptionRow.tsx +81 -0
- package/src/cli/ui/components/Separator.tsx +23 -0
- package/src/cli/ui/components/StatusBox.tsx +108 -0
- package/src/cli/ui/components/VargBox.tsx +51 -0
- package/src/cli/ui/components/VargProgress.tsx +36 -0
- package/src/cli/ui/components/VargSpinner.tsx +34 -0
- package/src/cli/ui/components/VargText.tsx +56 -0
- package/src/cli/ui/components/index.ts +19 -0
- package/src/cli/ui/index.ts +12 -0
- package/src/cli/ui/render.ts +35 -0
- package/src/cli/ui/theme.ts +63 -0
- package/src/cli/utils.ts +78 -0
- package/src/core/executor/executor.ts +201 -0
- package/src/core/executor/index.ts +13 -0
- package/src/core/executor/job.ts +214 -0
- package/src/core/executor/pipeline.ts +222 -0
- package/src/core/index.ts +11 -0
- package/src/core/registry/index.ts +9 -0
- package/src/core/registry/loader.ts +149 -0
- package/src/core/registry/registry.ts +221 -0
- package/src/core/registry/resolver.ts +206 -0
- package/src/core/schema/helpers.ts +134 -0
- package/src/core/schema/index.ts +8 -0
- package/src/core/schema/shared.ts +102 -0
- package/src/core/schema/types.ts +279 -0
- package/src/core/schema/validator.ts +92 -0
- package/src/definitions/actions/captions.ts +261 -0
- package/src/definitions/actions/edit.ts +298 -0
- package/src/definitions/actions/image.ts +125 -0
- package/src/definitions/actions/index.ts +114 -0
- package/src/definitions/actions/music.ts +205 -0
- package/src/definitions/actions/sync.ts +128 -0
- package/{action/transcribe/index.ts → src/definitions/actions/transcribe.ts} +58 -68
- package/src/definitions/actions/upload.ts +111 -0
- package/src/definitions/actions/video.ts +163 -0
- package/src/definitions/actions/voice.ts +119 -0
- package/src/definitions/index.ts +23 -0
- package/src/definitions/models/elevenlabs.ts +50 -0
- package/src/definitions/models/flux.ts +56 -0
- package/src/definitions/models/index.ts +36 -0
- package/src/definitions/models/kling.ts +56 -0
- package/src/definitions/models/llama.ts +54 -0
- package/src/definitions/models/nano-banana-pro.ts +102 -0
- package/src/definitions/models/sonauto.ts +68 -0
- package/src/definitions/models/soul.ts +65 -0
- package/src/definitions/models/wan.ts +54 -0
- package/src/definitions/models/whisper.ts +44 -0
- package/src/definitions/skills/index.ts +12 -0
- package/src/definitions/skills/talking-character.ts +87 -0
- package/src/definitions/skills/text-to-tiktok.ts +97 -0
- package/src/index.ts +118 -0
- package/src/providers/apify.ts +269 -0
- package/src/providers/base.ts +264 -0
- package/src/providers/elevenlabs.ts +217 -0
- package/src/providers/fal.ts +392 -0
- package/src/providers/ffmpeg.ts +544 -0
- package/src/providers/fireworks.ts +193 -0
- package/src/providers/groq.ts +149 -0
- package/src/providers/higgsfield.ts +145 -0
- package/src/providers/index.ts +143 -0
- package/src/providers/replicate.ts +147 -0
- package/src/providers/storage.ts +206 -0
- package/src/react/cli.ts +52 -0
- package/src/react/elements.ts +146 -0
- package/src/react/examples/branching.tsx +66 -0
- package/src/react/examples/captions-demo.tsx +37 -0
- package/src/react/examples/character-video.tsx +84 -0
- package/src/react/examples/grid.tsx +53 -0
- package/src/react/examples/layouts-demo.tsx +57 -0
- package/src/react/examples/madi.tsx +60 -0
- package/src/react/examples/music-test.tsx +35 -0
- package/src/react/examples/onlyfans-1m/workflow.tsx +88 -0
- package/src/react/examples/orange-portrait.tsx +41 -0
- package/src/react/examples/split-element-demo.tsx +60 -0
- package/src/react/examples/split-layout-demo.tsx +60 -0
- package/src/react/examples/split.tsx +41 -0
- package/src/react/examples/video-grid.tsx +46 -0
- package/src/react/index.ts +43 -0
- package/src/react/layouts/grid.tsx +28 -0
- package/src/react/layouts/index.ts +2 -0
- package/src/react/layouts/split.tsx +20 -0
- package/src/react/react.test.ts +309 -0
- package/src/react/render.ts +21 -0
- package/src/react/renderers/animate.ts +59 -0
- package/src/react/renderers/captions.ts +297 -0
- package/src/react/renderers/clip.ts +248 -0
- package/src/react/renderers/context.ts +17 -0
- package/src/react/renderers/image.ts +109 -0
- package/src/react/renderers/index.ts +22 -0
- package/src/react/renderers/music.ts +60 -0
- package/src/react/renderers/packshot.ts +84 -0
- package/src/react/renderers/progress.ts +173 -0
- package/src/react/renderers/render.ts +243 -0
- package/src/react/renderers/slider.ts +69 -0
- package/src/react/renderers/speech.ts +53 -0
- package/src/react/renderers/split.ts +91 -0
- package/src/react/renderers/subtitle.ts +16 -0
- package/src/react/renderers/swipe.ts +75 -0
- package/src/react/renderers/title.ts +17 -0
- package/src/react/renderers/utils.ts +124 -0
- package/src/react/renderers/video.ts +127 -0
- package/src/react/runtime/jsx-dev-runtime.ts +43 -0
- package/src/react/runtime/jsx-runtime.ts +35 -0
- package/src/react/types.ts +232 -0
- package/src/studio/index.ts +26 -0
- package/src/studio/scanner.ts +102 -0
- package/src/studio/server.ts +554 -0
- package/src/studio/stages.ts +251 -0
- package/src/studio/step-renderer.ts +279 -0
- package/src/studio/types.ts +60 -0
- package/src/studio/ui/cache.html +303 -0
- package/src/studio/ui/index.html +1820 -0
- package/src/tests/all.test.ts +509 -0
- package/src/tests/index.ts +33 -0
- package/src/tests/unit.test.ts +403 -0
- package/tsconfig.cli.json +8 -0
- package/tsconfig.json +21 -3
- package/TEST_RESULTS.md +0 -122
- package/action/captions/SKILL.md +0 -170
- package/action/captions/index.ts +0 -169
- package/action/edit/SKILL.md +0 -235
- package/action/edit/index.ts +0 -437
- package/action/image/SKILL.md +0 -140
- package/action/image/index.ts +0 -105
- package/action/sync/SKILL.md +0 -136
- package/action/sync/index.ts +0 -145
- package/action/transcribe/SKILL.md +0 -179
- package/action/video/SKILL.md +0 -116
- package/action/video/index.ts +0 -125
- package/action/voice/SKILL.md +0 -125
- package/action/voice/index.ts +0 -136
- package/cli/commands/find.ts +0 -58
- package/cli/commands/help.ts +0 -70
- package/cli/commands/list.ts +0 -49
- package/cli/commands/run.ts +0 -237
- package/cli/commands/which.ts +0 -66
- package/cli/discover.ts +0 -66
- package/cli/index.ts +0 -33
- package/cli/runner.ts +0 -65
- package/cli/types.ts +0 -49
- package/cli/ui.ts +0 -185
- package/index.ts +0 -75
- package/lib/README.md +0 -144
- package/lib/ai-sdk/fal.ts +0 -106
- package/lib/ai-sdk/replicate.ts +0 -107
- package/lib/elevenlabs.ts +0 -382
- package/lib/fal.ts +0 -467
- package/lib/ffmpeg.ts +0 -467
- package/lib/fireworks.ts +0 -235
- package/lib/groq.ts +0 -246
- package/lib/higgsfield.ts +0 -176
- package/lib/remotion/SKILL.md +0 -823
- package/lib/remotion/cli.ts +0 -115
- package/lib/remotion/functions.ts +0 -283
- package/lib/remotion/index.ts +0 -19
- package/lib/remotion/templates.ts +0 -73
- package/lib/replicate.ts +0 -304
- package/output.txt +0 -1
- package/test-import.ts +0 -7
- package/test-services.ts +0 -97
- package/utilities/s3.ts +0 -147
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apify provider for running actors and web scraping
|
|
3
|
+
* Supports TikTok scraping, web scraping, and other Apify actors
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ApifyClient } from "apify-client";
|
|
7
|
+
import type { JobStatusUpdate, ProviderConfig } from "../core/schema/types";
|
|
8
|
+
import { BaseProvider } from "./base";
|
|
9
|
+
|
|
10
|
+
export interface ApifyProviderConfig extends ProviderConfig {
|
|
11
|
+
/** Apify API token (defaults to APIFY_TOKEN env var) */
|
|
12
|
+
token?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface RunActorOptions {
|
|
16
|
+
/** Actor ID in format "username/actor-name" */
|
|
17
|
+
actorId: string;
|
|
18
|
+
/** Input parameters for the actor */
|
|
19
|
+
input?: Record<string, unknown>;
|
|
20
|
+
/** Whether to wait for the actor to finish (default: true) */
|
|
21
|
+
waitForFinish?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ApifyRunResult {
|
|
25
|
+
/** Run ID */
|
|
26
|
+
runId: string;
|
|
27
|
+
/** Dataset ID containing results */
|
|
28
|
+
datasetId: string;
|
|
29
|
+
/** Run status */
|
|
30
|
+
status: string;
|
|
31
|
+
/** Result items (if waitForFinish was true) */
|
|
32
|
+
items?: unknown[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class ApifyProvider extends BaseProvider {
|
|
36
|
+
readonly name = "apify";
|
|
37
|
+
private client: ApifyClient;
|
|
38
|
+
|
|
39
|
+
constructor(config?: ApifyProviderConfig) {
|
|
40
|
+
super(config);
|
|
41
|
+
this.client = new ApifyClient({
|
|
42
|
+
token: config?.token || process.env.APIFY_TOKEN,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Submit an actor run job
|
|
48
|
+
* @param model - Actor ID (e.g., "clockworks/tiktok-scraper")
|
|
49
|
+
* @param inputs - Actor input parameters
|
|
50
|
+
*/
|
|
51
|
+
async submit(
|
|
52
|
+
model: string,
|
|
53
|
+
inputs: Record<string, unknown>,
|
|
54
|
+
_config?: ProviderConfig,
|
|
55
|
+
): Promise<string> {
|
|
56
|
+
console.log(`[apify] submitting actor: ${model}`);
|
|
57
|
+
|
|
58
|
+
const actor = this.client.actor(model);
|
|
59
|
+
const run = await actor.start(inputs);
|
|
60
|
+
|
|
61
|
+
console.log(`[apify] run started: ${run.id}`);
|
|
62
|
+
return run.id;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get the status of an actor run
|
|
67
|
+
*/
|
|
68
|
+
async getStatus(jobId: string): Promise<JobStatusUpdate> {
|
|
69
|
+
const run = await this.client.run(jobId).get();
|
|
70
|
+
|
|
71
|
+
if (!run) {
|
|
72
|
+
return { status: "failed", error: "Run not found" };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const statusMap: Record<string, JobStatusUpdate["status"]> = {
|
|
76
|
+
READY: "queued",
|
|
77
|
+
RUNNING: "processing",
|
|
78
|
+
SUCCEEDED: "completed",
|
|
79
|
+
FAILED: "failed",
|
|
80
|
+
ABORTED: "cancelled",
|
|
81
|
+
ABORTING: "cancelled",
|
|
82
|
+
TIMED_OUT: "failed",
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
status: statusMap[run.status] ?? "processing",
|
|
87
|
+
output: run.defaultDatasetId,
|
|
88
|
+
error: run.status === "FAILED" ? "Actor run failed" : undefined,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get the results of a completed actor run
|
|
94
|
+
*/
|
|
95
|
+
async getResult(jobId: string): Promise<unknown> {
|
|
96
|
+
const run = await this.client.run(jobId).get();
|
|
97
|
+
|
|
98
|
+
if (!run?.defaultDatasetId) {
|
|
99
|
+
throw new Error("Run not found or has no dataset");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const { items } = await this.client
|
|
103
|
+
.dataset(run.defaultDatasetId)
|
|
104
|
+
.listItems();
|
|
105
|
+
return items;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Cancel a running actor
|
|
110
|
+
*/
|
|
111
|
+
override async cancel(jobId: string): Promise<void> {
|
|
112
|
+
await this.client.run(jobId).abort();
|
|
113
|
+
console.log(`[apify] run aborted: ${jobId}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ============================================================================
|
|
117
|
+
// High-level convenience methods
|
|
118
|
+
// ============================================================================
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Run an actor and optionally wait for results
|
|
122
|
+
*/
|
|
123
|
+
async runActor(options: RunActorOptions): Promise<ApifyRunResult> {
|
|
124
|
+
console.log(`[apify] running actor: ${options.actorId}`);
|
|
125
|
+
|
|
126
|
+
const actor = this.client.actor(options.actorId);
|
|
127
|
+
|
|
128
|
+
if (options.waitForFinish !== false) {
|
|
129
|
+
// call() waits for the run to finish
|
|
130
|
+
const run = await actor.call(options.input);
|
|
131
|
+
console.log(`[apify] run completed: ${run.id}`);
|
|
132
|
+
|
|
133
|
+
// Get results from dataset
|
|
134
|
+
const { items } = await this.client
|
|
135
|
+
.dataset(run.defaultDatasetId)
|
|
136
|
+
.listItems();
|
|
137
|
+
console.log(`[apify] retrieved ${items.length} items`);
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
runId: run.id,
|
|
141
|
+
datasetId: run.defaultDatasetId,
|
|
142
|
+
status: run.status,
|
|
143
|
+
items,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// start() returns immediately
|
|
148
|
+
const run = await actor.start(options.input);
|
|
149
|
+
console.log(`[apify] run started: ${run.id}`);
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
runId: run.id,
|
|
153
|
+
datasetId: run.defaultDatasetId,
|
|
154
|
+
status: run.status,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get items from a dataset
|
|
160
|
+
*/
|
|
161
|
+
async getDataset(datasetId: string): Promise<unknown[]> {
|
|
162
|
+
console.log(`[apify] fetching dataset: ${datasetId}`);
|
|
163
|
+
const { items } = await this.client.dataset(datasetId).listItems();
|
|
164
|
+
console.log(`[apify] retrieved ${items.length} items`);
|
|
165
|
+
return items;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Get run info
|
|
170
|
+
*/
|
|
171
|
+
async getRunInfo(runId: string) {
|
|
172
|
+
console.log(`[apify] fetching run info: ${runId}`);
|
|
173
|
+
const run = await this.client.run(runId).get();
|
|
174
|
+
return run;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Wait for a run to finish
|
|
179
|
+
*/
|
|
180
|
+
async waitForRun(runId: string) {
|
|
181
|
+
console.log(`[apify] waiting for run: ${runId}`);
|
|
182
|
+
const run = await this.client.run(runId).waitForFinish();
|
|
183
|
+
console.log(`[apify] run finished with status: ${run?.status}`);
|
|
184
|
+
return run;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get value from key-value store
|
|
189
|
+
*/
|
|
190
|
+
async getKeyValueStoreValue(storeId: string, key: string) {
|
|
191
|
+
console.log(`[apify] fetching key "${key}" from store: ${storeId}`);
|
|
192
|
+
const value = await this.client.keyValueStore(storeId).getRecord(key);
|
|
193
|
+
return value;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Download videos from scraped results using yt-dlp
|
|
198
|
+
*/
|
|
199
|
+
async downloadVideos(
|
|
200
|
+
items: Array<{ webVideoUrl?: string }>,
|
|
201
|
+
outputDir = "output/videos",
|
|
202
|
+
): Promise<string[]> {
|
|
203
|
+
const urls = items
|
|
204
|
+
.map((item) => item.webVideoUrl)
|
|
205
|
+
.filter((url): url is string => !!url);
|
|
206
|
+
|
|
207
|
+
console.log(`[apify] downloading ${urls.length} videos`);
|
|
208
|
+
|
|
209
|
+
// Create download dir if needed
|
|
210
|
+
await Bun.$`mkdir -p ${outputDir}`;
|
|
211
|
+
|
|
212
|
+
const downloaded: string[] = [];
|
|
213
|
+
|
|
214
|
+
for (const url of urls) {
|
|
215
|
+
console.log(`[apify] downloading: ${url}`);
|
|
216
|
+
try {
|
|
217
|
+
await Bun.$`yt-dlp -o "${outputDir}/%(id)s.%(ext)s" ${url}`;
|
|
218
|
+
downloaded.push(url);
|
|
219
|
+
console.log(`[apify] downloaded successfully`);
|
|
220
|
+
} catch (err) {
|
|
221
|
+
console.error(`[apify] failed to download ${url}:`, err);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
console.log(`[apify] all downloads complete. saved to: ${outputDir}`);
|
|
226
|
+
return downloaded;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Popular actors registry
|
|
231
|
+
export const ACTORS = {
|
|
232
|
+
TIKTOK: {
|
|
233
|
+
SCRAPER: "clockworks/tiktok-scraper",
|
|
234
|
+
HASHTAG: "clockworks/tiktok-hashtag-scraper",
|
|
235
|
+
PROFILE: "clockworks/tiktok-profile-scraper",
|
|
236
|
+
},
|
|
237
|
+
WEB: {
|
|
238
|
+
SCRAPER: "apify/web-scraper",
|
|
239
|
+
CHEERIO: "apify/cheerio-scraper",
|
|
240
|
+
PUPPETEER: "apify/puppeteer-scraper",
|
|
241
|
+
},
|
|
242
|
+
SOCIAL: {
|
|
243
|
+
INSTAGRAM: "apify/instagram-scraper",
|
|
244
|
+
TWITTER: "apify/twitter-scraper",
|
|
245
|
+
YOUTUBE: "bernardo/youtube-scraper",
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// Export singleton instance
|
|
250
|
+
export const apifyProvider = new ApifyProvider();
|
|
251
|
+
|
|
252
|
+
// Export convenience functions
|
|
253
|
+
export const runActor = (options: RunActorOptions) =>
|
|
254
|
+
apifyProvider.runActor(options);
|
|
255
|
+
|
|
256
|
+
export const getDataset = (datasetId: string) =>
|
|
257
|
+
apifyProvider.getDataset(datasetId);
|
|
258
|
+
|
|
259
|
+
export const getRunInfo = (runId: string) => apifyProvider.getRunInfo(runId);
|
|
260
|
+
|
|
261
|
+
export const waitForRun = (runId: string) => apifyProvider.waitForRun(runId);
|
|
262
|
+
|
|
263
|
+
export const getKeyValueStoreValue = (storeId: string, key: string) =>
|
|
264
|
+
apifyProvider.getKeyValueStoreValue(storeId, key);
|
|
265
|
+
|
|
266
|
+
export const downloadVideos = (
|
|
267
|
+
items: Array<{ webVideoUrl?: string }>,
|
|
268
|
+
outputDir?: string,
|
|
269
|
+
) => apifyProvider.downloadVideos(items, outputDir);
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base provider interface and abstract class
|
|
3
|
+
* All providers implement this interface for consistent execution
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
JobStatusUpdate,
|
|
8
|
+
Provider,
|
|
9
|
+
ProviderConfig,
|
|
10
|
+
} from "../core/schema/types";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Abstract base class for providers
|
|
14
|
+
* Provides common functionality and enforces the Provider interface
|
|
15
|
+
*/
|
|
16
|
+
export abstract class BaseProvider implements Provider {
|
|
17
|
+
abstract readonly name: string;
|
|
18
|
+
|
|
19
|
+
protected config: ProviderConfig;
|
|
20
|
+
|
|
21
|
+
constructor(config: ProviderConfig = {}) {
|
|
22
|
+
this.config = {
|
|
23
|
+
timeout: 300000, // 5 minutes default
|
|
24
|
+
retries: 3,
|
|
25
|
+
...config,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Submit a job to the provider
|
|
31
|
+
*/
|
|
32
|
+
abstract submit(
|
|
33
|
+
model: string,
|
|
34
|
+
inputs: Record<string, unknown>,
|
|
35
|
+
config?: ProviderConfig,
|
|
36
|
+
): Promise<string>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get the current status of a job
|
|
40
|
+
*/
|
|
41
|
+
abstract getStatus(jobId: string): Promise<JobStatusUpdate>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get the result of a completed job
|
|
45
|
+
*/
|
|
46
|
+
abstract getResult(jobId: string): Promise<unknown>;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Optional: Cancel a running job
|
|
50
|
+
*/
|
|
51
|
+
async cancel?(jobId: string): Promise<void>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Optional: Upload a file to the provider's storage
|
|
55
|
+
*/
|
|
56
|
+
async uploadFile?(
|
|
57
|
+
file: File | Blob | ArrayBuffer,
|
|
58
|
+
filename?: string,
|
|
59
|
+
): Promise<string>;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Helper: Wait for a job to complete with polling
|
|
63
|
+
*/
|
|
64
|
+
protected async waitForCompletion(
|
|
65
|
+
jobId: string,
|
|
66
|
+
options: {
|
|
67
|
+
maxWait?: number;
|
|
68
|
+
pollInterval?: number;
|
|
69
|
+
onProgress?: (progress: number, logs?: string[]) => void;
|
|
70
|
+
} = {},
|
|
71
|
+
): Promise<unknown> {
|
|
72
|
+
const { maxWait = this.config.timeout ?? 300000, pollInterval = 2000 } =
|
|
73
|
+
options;
|
|
74
|
+
const startTime = Date.now();
|
|
75
|
+
let currentInterval = pollInterval;
|
|
76
|
+
|
|
77
|
+
while (Date.now() - startTime < maxWait) {
|
|
78
|
+
const status = await this.getStatus(jobId);
|
|
79
|
+
|
|
80
|
+
if (options.onProgress && status.progress !== undefined) {
|
|
81
|
+
options.onProgress(status.progress, status.logs);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (status.status === "completed") {
|
|
85
|
+
return status.output ?? (await this.getResult(jobId));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (status.status === "failed") {
|
|
89
|
+
throw new Error(status.error ?? "Job failed");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (status.status === "cancelled") {
|
|
93
|
+
throw new Error("Job was cancelled");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Exponential backoff with cap
|
|
97
|
+
await this.sleep(currentInterval);
|
|
98
|
+
currentInterval = Math.min(currentInterval * 1.5, 10000);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
throw new Error(`Job ${jobId} timed out after ${maxWait}ms`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Helper: Sleep for a duration
|
|
106
|
+
*/
|
|
107
|
+
protected sleep(ms: number): Promise<void> {
|
|
108
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Helper: Retry a function with exponential backoff
|
|
113
|
+
*/
|
|
114
|
+
protected async retry<T>(
|
|
115
|
+
fn: () => Promise<T>,
|
|
116
|
+
options: { retries?: number; delay?: number } = {},
|
|
117
|
+
): Promise<T> {
|
|
118
|
+
const { retries = this.config.retries ?? 3, delay = 1000 } = options;
|
|
119
|
+
let lastError: Error | undefined;
|
|
120
|
+
|
|
121
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
122
|
+
try {
|
|
123
|
+
return await fn();
|
|
124
|
+
} catch (error) {
|
|
125
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
126
|
+
if (attempt < retries) {
|
|
127
|
+
await this.sleep(delay * 2 ** attempt);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
throw lastError;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Provider registry for managing multiple providers
|
|
138
|
+
*/
|
|
139
|
+
export class ProviderRegistry {
|
|
140
|
+
private providers = new Map<string, Provider>();
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Register a provider
|
|
144
|
+
*/
|
|
145
|
+
register(provider: Provider): void {
|
|
146
|
+
this.providers.set(provider.name, provider);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get a provider by name
|
|
151
|
+
*/
|
|
152
|
+
get(name: string): Provider | undefined {
|
|
153
|
+
return this.providers.get(name);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Check if a provider exists
|
|
158
|
+
*/
|
|
159
|
+
has(name: string): boolean {
|
|
160
|
+
return this.providers.has(name);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get all registered providers
|
|
165
|
+
*/
|
|
166
|
+
all(): Provider[] {
|
|
167
|
+
return Array.from(this.providers.values());
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get provider names
|
|
172
|
+
*/
|
|
173
|
+
names(): string[] {
|
|
174
|
+
return Array.from(this.providers.keys());
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Global provider registry instance
|
|
180
|
+
*/
|
|
181
|
+
export const providers = new ProviderRegistry();
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Helper type for provider-specific result structures
|
|
185
|
+
*/
|
|
186
|
+
export interface ProviderResult<T = unknown> {
|
|
187
|
+
data: T;
|
|
188
|
+
requestId?: string;
|
|
189
|
+
timing?: {
|
|
190
|
+
queueTime?: number;
|
|
191
|
+
processingTime?: number;
|
|
192
|
+
totalTime?: number;
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Helper: Ensure a path is a URL (upload local files if needed)
|
|
198
|
+
*/
|
|
199
|
+
export async function ensureUrl(
|
|
200
|
+
pathOrUrl: string,
|
|
201
|
+
uploader: (file: ArrayBuffer) => Promise<string>,
|
|
202
|
+
): Promise<string> {
|
|
203
|
+
// Already a URL
|
|
204
|
+
if (pathOrUrl.startsWith("http://") || pathOrUrl.startsWith("https://")) {
|
|
205
|
+
return pathOrUrl;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Local file - read and upload
|
|
209
|
+
const file = Bun.file(pathOrUrl);
|
|
210
|
+
if (!(await file.exists())) {
|
|
211
|
+
throw new Error(`Local file not found: ${pathOrUrl}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const buffer = await file.arrayBuffer();
|
|
215
|
+
return uploader(buffer);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Helper: Download a URL to a local file
|
|
220
|
+
*/
|
|
221
|
+
export async function downloadToFile(
|
|
222
|
+
url: string,
|
|
223
|
+
outputPath: string,
|
|
224
|
+
): Promise<string> {
|
|
225
|
+
const response = await fetch(url);
|
|
226
|
+
if (!response.ok) {
|
|
227
|
+
throw new Error(`Failed to download: ${response.statusText}`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const buffer = await response.arrayBuffer();
|
|
231
|
+
await Bun.write(outputPath, buffer);
|
|
232
|
+
return outputPath;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Helper: Get file extension from URL or content type
|
|
237
|
+
*/
|
|
238
|
+
export function getExtension(url: string, contentType?: string): string {
|
|
239
|
+
// Try to get from URL
|
|
240
|
+
const urlPath = new URL(url).pathname;
|
|
241
|
+
const urlExt = urlPath.split(".").pop();
|
|
242
|
+
if (urlExt && urlExt.length <= 4) {
|
|
243
|
+
return urlExt;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Try content type
|
|
247
|
+
if (contentType) {
|
|
248
|
+
const typeMap: Record<string, string> = {
|
|
249
|
+
"video/mp4": "mp4",
|
|
250
|
+
"video/webm": "webm",
|
|
251
|
+
"video/quicktime": "mov",
|
|
252
|
+
"image/jpeg": "jpg",
|
|
253
|
+
"image/png": "png",
|
|
254
|
+
"image/webp": "webp",
|
|
255
|
+
"image/gif": "gif",
|
|
256
|
+
"audio/mpeg": "mp3",
|
|
257
|
+
"audio/wav": "wav",
|
|
258
|
+
"audio/ogg": "ogg",
|
|
259
|
+
};
|
|
260
|
+
return typeMap[contentType] ?? "bin";
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return "bin";
|
|
264
|
+
}
|