1 | import { ActorListSortBy, ApifyClient } from 'apify-client@^2.19.0'; |
2 |
|
3 | type ApifyApiKey = { |
4 | api_key: string; |
5 | }; |
6 |
|
7 | type Apify = { |
8 | token: string; |
9 | }; |
10 |
|
11 | type ActorSource = 'RECENTLY_USED_ACTORS' | 'APIFY_STORE_ACTORS'; |
12 |
|
13 | type MemoryInMb = |
14 | | '128' |
15 | | '256' |
16 | | '512' |
17 | | '1024' |
18 | | '2048' |
19 | | '4096' |
20 | | '8192' |
21 | | '16384' |
22 | | '32768'; |
23 |
|
24 | export type DynSelect_actorId = string; |
25 | export async function actorId(actorSource: ActorSource, api_key?: ApifyApiKey, oauth_token?: Apify) { |
26 | if (!api_key?.api_key && !oauth_token?.token) { |
27 | return [{ value: '', label: 'Missing Apify API key or OAuth token' }]; |
28 | } |
29 |
|
30 | const client = createClient(api_key, oauth_token); |
31 |
|
32 | const mapActorToSelectOption = (actor: any) => { |
33 | const optionName = actor.title |
34 | ? `${actor.title} (${actor.username}/${actor.name})` |
35 | : `${actor.username}/${actor.name}`; |
36 |
|
37 | return { |
38 | label: optionName, |
39 | value: actor.id, |
40 | }; |
41 | }; |
42 |
|
43 | try { |
44 | if (actorSource === 'RECENTLY_USED_ACTORS') { |
45 | const recentActors = await client.actors().list({ |
46 | limit: 100, |
47 | offset: 0, |
48 | sortBy: ActorListSortBy.LAST_RUN_STARTED_AT, |
49 | desc: true, |
50 | }); |
51 | return recentActors.items.map(mapActorToSelectOption); |
52 | } |
53 |
|
54 | const storeActors = await client.store().list({ |
55 | limit: 1000, |
56 | offset: 0, |
57 | }); |
58 | return storeActors.items.map(mapActorToSelectOption); |
59 | } catch (error: any) { |
60 | return [ |
61 | { value: '', label: `Failed to load actors: ${error.message || error}` }, |
62 | ]; |
63 | } |
64 | } |
65 |
|
66 | const createClient = (api_key?: ApifyApiKey, oauth_token?: Apify): ApifyClient => { |
67 | const token = oauth_token?.token ?? api_key?.api_key; |
68 | if (!token) { |
69 | throw new Error('Missing Apify API key or OAuth token'); |
70 | } |
71 |
|
72 | return new ApifyClient({ |
73 | token: token, |
74 | requestInterceptors: [ |
75 | (request) => { |
76 | if (!request.headers) { |
77 | request.headers = {}; |
78 | } |
79 | request.headers['x-apify-integration-platform'] = 'windmill'; |
80 | return request; |
81 | }, |
82 | ], |
83 | }); |
84 | }; |
85 |
|
86 | async function pollRunStatus( |
87 | client: ApifyClient, |
88 | runId: string, |
89 | options: { throwIfNotSucceeded: boolean; } = { throwIfNotSucceeded: false } |
90 | ): Promise<any> { |
91 | let status = ''; |
92 | let runData: any; |
93 | while (true) { |
94 | runData = await client.run(runId).get(); |
95 | status = runData.status; |
96 | if (['SUCCEEDED', 'FAILED', 'ABORTED', 'TIMED-OUT'].includes(status)) break; |
97 | await new Promise((res) => setTimeout(res, 1000)); |
98 | } |
99 |
|
100 | if (options.throwIfNotSucceeded && status !== 'SUCCEEDED') { |
101 | throw new Error(`Actor run did not succeed: ${status}`); |
102 | } |
103 |
|
104 | return runData; |
105 | } |
106 |
|
107 | async function getBuildByTag( |
108 | client: ApifyClient, |
109 | actorId: string, |
110 | buildTag: string, |
111 | actorData: any |
112 | ) { |
113 | const buildByTag = actorData.taggedBuilds && actorData.taggedBuilds[buildTag]; |
114 | if (!buildByTag?.buildId) { |
115 | throw new Error( |
116 | `Build tag '${buildTag}' does not exist for actor ${ |
117 | actorData.title ?? actorData.name ?? actorId |
118 | }` |
119 | ); |
120 | } |
121 | const build = await client.build(buildByTag.buildId).get(); |
122 | if (!build) { |
123 | throw new Error( |
124 | `Build with ID '${buildByTag.buildId}' not found for actor ${actorId}` |
125 | ); |
126 | } |
127 | return build; |
128 | } |
129 |
|
130 | |
131 | async function getDefaultBuild(client: ApifyClient, actorId: string) { |
132 | const defaultBuildClient = await client.actor(actorId).defaultBuild(); |
133 |
|
134 | const defaultBuild = await defaultBuildClient.get(); |
135 |
|
136 | if (!defaultBuild) { |
137 | throw new Error(`Could not fetch default build for actor ${actorId}`); |
138 | } |
139 | return defaultBuild; |
140 | } |
141 |
|
142 | |
143 | function getDefaultInputsFromBuild(build: any) { |
144 | const buildInputProperties = build?.actorDefinition?.input?.properties; |
145 | const defaultInput: Record<string, any> = {}; |
146 | if (buildInputProperties && typeof buildInputProperties === 'object') { |
147 | for (const [key, property] of Object.entries(buildInputProperties)) { |
148 | if ( |
149 | property && |
150 | typeof property === 'object' && |
151 | 'prefill' in property && |
152 | (property as any).prefill !== undefined && |
153 | (property as any).prefill !== null |
154 | ) { |
155 | defaultInput[key] = (property as any).prefill; |
156 | } |
157 | } |
158 | } |
159 | return defaultInput; |
160 | } |
161 |
|
162 | export async function main( |
163 | actorSource: ActorSource, |
164 | actorId: DynSelect_actorId, |
165 | customBody: object = {}, |
166 | timeout?: number | null, |
167 | memoryInMb: MemoryInMb = '1024', |
168 | buildTag?: string | null, |
169 | waitForFinish: boolean = true, |
170 | api_key?: ApifyApiKey, |
171 | oauth_token?: Apify, |
172 | ) { |
173 | const client = createClient(api_key, oauth_token); |
174 |
|
175 | try { |
176 | |
177 | const actorData = await client.actor(actorId).get(); |
178 | if (!actorData) { |
179 | return { |
180 | error: `Actor ${actorId} not found`, |
181 | }; |
182 | } |
183 |
|
184 | |
185 | let build: any; |
186 | if (buildTag) { |
187 | build = await getBuildByTag(client, actorId, buildTag, actorData); |
188 | } else { |
189 | build = await getDefaultBuild(client, actorId); |
190 | } |
191 |
|
192 | |
193 | const defaultInput = getDefaultInputsFromBuild(build); |
194 |
|
195 | |
196 | const mergedInput = { ...defaultInput, ...customBody }; |
197 |
|
198 | |
199 | const runOptions: any = { |
200 | waitSecs: 0, |
201 | }; |
202 |
|
203 | if (timeout != null) runOptions.timeout = timeout; |
204 | if (memoryInMb != null) runOptions.memory = Number(memoryInMb); |
205 | if (build?.buildNumber) runOptions.build = build.buildNumber; |
206 |
|
207 | |
208 | const run = await client.actor(actorId).call(mergedInput, runOptions); |
209 | if (!run?.id) { |
210 | return { |
211 | error: `Run ID not found after running the actor`, |
212 | }; |
213 | } |
214 |
|
215 | |
216 | if (!waitForFinish) { |
217 | return { ...run }; |
218 | } |
219 |
|
220 | |
221 | const runId = run.id; |
222 | const lastRunData = await pollRunStatus(client, runId); |
223 | return { ...lastRunData }; |
224 | } catch (error: any) { |
225 | return { |
226 | error: `Failed to run actor. Reason: ${error.message}`, |
227 | }; |
228 | } |
229 | } |
230 |
|