/***
@ Base: https://play.google.com/store/apps/details?id=ai.elin.app.android
@ Author: Shannz
@ Note: Elin AI support follow up message and image analyze.
***/
import axios from 'axios';
import qs from 'qs';
import FormData from 'form-data';
import fs from 'fs';
const CONFIG = {
BASE_URL: "https://api.elin.ai",
CREDENTIALS: {
username: '', //email lu
password: '' //pw lu
},
API: {
TOKEN: "/api/v1/auth/token",
SESSION: "/api/v3/chats/sessions/id",
UPLOAD: "/api/v3/content/upload",
CHAT: (id) => `/api/v3/chats/${id}/stream`
},
HEADERS: {
'User-Agent': 'Elin AI/2.8.0 (ai.elin.app.android; build:220; Android 15; Model:25028RN03A)',
'Accept': 'application/json',
'Accept-Encoding': 'gzip',
'x-device-id': '540e28659b22cf05',
'accept-language': 'en',
'x-timezone': 'Asia/Jakarta',
'accept-charset': 'UTF-8'
}
};
const parseStreamResponse = (dataString) => {
if (typeof dataString === 'object') return JSON.stringify(dataString);
const lines = dataString.split('\n');
let finalContent = "";
for (const line of lines) {
if (line.startsWith('data: ')) {
try {
const jsonStr = line.replace('data: ', '').trim();
if (jsonStr === '[DONE]') break;
const parsed = JSON.parse(jsonStr);
if (parsed.content && parsed.content.type === 'markdown') {
finalContent = parsed.content.content;
}
} catch (e) {}
}
}
return finalContent;
};
export const elin = {
chat: async (prompt, options = {}) => {
try {
let { chatId, imagePath } = options;
const loginData = qs.stringify(CONFIG.CREDENTIALS);
const loginRes = await axios.post(CONFIG.BASE_URL + CONFIG.API.TOKEN, loginData, {
headers: {
...CONFIG.HEADERS,
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
});
const token = loginRes.data?.access_token;
if (!token) throw new Error("Gagal login: Token null");
const authHeaders = {
...CONFIG.HEADERS,
'authorization': `Bearer ${token}`
};
if (!chatId) {
const sessionRes = await axios.get(CONFIG.BASE_URL + CONFIG.API.SESSION, {
headers: authHeaders
});
chatId = sessionRes.data?.id;
if (!chatId) throw new Error("Gagal create session ID baru");
} else { }
let messageContent;
if (imagePath) {
if (!fs.existsSync(imagePath)) throw new Error(`File tidak ditemukan: ${imagePath}`);
const form = new FormData();
form.append('file', fs.createReadStream(imagePath));
const uploadRes = await axios.post(CONFIG.BASE_URL + CONFIG.API.UPLOAD, form, {
headers: { ...authHeaders, ...form.getHeaders() }
});
if (!uploadRes.data?.filename) throw new Error("Gagal upload gambar.");
messageContent = {
type: "path",
content: uploadRes.data.filename,
context: prompt,
resource_type: "image"
};
} else {
messageContent = {
type: "text",
content: prompt
};
}
const payload = {
id: chatId,
messages: [{
type: "user",
order: 1,
timestamp: new Date().toISOString(),
context: {},
content: messageContent
}],
type: "generic",
overrides: { retry: false }
};
const chatRes = await axios.post(CONFIG.BASE_URL + CONFIG.API.CHAT(chatId), payload, {
headers: {
...authHeaders,
'Content-Type': 'application/json'
},
responseType: 'stream'
});
return new Promise((resolve, reject) => {
let fullData = '';
chatRes.data.on('data', (chunk) => {
fullData += chunk.toString();
});
chatRes.data.on('end', () => {
const resultText = parseStreamResponse(fullData);
resolve({
status: true,
chatId: chatId,
result: resultText || "Tidak ada respon teks."
});
});
chatRes.data.on('error', (err) => reject(err));
});
} catch (error) {
let errMsg = error.message;
if (error.response) {
if (error.response.data && !error.response.data.read) {
errMsg = `Server Error (${error.response.status}): ${JSON.stringify(error.response.data)}`;
} else {
errMsg = `Server Error (${error.response.status})`;
}
}
return { status: false, error: errMsg };
}
}
};