@clawchatsai/connector 0.0.37 → 0.0.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/server.js +107 -95
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -296,6 +296,26 @@ function getActiveDb() {
|
|
|
296
296
|
return getDb(getWorkspaces().active);
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
+
let _globalDb = null;
|
|
300
|
+
function getGlobalDb() {
|
|
301
|
+
if (_globalDb) return _globalDb;
|
|
302
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
303
|
+
const dbPath = path.join(DATA_DIR, 'global.db');
|
|
304
|
+
_globalDb = new Database(dbPath);
|
|
305
|
+
_globalDb.pragma('journal_mode = WAL');
|
|
306
|
+
_globalDb.exec(`
|
|
307
|
+
CREATE TABLE IF NOT EXISTS custom_emojis (
|
|
308
|
+
name TEXT NOT NULL,
|
|
309
|
+
pack TEXT NOT NULL DEFAULT 'slackmojis',
|
|
310
|
+
url TEXT NOT NULL,
|
|
311
|
+
mime_type TEXT,
|
|
312
|
+
created_at INTEGER DEFAULT (strftime('%s','now')),
|
|
313
|
+
PRIMARY KEY (name, pack)
|
|
314
|
+
)
|
|
315
|
+
`);
|
|
316
|
+
return _globalDb;
|
|
317
|
+
}
|
|
318
|
+
|
|
299
319
|
function closeDb(workspaceName) {
|
|
300
320
|
const db = dbCache.get(workspaceName);
|
|
301
321
|
if (db) {
|
|
@@ -309,6 +329,7 @@ function closeAllDbs() {
|
|
|
309
329
|
db.close();
|
|
310
330
|
}
|
|
311
331
|
dbCache.clear();
|
|
332
|
+
if (_globalDb) { _globalDb.close(); _globalDb = null; }
|
|
312
333
|
}
|
|
313
334
|
|
|
314
335
|
function migrate(db) {
|
|
@@ -1445,15 +1466,32 @@ function handleWorkspaceFileRead(req, res, query) {
|
|
|
1445
1466
|
return sendError(res, 404, 'File not found');
|
|
1446
1467
|
}
|
|
1447
1468
|
|
|
1448
|
-
// Limit file size to 1MB
|
|
1449
1469
|
const stat = fs.statSync(resolved);
|
|
1450
|
-
|
|
1451
|
-
|
|
1470
|
+
const ext = path.extname(resolved).toLowerCase().slice(1);
|
|
1471
|
+
|
|
1472
|
+
// MIME type map — binary types get served as binary, rest as text
|
|
1473
|
+
const mimeMap = {
|
|
1474
|
+
png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg',
|
|
1475
|
+
gif: 'image/gif', webp: 'image/webp', svg: 'image/svg+xml',
|
|
1476
|
+
bmp: 'image/bmp', ico: 'image/x-icon',
|
|
1477
|
+
pdf: 'application/pdf',
|
|
1478
|
+
mp3: 'audio/mpeg', mp4: 'video/mp4', wav: 'audio/wav',
|
|
1479
|
+
ogg: 'audio/ogg', webm: 'video/webm',
|
|
1480
|
+
};
|
|
1481
|
+
const mime = mimeMap[ext];
|
|
1482
|
+
const isBinary = !!mime;
|
|
1483
|
+
|
|
1484
|
+
if (isBinary) {
|
|
1485
|
+
if (stat.size > 20 * 1024 * 1024) return sendError(res, 413, 'File too large (max 20MB)');
|
|
1486
|
+
const content = fs.readFileSync(resolved);
|
|
1487
|
+
res.writeHead(200, { 'Content-Type': mime, 'Cache-Control': 'private, max-age=60' });
|
|
1488
|
+
res.end(content);
|
|
1489
|
+
} else {
|
|
1490
|
+
if (stat.size > 1024 * 1024) return sendError(res, 413, 'File too large (max 1MB)');
|
|
1491
|
+
const content = fs.readFileSync(resolved, 'utf8');
|
|
1492
|
+
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
1493
|
+
res.end(content);
|
|
1452
1494
|
}
|
|
1453
|
-
|
|
1454
|
-
const content = fs.readFileSync(resolved, 'utf8');
|
|
1455
|
-
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
1456
|
-
res.end(content);
|
|
1457
1495
|
}
|
|
1458
1496
|
|
|
1459
1497
|
async function handleWorkspaceFileWrite(req, res, query) {
|
|
@@ -2090,28 +2128,11 @@ async function handleRequest(req, res) {
|
|
|
2090
2128
|
|
|
2091
2129
|
// Custom emoji listing (no auth — public like static assets)
|
|
2092
2130
|
if (method === 'GET' && urlPath === '/api/emoji') {
|
|
2093
|
-
const emojiDir = path.join(__dirname, 'emoji');
|
|
2094
|
-
if (!fs.existsSync(emojiDir)) {
|
|
2095
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2096
|
-
return res.end('[]');
|
|
2097
|
-
}
|
|
2098
2131
|
try {
|
|
2099
|
-
const
|
|
2100
|
-
|
|
2101
|
-
);
|
|
2102
|
-
const result = [];
|
|
2103
|
-
for (const pack of packs) {
|
|
2104
|
-
const packDir = path.join(emojiDir, pack);
|
|
2105
|
-
const files = fs.readdirSync(packDir).filter(f =>
|
|
2106
|
-
/\.(png|gif|webp|jpg|jpeg)$/i.test(f)
|
|
2107
|
-
);
|
|
2108
|
-
for (const file of files) {
|
|
2109
|
-
const name = file.replace(/\.[^.]+$/, '');
|
|
2110
|
-
result.push({ name, pack, path: `/emoji/${pack}/${file}` });
|
|
2111
|
-
}
|
|
2112
|
-
}
|
|
2132
|
+
const db = getGlobalDb();
|
|
2133
|
+
const rows = db.prepare('SELECT name, pack, url, mime_type FROM custom_emojis ORDER BY created_at DESC').all();
|
|
2113
2134
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'public, max-age=300' });
|
|
2114
|
-
return res.end(JSON.stringify(
|
|
2135
|
+
return res.end(JSON.stringify(rows));
|
|
2115
2136
|
} catch (e) {
|
|
2116
2137
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
2117
2138
|
return res.end(JSON.stringify({ error: e.message }));
|
|
@@ -2161,7 +2182,7 @@ async function handleRequest(req, res) {
|
|
|
2161
2182
|
}
|
|
2162
2183
|
}
|
|
2163
2184
|
|
|
2164
|
-
//
|
|
2185
|
+
// Add custom emoji (store URL in global.db)
|
|
2165
2186
|
if (method === 'POST' && urlPath === '/api/emoji/add') {
|
|
2166
2187
|
if (!checkAuth(req, res)) return;
|
|
2167
2188
|
try {
|
|
@@ -2170,45 +2191,40 @@ async function handleRequest(req, res) {
|
|
|
2170
2191
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2171
2192
|
return res.end(JSON.stringify({ error: 'Missing url or name' }));
|
|
2172
2193
|
}
|
|
2173
|
-
const
|
|
2174
|
-
const
|
|
2175
|
-
|
|
2194
|
+
const safeName = name.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
2195
|
+
const targetPack = pack || 'slackmojis';
|
|
2196
|
+
// Determine mime type from URL extension
|
|
2197
|
+
const urlLower = url.split('?')[0].toLowerCase();
|
|
2198
|
+
let mimeType = 'image/png';
|
|
2199
|
+
if (urlLower.endsWith('.gif')) mimeType = 'image/gif';
|
|
2200
|
+
else if (urlLower.endsWith('.webp')) mimeType = 'image/webp';
|
|
2201
|
+
else if (urlLower.endsWith('.jpg') || urlLower.endsWith('.jpeg')) mimeType = 'image/jpeg';
|
|
2202
|
+
|
|
2203
|
+
const db = getGlobalDb();
|
|
2204
|
+
db.prepare('INSERT OR REPLACE INTO custom_emojis (name, pack, url, mime_type) VALUES (?, ?, ?, ?)')
|
|
2205
|
+
.run(safeName, targetPack, url, mimeType);
|
|
2176
2206
|
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
if (resp.statusCode >= 300 && resp.statusCode < 400 && resp.headers.location) {
|
|
2185
|
-
return resolve(download(resp.headers.location, redirects + 1));
|
|
2186
|
-
}
|
|
2187
|
-
if (resp.statusCode !== 200) return reject(new Error(`HTTP ${resp.statusCode}`));
|
|
2188
|
-
const chunks = [];
|
|
2189
|
-
resp.on('data', chunk => chunks.push(chunk));
|
|
2190
|
-
resp.on('end', () => resolve({ buffer: Buffer.concat(chunks), contentType: resp.headers['content-type'] || '' }));
|
|
2191
|
-
}).on('error', reject);
|
|
2192
|
-
});
|
|
2207
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2208
|
+
return res.end(JSON.stringify({ name: safeName, pack: targetPack, url, mime_type: mimeType }));
|
|
2209
|
+
} catch (e) {
|
|
2210
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
2211
|
+
return res.end(JSON.stringify({ error: e.message }));
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2193
2214
|
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
if (['png', 'gif', 'webp', 'jpg', 'jpeg'].includes(urlExt)) ext = urlExt;
|
|
2215
|
+
// Delete custom emoji
|
|
2216
|
+
if (method === 'DELETE' && urlPath === '/api/emoji') {
|
|
2217
|
+
if (!checkAuth(req, res)) return;
|
|
2218
|
+
try {
|
|
2219
|
+
const { name, pack } = await parseBody(req);
|
|
2220
|
+
if (!name || !pack) {
|
|
2221
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2222
|
+
return res.end(JSON.stringify({ error: 'Missing name or pack' }));
|
|
2203
2223
|
}
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
const filePath = path.join(emojiDir, `${safeName}.${ext}`);
|
|
2207
|
-
fs.writeFileSync(filePath, buffer);
|
|
2208
|
-
|
|
2209
|
-
const result = { name: safeName, pack: targetPack, path: `/emoji/${targetPack}/${safeName}.${ext}` };
|
|
2224
|
+
const db = getGlobalDb();
|
|
2225
|
+
db.prepare('DELETE FROM custom_emojis WHERE name = ? AND pack = ?').run(name, pack);
|
|
2210
2226
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2211
|
-
return res.end(JSON.stringify(
|
|
2227
|
+
return res.end(JSON.stringify({ ok: true }));
|
|
2212
2228
|
} catch (e) {
|
|
2213
2229
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
2214
2230
|
return res.end(JSON.stringify({ error: e.message }));
|
|
@@ -4285,17 +4301,11 @@ export function createApp(config = {}) {
|
|
|
4285
4301
|
|
|
4286
4302
|
// Custom emoji listing (no auth)
|
|
4287
4303
|
if (method === 'GET' && urlPath === '/api/emoji') {
|
|
4288
|
-
const emojiDir = path.join(__dirname, 'emoji');
|
|
4289
|
-
if (!fs.existsSync(emojiDir)) { res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end('[]'); }
|
|
4290
4304
|
try {
|
|
4291
|
-
const
|
|
4292
|
-
const
|
|
4293
|
-
for (const pack of packs) {
|
|
4294
|
-
const files = fs.readdirSync(path.join(emojiDir, pack)).filter(f => /\.(png|gif|webp|jpg|jpeg)$/i.test(f));
|
|
4295
|
-
for (const file of files) result.push({ name: file.replace(/\.[^.]+$/, ''), pack, path: `/emoji/${pack}/${file}` });
|
|
4296
|
-
}
|
|
4305
|
+
const db = getGlobalDb();
|
|
4306
|
+
const rows = db.prepare('SELECT name, pack, url, mime_type FROM custom_emojis ORDER BY created_at DESC').all();
|
|
4297
4307
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'public, max-age=300' });
|
|
4298
|
-
return res.end(JSON.stringify(
|
|
4308
|
+
return res.end(JSON.stringify(rows));
|
|
4299
4309
|
} catch (e) { res.writeHead(500, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: e.message })); }
|
|
4300
4310
|
}
|
|
4301
4311
|
|
|
@@ -4324,35 +4334,34 @@ export function createApp(config = {}) {
|
|
|
4324
4334
|
|
|
4325
4335
|
if (!_checkAuth(req, res)) return;
|
|
4326
4336
|
|
|
4327
|
-
//
|
|
4337
|
+
// Add custom emoji (auth required)
|
|
4328
4338
|
if (method === 'POST' && urlPath === '/api/emoji/add') {
|
|
4329
4339
|
try {
|
|
4330
4340
|
const { url, name, pack } = await parseBody(req);
|
|
4331
4341
|
if (!url || !name) { res.writeHead(400, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: 'Missing url or name' })); }
|
|
4332
|
-
const targetPack = pack || 'custom';
|
|
4333
|
-
const emojiDir = path.join(__dirname, 'emoji', targetPack);
|
|
4334
|
-
if (!fs.existsSync(emojiDir)) fs.mkdirSync(emojiDir, { recursive: true });
|
|
4335
|
-
const https = await import('https');
|
|
4336
|
-
const http = await import('http');
|
|
4337
|
-
const download = (dlUrl, redirects = 0) => new Promise((resolve, reject) => {
|
|
4338
|
-
if (redirects > 5) return reject(new Error('Too many redirects'));
|
|
4339
|
-
const mod = dlUrl.startsWith('https') ? https.default : http.default;
|
|
4340
|
-
mod.get(dlUrl, (resp) => {
|
|
4341
|
-
if (resp.statusCode >= 300 && resp.statusCode < 400 && resp.headers.location) return resolve(download(resp.headers.location, redirects + 1));
|
|
4342
|
-
if (resp.statusCode !== 200) return reject(new Error(`HTTP ${resp.statusCode}`));
|
|
4343
|
-
const chunks = []; resp.on('data', c => chunks.push(c)); resp.on('end', () => resolve({ buffer: Buffer.concat(chunks), contentType: resp.headers['content-type'] || '' }));
|
|
4344
|
-
}).on('error', reject);
|
|
4345
|
-
});
|
|
4346
|
-
const { buffer, contentType } = await download(url);
|
|
4347
|
-
let ext = 'png';
|
|
4348
|
-
if (contentType.includes('gif')) ext = 'gif';
|
|
4349
|
-
else if (contentType.includes('webp')) ext = 'webp';
|
|
4350
|
-
else if (contentType.includes('jpeg') || contentType.includes('jpg')) ext = 'jpg';
|
|
4351
|
-
else { const u = url.split('?')[0].split('.').pop().toLowerCase(); if (['png','gif','webp','jpg','jpeg'].includes(u)) ext = u; }
|
|
4352
4342
|
const safeName = name.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
4353
|
-
|
|
4343
|
+
const targetPack = pack || 'slackmojis';
|
|
4344
|
+
const urlLower = url.split('?')[0].toLowerCase();
|
|
4345
|
+
let mimeType = 'image/png';
|
|
4346
|
+
if (urlLower.endsWith('.gif')) mimeType = 'image/gif';
|
|
4347
|
+
else if (urlLower.endsWith('.webp')) mimeType = 'image/webp';
|
|
4348
|
+
else if (urlLower.endsWith('.jpg') || urlLower.endsWith('.jpeg')) mimeType = 'image/jpeg';
|
|
4349
|
+
const db = getGlobalDb();
|
|
4350
|
+
db.prepare('INSERT OR REPLACE INTO custom_emojis (name, pack, url, mime_type) VALUES (?, ?, ?, ?)').run(safeName, targetPack, url, mimeType);
|
|
4351
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4352
|
+
return res.end(JSON.stringify({ name: safeName, pack: targetPack, url, mime_type: mimeType }));
|
|
4353
|
+
} catch (e) { res.writeHead(500, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: e.message })); }
|
|
4354
|
+
}
|
|
4355
|
+
|
|
4356
|
+
// Delete custom emoji (auth required)
|
|
4357
|
+
if (method === 'DELETE' && urlPath === '/api/emoji') {
|
|
4358
|
+
try {
|
|
4359
|
+
const { name, pack } = await parseBody(req);
|
|
4360
|
+
if (!name || !pack) { res.writeHead(400, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: 'Missing name or pack' })); }
|
|
4361
|
+
const db = getGlobalDb();
|
|
4362
|
+
db.prepare('DELETE FROM custom_emojis WHERE name = ? AND pack = ?').run(name, pack);
|
|
4354
4363
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4355
|
-
return res.end(JSON.stringify({
|
|
4364
|
+
return res.end(JSON.stringify({ ok: true }));
|
|
4356
4365
|
} catch (e) { res.writeHead(500, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: e.message })); }
|
|
4357
4366
|
}
|
|
4358
4367
|
|
|
@@ -4503,6 +4512,9 @@ if (isDirectRun) {
|
|
|
4503
4512
|
|
|
4504
4513
|
// Connect to gateway
|
|
4505
4514
|
app.gatewayClient.connect();
|
|
4515
|
+
|
|
4516
|
+
// Initialize global DB (custom emojis, etc.)
|
|
4517
|
+
getGlobalDb();
|
|
4506
4518
|
});
|
|
4507
4519
|
|
|
4508
4520
|
// Graceful shutdown
|