Gpt 5.6 Luna
const axios = require('axios');
const fetch = require('node-fetch');
const fs = require('fs');
// --- Konfigurasi ---
const API_BASE = 'https://tutorgpt.io';
const CHAT_ENDPOINT = '/api/v1/chat/stream';
// Strategi Accept header yang akan dicoba berurutan
const ACCEPT_STRATEGIES = [
'application/json, text/event-stream', // ← Strategi utama (paling mungkin berhasil)
'text/event-stream, application/json',
'text/event-stream',
'*/*',
];
// --- Helper: Generate UUID v4 ---
function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
// --- Header builder ---
function getHeaders(acceptHeader = ACCEPT_STRATEGIES[0]) {
return {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36',
'Accept': acceptHeader,
'Accept-Language': 'en-US,en;q=0.9,id;q=0.8',
'Content-Type': 'application/json',
'Origin': API_BASE,
'Referer': API_BASE + '/',
'Sec-Ch-Ua': '"Chromium";v="152", "Not?A_Brand";v="24", "Google Chrome";v="152"',
'Sec-Ch-Ua-Mobile': '?0',
'Sec-Ch-Ua-Platform': '"Windows"',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
};
}
// --- Parser SSE ---
function createSSEParser(onEvent) {
let buffer = '';
return (chunk) => {
buffer += chunk.toString('utf8');
const events = buffer.split('\n\n');
buffer = events.pop() || '';
for (const eventBlock of events) {
if (!eventBlock.trim()) continue;
const lines = eventBlock.split('\n');
for (const line of lines) {
if (!line.startsWith('data:')) continue;
const dataStr = line.slice(5).trim();
if (!dataStr) continue;
try {
const parsed = JSON.parse(dataStr);
onEvent(parsed);
} catch (e) {
// Skip invalid JSON
}
}
}
};
}
// ============================================================
// CORE: Proses stream (dipakai semua engine)
// ============================================================
function processStream(stream, { conversationId, showReasoning, onChunk }) {
return new Promise((resolve, reject) => {
let fullText = '';
let fullReasoning = '';
const chunks = [];
let isFirstText = true;
if (showReasoning) {
process.stdout.write('\n[Reasoning]\n');
}
const handleEvent = (parsed) => {
chunks.push(parsed);
if (parsed.reasoning) {
fullReasoning += parsed.reasoning;
if (showReasoning) {
process.stdout.write(parsed.reasoning);
}
}
if (parsed.text) {
if (isFirstText) {
process.stdout.write('\n[Answer]\n');
isFirstText = false;
}
fullText += parsed.text;
process.stdout.write(parsed.text);
}
if (onChunk) {
try {
onChunk(parsed.text || '', parsed.reasoning || '', parsed);
} catch (e) { /* ignore */ }
}
if (parsed.done) {
process.stdout.write('\n\n[+] Stream selesai.\n');
resolve({
success: true,
conversationId,
answer: fullText,
reasoning: fullReasoning,
totalChunks: chunks.length,
rawChunks: chunks,
});
}
};
const parser = createSSEParser(handleEvent);
stream.on('data', parser);
stream.on('end', () => {
if (!fullText && !fullReasoning) {
resolve({
success: false,
conversationId,
answer: '',
reasoning: '',
totalChunks: chunks.length,
rawChunks: chunks,
error: 'Stream berakhir tanpa respons.',
});
} else {
resolve({
success: true,
conversationId,
answer: fullText,
reasoning: fullReasoning,
totalChunks: chunks.length,
rawChunks: chunks,
});
}
});
stream.on('error', (err) => reject(err));
});
}
// ============================================================
// ENGINE 1: Axios
// ============================================================
async function chatStreamAxios(message, options = {}) {
const {
language = 'auto',
model = 'deepseek-v4-flash',
tone = 'default',
length = 'moderate',
conversationId = generateUUID(),
imageUrls = [],
chatMode = 'standard',
showReasoning = false,
onChunk = null,
acceptHeader = ACCEPT_STRATEGIES[0],
} = options;
const payload = {
message,
language,
model,
tone,
length,
conversation_id: conversationId,
image_urls: imageUrls,
chat_mode: chatMode,
};
console.log(`[*] Mengirim pesan (axios, Accept: "${acceptHeader}")...`);
const response = await axios.post(
API_BASE + CHAT_ENDPOINT,
payload,
{
responseType: 'stream',
timeout: 180000,
headers: getHeaders(acceptHeader),
decompress: true,
validateStatus: (status) => status < 500, // Biarkan 4xx lewat agar bisa fallback
}
);
if (response.status >= 400) {
const err = new Error(`HTTP ${response.status}`);
err.status = response.status;
err.isAxios = true;
throw err;
}
return processStream(response.data, {
conversationId,
showReasoning,
onChunk,
});
}
// ============================================================
// ENGINE 2: node-fetch
// ============================================================
async function chatStreamFetch(message, options = {}) {
const {
language = 'auto',
model = 'deepseek-v4-flash',
tone = 'default',
length = 'moderate',
conversationId = generateUUID(),
imageUrls = [],
chatMode = 'standard',
showReasoning = false,
onChunk = null,
acceptHeader = ACCEPT_STRATEGIES[0],
} = options;
const payload = {
message,
language,
model,
tone,
length,
conversation_id: conversationId,
image_urls: imageUrls,
chat_mode: chatMode,
};
console.log(`[*] Mengirim pesan (node-fetch, Accept: "${acceptHeader}")...`);
const response = await fetch(API_BASE + CHAT_ENDPOINT, {
method: 'POST',
headers: getHeaders(acceptHeader),
body: JSON.stringify(payload),
});
if (!response.ok) {
const bodyText = await response.text();
const err = new Error(`HTTP ${response.status} ${response.statusText}: ${bodyText.substring(0, 300)}`);
err.status = response.status;
err.isFetch = true;
throw err;
}
return processStream(response.body, {
conversationId,
showReasoning,
onChunk,
});
}
// ============================================================
// SMART WRAPPER: Coba semua strategi Accept + engine
// ============================================================
async function chatStream(message, options = {}) {
const conversationId = options.conversationId || generateUUID();
// Daftar engine & Accept header yang akan dicoba
const attempts = [];
for (const accept of ACCEPT_STRATEGIES) {
attempts.push({ engine: 'axios', accept });
}
for (const accept of ACCEPT_STRATEGIES) {
attempts.push({ engine: 'fetch', accept });
}
let lastError = null;
for (let i = 0; i < attempts.length; i++) {
const { engine, accept } = attempts[i];
// Skip jika user sudah set engine spesifik
if (options.forceEngine && options.forceEngine !== engine) continue;
if (i > 0) {
console.log(`\n[*] Percobaan #${i + 1}: ${engine} + Accept="${accept}"`);
} else {
console.log(`[*] Percobaan #1: ${engine} + Accept="${accept}"`);
}
try {
const fn = engine === 'axios' ? chatStreamAxios : chatStreamFetch;
const result = await fn(message, {
...options,
conversationId,
acceptHeader: accept,
});
console.log(`\n[+] BERHASIL dengan engine=${engine}, Accept="${accept}"`);
return result;
} catch (error) {
lastError = error;
// Hanya coba fallback jika error 406 (Accept header)
if (error.status === 406 || (error.message && error.message.includes('406'))) {
console.log(`[!] Gagal (406), coba strategi berikutnya...`);
continue;
}
// Kalau bukan 406, langsung throw (error lain)
throw error;
}
}
// Semua strategi gagal
throw lastError || new Error('Semua strategi gagal.');
}
// ============================================================
// WRAPPER: chat (non-streaming)
// ============================================================
async function chat(message, options = {}) {
return chatStream(message, { ...options, showReasoning: false });
}
// ============================================================
// SAVE CHAT
// ============================================================
function saveChat(result, filename = null) {
if (!filename) filename = `chat-${Date.now()}.txt`;
const lines = [
'=============================================',
' TUTORGPT CHAT LOG ',
'=============================================',
`Conversation ID : ${result.conversationId}`,
`Chunks : ${result.totalChunks}`,
'',
'----- REASONING -----',
result.reasoning || '(tidak ada)',
'',
'----- ANSWER -----',
result.answer,
'',
'=============================================',
];
fs.writeFileSync(filename, lines.join('\n'), 'utf8');
console.log(`[+] Disimpan: ${filename}`);
return filename;
}
// ============================================================
// CLI
// ============================================================
async function main() {
const args = process.argv.slice(2);
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
console.log('=============================================');
console.log(' TUTORGPT AI CHAT SCRAPER ');
console.log('=============================================');
console.log('');
console.log('Penggunaan:');
console.log(' node tutorgpt.js "pesan anda" [opsi]');
console.log('');
console.log('Opsi:');
console.log(' --reasoning Tampilkan chain-of-thought AI');
console.log(' --save Simpan hasil ke file .txt');
console.log(' --model=NAMA Ganti model (default: deepseek-v4-flash)');
console.log(' --tone=NAMA Ganti tone');
console.log(' --length=N short/moderate/long');
console.log(' --conv=UUID Lanjutkan percakapan');
console.log(' --engine=axios Paksa pakai axios saja');
console.log(' --engine=fetch Paksa pakai node-fetch saja');
console.log('');
console.log('Contoh:');
console.log(' node tutorgpt.js "hai"');
console.log(' node tutorgpt.js "jelaskan hukum newton" --reasoning');
console.log(' node tutorgpt.js "buat puisi" --save');
process.exit(0);
}
const message = args[0];
const showReasoning = args.includes('--reasoning');
const shouldSave = args.includes('--save');
const getArg = (name, def) => {
const arg = args.find(a => a.startsWith(`--${name}=`));
return arg ? arg.split('=')[1] : def;
};
const model = getArg('model', 'gpt-5.6-luna');
const tone = getArg('tone', 'default');
const length = getArg('length', 'moderate');
const conversationId = getArg('conv', null);
const forceEngine = getArg('engine', null);
console.log('=============================================');
console.log(' TUTORGPT AI CHAT SCRAPER ');
console.log('=============================================');
console.log(`Message : "${message}"`);
console.log(`Model : ${model}`);
console.log(`Conv ID : ${conversationId || '(auto)'}`);
console.log('');
try {
const result = await chatStream(message, {
model,
tone,
length,
showReasoning,
conversationId: conversationId || generateUUID(),
forceEngine,
});
if (shouldSave) {
console.log('');
saveChat(result);
}
console.log('\n=============================================');
console.log(' SELESAI ');
console.log('=============================================');
console.log(`Total chunks : ${result.totalChunks}`);
console.log(`Panjang jawaban : ${result.answer.length} chars`);
if (result.reasoning) {
console.log(`Panjang reasoning : ${result.reasoning.length} chars`);
}
console.log(`Conversation ID : ${result.conversationId}`);
console.log('');
console.log(` Lanjutkan: --conv=${result.conversationId}`);
} catch (error) {
console.error('\n[!] Error:', error.message);
if (error.response) {
console.error(' HTTP Status:', error.response.status);
}
process.exit(1);
}
}
// ============================================================
// EXPORTS
// ============================================================
module.exports = {
chat,
chatStream,
chatStreamAxios,
chatStreamFetch,
saveChat,
generateUUID,
ACCEPT_STRATEGIES,
};
if (require.main === module) {
main().catch(err => {
console.error('[!] Fatal error:', err);
process.exit(1);
});
}
Discussion
Thread
Belum ada komentarJadilah yang pertama membuka diskusi.