1const { Actor } = require('apify');
2const express = require('express');
3const fetch = require('node-fetch');
4const { Dataset } = require('apify');
5
6Actor.main(async () => {
7 const input = await Actor.getInput();
8
9
10 const VPS_BASE_URL = 'http://h8swosc0oo0c8wwokkkkk00g.144.24.194.211.sslip.io:3000/api/v1';
11 const VPS_DOWNLOAD_ENDPOINT = '/download';
12 const VPS_STATUS_ENDPOINT = '/status';
13 const VPS_API_KEY = 'WYwfEDS1IiZ3iGXwradItR43EpC5TCih';
14 const REQUEST_TIMEOUT = 300000;
15
16 const app = express();
17 app.use(express.json());
18
19
20 app.get('/health', (req, res) => {
21 res.json({ status: 'ok', timestamp: new Date().toISOString() });
22 });
23
24
25 app.post('/run', async (req, res) => {
26 try {
27 const { imageUrl, width, height, quality = 80, format = 'webp' } = req.body;
28
29 if (!imageUrl) {
30 return res.status(400).json({
31 success: false,
32 error: 'imageUrl is required'
33 });
34 }
35
36 const vpsUrl = `${VPS_BASE_URL}${VPS_DOWNLOAD_ENDPOINT}`;
37 const requestBody = {
38 url: imageUrl,
39 width,
40 height,
41 quality,
42 format
43 };
44
45 console.log(`🚀 Optimizing image: ${imageUrl.substring(0, 50)}...`);
46
47 const response = await fetch(vpsUrl, {
48 method: 'POST',
49 headers: {
50 'Content-Type': 'application/json',
51 'Authorization': `Bearer ${VPS_API_KEY}`,
52 'User-Agent': 'Apify-Proxy/1.0'
53 },
54 body: JSON.stringify(requestBody),
55 timeout: REQUEST_TIMEOUT
56 });
57
58 const responseData = await response.json();
59
60
61
62 res.status(response.status).json(responseData);
63
64 } catch (error) {
65 res.status(500).json({
66 success: false,
67 error: 'Failed to connect to VPS API',
68 details: error.message
69 });
70 }
71 });
72
73
74 app.get('/status/:jobId', async (req, res) => {
75 try {
76 const { jobId } = req.params;
77
78 if (!jobId) {
79 return res.status(400).json({
80 success: false,
81 error: 'jobId is required'
82 });
83 }
84
85 const vpsUrl = `${VPS_BASE_URL}${VPS_STATUS_ENDPOINT}/${jobId}`;
86
87
88 const response = await fetch(vpsUrl, {
89 method: 'GET',
90 headers: {
91 'Authorization': `Bearer ${VPS_API_KEY}`,
92 'User-Agent': 'Apify-Proxy/1.0'
93 },
94 timeout: REQUEST_TIMEOUT
95 });
96
97 const responseData = await response.json();
98
99
100
101 res.status(response.status).json(responseData);
102
103 } catch (error) {
104 res.status(500).json({
105 success: false,
106 error: 'Failed to connect to VPS API',
107 details: error.message
108 });
109 }
110 });
111
112
113 if (input) {
114 const { action, imageUrl, jobId, width, height, quality, format } = input;
115
116 if (action === 'submit') {
117 if (!imageUrl) {
118 throw new Error('imageUrl is required for submit action');
119 }
120
121 const vpsUrl = `${VPS_BASE_URL}${VPS_DOWNLOAD_ENDPOINT}`;
122 const requestBody = {
123 url: imageUrl,
124 width,
125 height,
126 quality,
127 format
128 };
129
130 console.log(`📤 Submitting image for async optimization...`);
131
132 const response = await fetch(vpsUrl, {
133 method: 'POST',
134 headers: {
135 'Content-Type': 'application/json',
136 'Authorization': `Bearer ${VPS_API_KEY}`,
137 'User-Agent': 'Apify-Proxy/1.0'
138 },
139 body: JSON.stringify(requestBody),
140 timeout: REQUEST_TIMEOUT
141 });
142
143 const result = await response.json();
144
145 if (!response.ok) {
146 throw new Error(`VPS API error: ${result.error || 'Unknown error'}`);
147 }
148
149 await Actor.setValue('OUTPUT', result);
150
151 console.log(`✅ Job submitted! Check status with job ID: ${result.data?.id || 'unknown'}`);
152
153 } else if (action === 'optimize') {
154 if (!imageUrl) {
155 throw new Error('imageUrl is required for optimize action');
156 }
157
158 const vpsUrl = `${VPS_BASE_URL}/optimize`;
159 const requestBody = {
160 url: imageUrl,
161 optimization: {
162 maxWidth: width || 1200,
163 maxHeight: height || 1200,
164 quality: quality || 80,
165 format: format || 'webp'
166 }
167 };
168
169
170 const response = await fetch(vpsUrl, {
171 method: 'POST',
172 headers: {
173 'Content-Type': 'application/json',
174 'Authorization': `Bearer ${VPS_API_KEY}`,
175 'User-Agent': 'Apify-Proxy/1.0'
176 },
177 body: JSON.stringify(requestBody),
178 timeout: REQUEST_TIMEOUT
179 });
180
181 const result = await response.json();
182
183
184 if (!response.ok) {
185 throw new Error(`VPS API error: ${result.message || result.error || 'Unknown error'}`);
186 }
187
188
189 const jobData = result.data?.result;
190 const isCompleted = result.data?.status === 'completed' && jobData?.success;
191
192 let userFriendlyResult;
193
194 if (isCompleted) {
195
196 const compressionSaved = jobData.originalSize && jobData.optimizedSize
197 ? Math.round(((jobData.originalSize - jobData.optimizedSize) / jobData.originalSize) * 100)
198 : 0;
199
200 userFriendlyResult = {
201 success: true,
202 optimizedUrl: jobData.uploadResult?.url || '',
203 originalSize: jobData.originalSize,
204 optimizedSize: jobData.optimizedSize,
205 compressionSaved: `${compressionSaved}%`,
206 dimensions: {
207 width: jobData.metadata?.width || 0,
208 height: jobData.metadata?.height || 0
209 },
210 format: jobData.contentType?.replace('image/', '') || 'webp'
211 };
212 } else {
213
214 userFriendlyResult = {
215 success: false,
216 error: result.data?.error || jobData?.error || 'Image optimization failed'
217 };
218 }
219
220 await Actor.setValue('OUTPUT', userFriendlyResult);
221
222
223 if (userFriendlyResult.success) {
224 const dataset = await Dataset.open();
225 await dataset.pushData({
226 optimizedUrl: userFriendlyResult.optimizedUrl,
227 originalSize: userFriendlyResult.originalSize,
228 optimizedSize: userFriendlyResult.optimizedSize,
229 compressionSaved: userFriendlyResult.compressionSaved,
230 dimensions: userFriendlyResult.dimensions,
231 format: userFriendlyResult.format,
232 success: userFriendlyResult.success,
233 processedAt: new Date().toISOString()
234 });
235
236 console.log(`✅ Success! ${userFriendlyResult.compressionSaved} smaller (${userFriendlyResult.optimizedSize} bytes)`);
237 console.log(`🔗 Optimized image: ${userFriendlyResult.optimizedUrl}`);
238 } else {
239 console.log(`❌ Failed: ${userFriendlyResult.error}`);
240 }
241
242
243 } else if (action === 'status') {
244 if (!jobId) {
245 throw new Error('jobId is required for status action');
246 }
247
248 const vpsUrl = `${VPS_BASE_URL}${VPS_STATUS_ENDPOINT}/${jobId}`;
249
250 console.log(`🔍 Checking job status: ${jobId.substring(0, 8)}...`);
251
252 const response = await fetch(vpsUrl, {
253 method: 'GET',
254 headers: {
255 'Authorization': `Bearer ${VPS_API_KEY}`,
256 'User-Agent': 'Apify-Proxy/1.0'
257 },
258 timeout: REQUEST_TIMEOUT
259 });
260
261 const result = await response.json();
262
263 if (!response.ok) {
264 throw new Error(`VPS API error: ${result.error || 'Unknown error'}`);
265 }
266
267
268 const jobData = result.data?.result;
269 const isCompleted = result.data?.status === 'completed' && jobData?.success;
270
271 let userFriendlyResult;
272
273 if (isCompleted) {
274
275 const compressionSaved = jobData.originalSize && jobData.optimizedSize
276 ? Math.round(((jobData.originalSize - jobData.optimizedSize) / jobData.originalSize) * 100)
277 : 0;
278
279 userFriendlyResult = {
280 success: true,
281 optimizedUrl: jobData.uploadResult?.url || '',
282 originalSize: jobData.originalSize,
283 optimizedSize: jobData.optimizedSize,
284 compressionSaved: `${compressionSaved}%`,
285 dimensions: {
286 width: jobData.metadata?.width || 0,
287 height: jobData.metadata?.height || 0
288 },
289 format: jobData.contentType?.replace('image/', '') || 'webp'
290 };
291 } else {
292
293 userFriendlyResult = {
294 success: false,
295 error: jobData?.error || 'Image optimization failed'
296 };
297 }
298
299 await Actor.setValue('OUTPUT', userFriendlyResult);
300
301
302 if (userFriendlyResult.success) {
303 const dataset = await Dataset.open();
304 await dataset.pushData({
305 optimizedUrl: userFriendlyResult.optimizedUrl,
306 originalSize: userFriendlyResult.originalSize,
307 optimizedSize: userFriendlyResult.optimizedSize,
308 compressionSaved: userFriendlyResult.compressionSaved,
309 dimensions: userFriendlyResult.dimensions,
310 format: userFriendlyResult.format,
311 success: userFriendlyResult.success,
312 processedAt: new Date().toISOString()
313 });
314
315 console.log(`✅ Job completed! ${userFriendlyResult.compressionSaved} compression achieved`);
316 } else {
317 console.log(`⏳ Job still processing...`);
318 }
319
320 }
321 } else {
322
323 const port = process.env.PORT || 3000;
324 await new Promise((resolve) => {
325 app.listen(port, () => {
326 resolve();
327 });
328 });
329 }
330});