import { ActorListSortBy, ApifyClient } from 'apify-client@^2.19.0';
type ApifyApiKey = {
api_key: string;
};
type Apify = {
token: string;
};
type ActorSource = 'RECENTLY_USED_ACTORS' | 'APIFY_STORE_ACTORS';
type MemoryInMb =
| '128'
| '256'
| '512'
| '1024'
| '2048'
| '4096'
| '8192'
| '16384'
| '32768';
export type DynSelect_actorId = string;
export async function actorId(actorSource: ActorSource, api_key?: ApifyApiKey, oauth_token?: Apify) {
if (!api_key?.api_key && !oauth_token?.token) {
return [{ value: '', label: 'Missing Apify API key or OAuth token' }];
}
const client = createClient(api_key, oauth_token);
const mapActorToSelectOption = (actor: any) => {
const optionName = actor.title
? `${actor.title} (${actor.username}/${actor.name})`
: `${actor.username}/${actor.name}`;
return {
label: optionName,
value: actor.id,
};
};
try {
if (actorSource === 'RECENTLY_USED_ACTORS') {
const recentActors = await client.actors().list({
limit: 100,
offset: 0,
sortBy: ActorListSortBy.LAST_RUN_STARTED_AT,
desc: true,
});
return recentActors.items.map(mapActorToSelectOption);
}
const storeActors = await client.store().list({
limit: 1000,
offset: 0,
});
return storeActors.items.map(mapActorToSelectOption);
} catch (error: any) {
return [
{ value: '', label: `Failed to load actors: ${error.message || error}` },
];
}
}
const createClient = (api_key?: ApifyApiKey, oauth_token?: Apify): ApifyClient => {
const token = oauth_token?.token ?? api_key?.api_key;
if (!token) {
throw new Error('Missing Apify API key or OAuth token');
}
return new ApifyClient({
token: token,
requestInterceptors: [
(request) => {
if (!request.headers) {
request.headers = {};
}
request.headers['x-apify-integration-platform'] = 'windmill';
return request;
},
],
});
};
async function pollRunStatus(
client: ApifyClient,
runId: string,
options: { throwIfNotSucceeded: boolean; } = { throwIfNotSucceeded: false }
): Promise<any> {
let status = '';
let runData: any;
while (true) {
runData = await client.run(runId).get();
status = runData.status;
if (['SUCCEEDED', 'FAILED', 'ABORTED', 'TIMED-OUT'].includes(status)) break;
await new Promise((res) => setTimeout(res, 1000));
}
if (options.throwIfNotSucceeded && status !== 'SUCCEEDED') {
throw new Error(`Actor run did not succeed: ${status}`);
}
return runData;
}
async function getBuildByTag(
client: ApifyClient,
actorId: string,
buildTag: string,
actorData: any
) {
const buildByTag = actorData.taggedBuilds && actorData.taggedBuilds[buildTag];
if (!buildByTag?.buildId) {
throw new Error(
`Build tag '${buildTag}' does not exist for actor ${
actorData.title ?? actorData.name ?? actorId
}`
);
}
const build = await client.build(buildByTag.buildId).get();
if (!build) {
throw new Error(
`Build with ID '${buildByTag.buildId}' not found for actor ${actorId}`
);
}
return build;
}
// get default build
async function getDefaultBuild(client: ApifyClient, actorId: string) {
const defaultBuildClient = await client.actor(actorId).defaultBuild();
const defaultBuild = await defaultBuildClient.get();
if (!defaultBuild) {
throw new Error(`Could not fetch default build for actor ${actorId}`);
}
return defaultBuild;
}
// get default inputs from build
function getDefaultInputsFromBuild(build: any) {
const buildInputProperties = build?.actorDefinition?.input?.properties;
const defaultInput: Record<string, any> = {};
if (buildInputProperties && typeof buildInputProperties === 'object') {
for (const [key, property] of Object.entries(buildInputProperties)) {
if (
property &&
typeof property === 'object' &&
'prefill' in property &&
(property as any).prefill !== undefined &&
(property as any).prefill !== null
) {
defaultInput[key] = (property as any).prefill;
}
}
}
return defaultInput;
}
export async function main(
actorSource: ActorSource,
actorId: DynSelect_actorId,
customBody: object = {},
timeout?: number | null,
memoryInMb: MemoryInMb = '1024',
buildTag?: string | null,
waitForFinish: boolean = true,
api_key?: ApifyApiKey,
oauth_token?: Apify,
) {
const client = createClient(api_key, oauth_token);
try {
// 1. Get the actor details
const actorData = await client.actor(actorId).get();
if (!actorData) {
return {
error: `Actor ${actorId} not found`,
};
}
// 2. Build selection logic
let build: any;
if (buildTag) {
build = await getBuildByTag(client, actorId, buildTag, actorData);
} else {
build = await getDefaultBuild(client, actorId);
}
// 3. Get default input for this build
const defaultInput = getDefaultInputsFromBuild(build);
// 4. Merge default input and user's input (user's input overrides)
const mergedInput = { ...defaultInput, ...customBody };
// 5. Prepare run options
const runOptions: any = {
waitSecs: 0, // set initial run actor to not wait for finish
};
if (timeout != null) runOptions.timeout = timeout;
if (memoryInMb != null) runOptions.memory = Number(memoryInMb);
if (build?.buildNumber) runOptions.build = build.buildNumber;
// 6. Run the actor
const run = await client.actor(actorId).call(mergedInput, runOptions);
if (!run?.id) {
return {
error: `Run ID not found after running the actor`,
};
}
// 7a. If waitForFinish is false, return the run data immediately
if (!waitForFinish) {
return { ...run };
}
// 7b. Start polling for run status until it reaches a terminal state
const runId = run.id;
const lastRunData = await pollRunStatus(client, runId);
return { ...lastRunData };
} catch (error: any) {
return {
error: `Failed to run actor. Reason: ${error.message}`,
};
}
}
Submitted by jakub.drobnik222 88 days ago