112 lines
4.9 KiB
JavaScript
112 lines
4.9 KiB
JavaScript
const http = require('http');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const ADMIN_KEY = process.env.GREENLENS_ADMIN_KEY || 'greenlens-admin-2024';
|
|
const PORT = 5173;
|
|
const IMAGES = process.argv.slice(2);
|
|
if (!IMAGES.length) {
|
|
console.error('Provide image paths');
|
|
process.exit(1);
|
|
}
|
|
|
|
(async () => {
|
|
const upstream = await import('axios');
|
|
const axios = upstream.default || upstream;
|
|
|
|
const server = http.createServer(async (req, res) => {
|
|
if (req.url === '/api/tiktok/upload/photo' && req.method === 'POST') {
|
|
const chunks = [];
|
|
req.on('data', chunk => chunks.push(chunk));
|
|
req.on('end', async () => {
|
|
const raw = Buffer.concat(chunks).toString();
|
|
const contentType = req.headers['content-type'] || '';
|
|
let payload;
|
|
if (contentType.includes('multipart/form-data')) {
|
|
const busboy = (await import('@fastify/busboy')).default || (await import('busboy')).default;
|
|
} else {
|
|
try { payload = JSON.parse(raw); } catch {
|
|
res.writeHead(400, {'content-type':'application/json'});
|
|
return res.end(JSON.stringify({'code':'BAD_REQUEST','message':'Invalid JSON'}));
|
|
}
|
|
}
|
|
|
|
let files = [];
|
|
if (Array.isArray(payload?.files)) {
|
|
files = payload.files || [];
|
|
} else if (Array.isArray(payload?.photos)) {
|
|
files = payload.photos.map((photo) => {
|
|
if (Buffer.isBuffer(photo?.buffer)) {
|
|
return { buffer: photo.buffer, contentType: photo.mimeType || photo.contentType || 'image/jpeg' };
|
|
}
|
|
if (typeof photo?.url === 'string' && /^https?:\/\//i.test(photo.url)) {
|
|
return { buffer: Buffer.from(photo.url), contentType: photo.mimeType || photo.contentType || 'image/jpeg', url: photo.url };
|
|
}
|
|
return null;
|
|
}).filter(Boolean);
|
|
}
|
|
|
|
try {
|
|
const { uploadImage } = require('./lib/storage');
|
|
const fileInfos = [];
|
|
for (const file of files) {
|
|
const buffer = file.buffer || file.data;
|
|
const mimeType = file.contentType || file.mimeType || 'image/jpeg';
|
|
if (!Buffer.isBuffer(buffer)) {
|
|
res.writeHead(400, {'content-type':'application/json'});
|
|
return res.end(JSON.stringify({'code':'BAD_REQUEST','message':'Each file must include a binary buffer.'}));
|
|
}
|
|
const uploadRes = await uploadImage(buffer.toString('base64'), mimeType);
|
|
fileInfos.push({ url: uploadRes.url, mime_type: mimeType });
|
|
}
|
|
console.log('Uploaded URLs:', fileInfos.map(f => f.url));
|
|
|
|
const { getLiveTiktokAccessToken } = require('./lib/tiktok');
|
|
async function tiktokApi(url, options = {}) {
|
|
const tokens = await getLiveTiktokAccessToken();
|
|
const r = await axios({ url, ...options, headers: { ...(options.headers || {}), Authorization: 'Bearer ' + tokens.access_token, 'Content-Type': 'application/json; charset=UTF-8' } });
|
|
if (r.status >= 400) throw new Error('TikTok API error: ' + r.status + ' ' + JSON.stringify(r.data));
|
|
return r.data;
|
|
}
|
|
|
|
const initBody = {
|
|
media_type: 'PHOTO',
|
|
photo_cover_index: 0,
|
|
file_paths: fileInfos.map(info => info.url),
|
|
file_extensions: fileInfos.map(info => (info.mime_type || 'image/jpeg').split('/').pop()),
|
|
post_mode: 'DRAFT',
|
|
};
|
|
|
|
const initResult = await tiktokApi('https://open.tiktokapis.com/v2/post/publish/content/init/', { method: 'POST', data: initBody });
|
|
console.log('TikTok init:', JSON.stringify(initResult));
|
|
res.writeHead(200, {'content-type':'application/json'});
|
|
res.end(JSON.stringify({ publish_id: initResult?.data?.publish_id, status: initResult?.data?.status || 'INITIATED' }));
|
|
} catch (error) {
|
|
console.error('Handler error:', error);
|
|
res.writeHead(error.status || 500, {'content-type':'application/json'});
|
|
res.end(JSON.stringify({ code: error.code || 'SERVER_ERROR', message: error.message }));
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
res.writeHead(404, {'content-type':'application/json'});
|
|
res.end(JSON.stringify({ error: 'Not found' }));
|
|
});
|
|
|
|
server.listen(PORT, '127.0.0.1', async () => {
|
|
try {
|
|
const form = new FormData();
|
|
for (const file of IMAGES) {
|
|
form.append('photos', fs.createReadStream(file), { filename: path.basename(file), contentType: 'image/png' });
|
|
}
|
|
const { default: axios } = await import('axios');
|
|
const resp = await axios.post('http://127.0.0.1:' + PORT + '/api/tiktok/upload/photo', form, { headers: { ...form.getHeaders(), 'x-admin-key': ADMIN_KEY }, maxBodyLength: Infinity, maxContentLength: Infinity });
|
|
console.log('UPLOAD_RESPONSE', JSON.stringify(resp.data));
|
|
} finally {
|
|
server.close();
|
|
process.exit(0);
|
|
}
|
|
});
|
|
})();
|