Generate Text-to-Image
No comments yet. Be the first to comment!
1/**
2 * Generate text-to-image
3 * Author : Nasirxml
4 * Base : https://freegen.app
5 * Note: untuk img-to-img nya ga dibikin karna hasil nya jelek🗿
6 */
7
8
9const WebSocket = require('ws');
10const crypto = require('crypto');
11const https = require('https');
12const http = require('http');
13
14const SIGNER_URL = 'https://prompt-signer.freegen.app';
15const GENERATOR_URL = 'https://image-generator.freegen.app';
16const WEBSOCKET_URL = 'wss://websocket-bridge.freegen.app/ws';
17const STATS_API_URL = 'https://stats.freegen.app';
18
19// Aspect ratios available
20const RATIOS = {
21 '1:1': { width: 1024, height: 1024 },
22 '4:3': { width: 1024, height: 768 },
23 '3:4': { width: 768, height: 1024 },
24 '16:9': { width: 1024, height: 576 },
25 '9:16': { width: 576, height: 1024 }
26};
27
28class FreeGenAPI {
29 constructor() {
30 this.prompt = '';
31 this.ratio = '4:3';
32 }
33
34 async signer(prompt) {
35 return new Promise((resolve, reject) => {
36 const data = JSON.stringify({ prompt });
37
38 const req = https.request(`${SIGNER_URL}`, {
39 method: 'POST',
40 headers: {
41 'Content-Type': 'application/json',
42 'Content-Length': Buffer.byteLength(data)
43 }
44 }, (res) => {
45 let body = '';
46 res.on('data', chunk => body += chunk);
47 res.on('end', () => {
48 try {
49 const json = JSON.parse(body);
50 resolve(json);
51 } catch (e) {
52 reject(new Error('Failed to parse signer response'));
53 }
54 });
55 });
56
57 req.on('error', reject);
58 req.write(data);
59 req.end();
60 });
61 }
62
63 async generator(prompt, ts, sig, ratioId) {
64 return new Promise((resolve, reject) => {
65 const body = JSON.stringify({
66 prompt,
67 ts,
68 sig,
69 ratio_id: ratioId
70 });
71
72 const req = https.request(`${GENERATOR_URL}`, {
73 method: 'POST',
74 headers: {
75 'Content-Type': 'application/json',
76 'Content-Length': Buffer.byteLength(body)
77 }
78 }, (res) => {
79 let data = '';
80 res.on('data', chunk => data += chunk);
81 res.on('end', () => {
82 try {
83 const json = JSON.parse(data);
84 resolve(json);
85 } catch (e) {
86 reject(new Error('Failed to parse generator response'));
87 }
88 });
89 });
90
91 req.on('error', reject);
92 req.write(body);
93 req.end();
94 });
95 }
96
97 createAuth(jobId, timestamp) {
98 const message = jobId + timestamp.toString();
99 const hash = crypto.createHash('sha256').update(message).digest();
100 const auth = hash.toString('base64').substring(0, 20) + ':' + timestamp;
101 return auth;
102 }
103
104 async waitForResult(jobId, auth) {
105 return new Promise((resolve, reject) => {
106 const ws = new WebSocket(WEBSOCKET_URL);
107
108 let timeout = setTimeout(() => {
109 ws.close();
110 reject(new Error('Timeout waiting for result'));
111 }, 120000);
112
113 ws.on('open', () => {
114 console.log('[WS] Connected to WebSocket');
115
116 // Subscribe to job
117 ws.send(JSON.stringify({
118 type: 'subscribe',
119 job_id: jobId,
120 auth: auth
121 }));
122 console.log('[WS] Subscribed to job:', jobId);
123 });
124
125 ws.on('message', (data) => {
126 try {
127 const msg = JSON.parse(data.toString());
128
129 if (msg.type === 'status') {
130 console.log('[WS] Status:', msg.message);
131 } else if (msg.type === 'result') {
132 console.log('[WS] Image generated!');
133 clearTimeout(timeout);
134 ws.close();
135 resolve(msg.image_data);
136 } else if (msg.type === 'error') {
137 clearTimeout(timeout);
138 ws.close();
139 reject(new Error(msg.message || 'Unknown error'));
140 }
141 } catch (e) {
142 console.error('[WS] Failed to parse message:', e);
143 }
144 });
145
146 ws.on('error', (err) => {
147 clearTimeout(timeout);
148 reject(err);
149 });
150
151 ws.on('close', () => {
152 console.log('[WS] Connection closed');
153 });
154 });
155 }
156
157 async recordCompletion(jobId, totalTimeMs) {
158 return new Promise((resolve) => {
159 const body = JSON.stringify({
160 job_id: jobId,
161 total_time_ms: totalTimeMs,
162 timestamp: new Date().toISOString()
163 });
164
165 const req = https.request(`${STATS_API_URL}/record-completion`, {
166 method: 'POST',
167 headers: {
168 'Content-Type': 'application/json',
169 'Content-Length': Buffer.byteLength(body)
170 }
171 }, (res) => {
172 resolve();
173 });
174
175 req.on('error', () => resolve()); // Ignore errors
176 req.write(body);
177 req.end();
178 });
179 }
180
181 async generate(prompt, ratio = '4:3') {
182 console.log('\n=== FreeGen.app API ===');
183 console.log('Prompt:', prompt);
184 console.log('Ratio:', ratio);
185 console.log('');
186
187 const startTime = Date.now();
188
189 try {
190 // Step 1: Get signature from signer
191 console.log('[1/4] Getting signature from signer...');
192 const { ts, sig } = await this.signer(prompt);
193 console.log('[OK] Signature received:', sig.substring(0, 20) + '...');
194
195 // Step 2: Request image generation
196 console.log('\n[2/4] Requesting image generation...');
197 const jobData = await this.generator(prompt, ts, sig, ratio);
198 console.log('[OK] Job created:', jobData.job_id);
199
200 if (jobData.status) {
201 console.log('[OK] Status:', jobData.status);
202 }
203
204 // Step 3: Wait for result via WebSocket
205 console.log('\n[3/4] Connecting to WebSocket for real-time updates...');
206 const auth = this.createAuth(jobData.job_id, ts);
207 const imageUrl = await this.waitForResult(jobData.job_id, auth);
208
209 // Step 4: Record completion timing
210 const totalTime = Date.now() - startTime;
211 console.log('\n[4/4] Recording completion timing...');
212 await this.recordCompletion(jobData.job_id, totalTime);
213
214 console.log('\n' + '='.repeat(50));
215 console.log('IMAGE GENERATED!');
216 console.log('='.repeat(50));
217 console.log('URL:', imageUrl);
218 console.log('Time:', (totalTime / 1000).toFixed(2) + 's');
219 console.log('='.repeat(50) + '\n');
220
221 return { success: true, imageUrl, jobId: jobData.job_id, time: totalTime };
222
223 } catch (error) {
224 console.error('\n[ERROR]', error.message);
225 return { success: false, error: error.message };
226 }
227 }
228}
229
230// Main execution
231async function main() {
232 const args = process.argv.slice(2);
233
234 if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
235 console.log(`
236FreeGen.app API - AI Image Generator
237
238Usage:
239 node freegen.js "your prompt" [options]
240
241Options:
242 --ratio <ratio> Aspect ratio (1:1, 4:3, 3:4, 16:9, 9:16)
243 --help, -h Show this help message
244
245Examples:
246 node freegen.js "a cute cat"
247 node freegen.js "sunset over ocean" --ratio 16:9
248 node freegen.js "cyberpunk city" --ratio 3:4
249`);
250 process.exit(0);
251 }
252
253 let prompt = '';
254 let ratio = '4:3';
255
256 // Parse arguments
257 for (let i = 0; i < args.length; i++) {
258 if (args[i] === '--ratio' && args[i + 1]) {
259 ratio = args[i + 1];
260 i++;
261 } else if (!args[i].startsWith('--')) {
262 prompt += (prompt ? ' ' : '') + args[i];
263 }
264 }
265
266 if (!prompt) {
267 console.error('Error: Please provide a prompt');
268 process.exit(1);
269 }
270
271 if (!RATIOS[ratio]) {
272 console.error('Error: Invalid ratio. Available: 1:1, 4:3, 3:4, 16:9, 9:16');
273 process.exit(1);
274 }
275
276 const api = new FreeGenAPI();
277 const result = await api.generate(prompt, ratio);
278
279 if (!result.success) {
280 process.exit(1);
281 }
282}
283
284main().catch(console.error);
285
286module.exports = FreeGenAPI;