Edits history of script submission #22235 for ' Run Actor (apify)'

  • bun
    One script reply has been approved by the moderators
    Ap­pro­ved
    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 172 days ago