@clawchatsai/connector 0.0.37 → 0.0.38
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 +83 -88
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) {
|
|
@@ -2090,28 +2111,11 @@ async function handleRequest(req, res) {
|
|
|
2090
2111
|
|
|
2091
2112
|
// Custom emoji listing (no auth — public like static assets)
|
|
2092
2113
|
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
2114
|
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
|
-
}
|
|
2115
|
+
const db = getGlobalDb();
|
|
2116
|
+
const rows = db.prepare('SELECT name, pack, url, mime_type FROM custom_emojis ORDER BY created_at DESC').all();
|
|
2113
2117
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'public, max-age=300' });
|
|
2114
|
-
return res.end(JSON.stringify(
|
|
2118
|
+
return res.end(JSON.stringify(rows));
|
|
2115
2119
|
} catch (e) {
|
|
2116
2120
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
2117
2121
|
return res.end(JSON.stringify({ error: e.message }));
|
|
@@ -2161,7 +2165,7 @@ async function handleRequest(req, res) {
|
|
|
2161
2165
|
}
|
|
2162
2166
|
}
|
|
2163
2167
|
|
|
2164
|
-
//
|
|
2168
|
+
// Add custom emoji (store URL in global.db)
|
|
2165
2169
|
if (method === 'POST' && urlPath === '/api/emoji/add') {
|
|
2166
2170
|
if (!checkAuth(req, res)) return;
|
|
2167
2171
|
try {
|
|
@@ -2170,45 +2174,40 @@ async function handleRequest(req, res) {
|
|
|
2170
2174
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2171
2175
|
return res.end(JSON.stringify({ error: 'Missing url or name' }));
|
|
2172
2176
|
}
|
|
2173
|
-
const
|
|
2174
|
-
const
|
|
2175
|
-
|
|
2177
|
+
const safeName = name.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
2178
|
+
const targetPack = pack || 'slackmojis';
|
|
2179
|
+
// Determine mime type from URL extension
|
|
2180
|
+
const urlLower = url.split('?')[0].toLowerCase();
|
|
2181
|
+
let mimeType = 'image/png';
|
|
2182
|
+
if (urlLower.endsWith('.gif')) mimeType = 'image/gif';
|
|
2183
|
+
else if (urlLower.endsWith('.webp')) mimeType = 'image/webp';
|
|
2184
|
+
else if (urlLower.endsWith('.jpg') || urlLower.endsWith('.jpeg')) mimeType = 'image/jpeg';
|
|
2185
|
+
|
|
2186
|
+
const db = getGlobalDb();
|
|
2187
|
+
db.prepare('INSERT OR REPLACE INTO custom_emojis (name, pack, url, mime_type) VALUES (?, ?, ?, ?)')
|
|
2188
|
+
.run(safeName, targetPack, url, mimeType);
|
|
2176
2189
|
|
|
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
|
-
});
|
|
2190
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2191
|
+
return res.end(JSON.stringify({ name: safeName, pack: targetPack, url, mime_type: mimeType }));
|
|
2192
|
+
} catch (e) {
|
|
2193
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
2194
|
+
return res.end(JSON.stringify({ error: e.message }));
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2193
2197
|
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
if (['png', 'gif', 'webp', 'jpg', 'jpeg'].includes(urlExt)) ext = urlExt;
|
|
2198
|
+
// Delete custom emoji
|
|
2199
|
+
if (method === 'DELETE' && urlPath === '/api/emoji') {
|
|
2200
|
+
if (!checkAuth(req, res)) return;
|
|
2201
|
+
try {
|
|
2202
|
+
const { name, pack } = await parseBody(req);
|
|
2203
|
+
if (!name || !pack) {
|
|
2204
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2205
|
+
return res.end(JSON.stringify({ error: 'Missing name or pack' }));
|
|
2203
2206
|
}
|
|
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}` };
|
|
2207
|
+
const db = getGlobalDb();
|
|
2208
|
+
db.prepare('DELETE FROM custom_emojis WHERE name = ? AND pack = ?').run(name, pack);
|
|
2210
2209
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2211
|
-
return res.end(JSON.stringify(
|
|
2210
|
+
return res.end(JSON.stringify({ ok: true }));
|
|
2212
2211
|
} catch (e) {
|
|
2213
2212
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
2214
2213
|
return res.end(JSON.stringify({ error: e.message }));
|
|
@@ -4285,17 +4284,11 @@ export function createApp(config = {}) {
|
|
|
4285
4284
|
|
|
4286
4285
|
// Custom emoji listing (no auth)
|
|
4287
4286
|
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
4287
|
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
|
-
}
|
|
4288
|
+
const db = getGlobalDb();
|
|
4289
|
+
const rows = db.prepare('SELECT name, pack, url, mime_type FROM custom_emojis ORDER BY created_at DESC').all();
|
|
4297
4290
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'public, max-age=300' });
|
|
4298
|
-
return res.end(JSON.stringify(
|
|
4291
|
+
return res.end(JSON.stringify(rows));
|
|
4299
4292
|
} catch (e) { res.writeHead(500, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: e.message })); }
|
|
4300
4293
|
}
|
|
4301
4294
|
|
|
@@ -4324,35 +4317,34 @@ export function createApp(config = {}) {
|
|
|
4324
4317
|
|
|
4325
4318
|
if (!_checkAuth(req, res)) return;
|
|
4326
4319
|
|
|
4327
|
-
//
|
|
4320
|
+
// Add custom emoji (auth required)
|
|
4328
4321
|
if (method === 'POST' && urlPath === '/api/emoji/add') {
|
|
4329
4322
|
try {
|
|
4330
4323
|
const { url, name, pack } = await parseBody(req);
|
|
4331
4324
|
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
4325
|
const safeName = name.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
4353
|
-
|
|
4326
|
+
const targetPack = pack || 'slackmojis';
|
|
4327
|
+
const urlLower = url.split('?')[0].toLowerCase();
|
|
4328
|
+
let mimeType = 'image/png';
|
|
4329
|
+
if (urlLower.endsWith('.gif')) mimeType = 'image/gif';
|
|
4330
|
+
else if (urlLower.endsWith('.webp')) mimeType = 'image/webp';
|
|
4331
|
+
else if (urlLower.endsWith('.jpg') || urlLower.endsWith('.jpeg')) mimeType = 'image/jpeg';
|
|
4332
|
+
const db = getGlobalDb();
|
|
4333
|
+
db.prepare('INSERT OR REPLACE INTO custom_emojis (name, pack, url, mime_type) VALUES (?, ?, ?, ?)').run(safeName, targetPack, url, mimeType);
|
|
4354
4334
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4355
|
-
return res.end(JSON.stringify({ name: safeName, pack: targetPack,
|
|
4335
|
+
return res.end(JSON.stringify({ name: safeName, pack: targetPack, url, mime_type: mimeType }));
|
|
4336
|
+
} catch (e) { res.writeHead(500, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: e.message })); }
|
|
4337
|
+
}
|
|
4338
|
+
|
|
4339
|
+
// Delete custom emoji (auth required)
|
|
4340
|
+
if (method === 'DELETE' && urlPath === '/api/emoji') {
|
|
4341
|
+
try {
|
|
4342
|
+
const { name, pack } = await parseBody(req);
|
|
4343
|
+
if (!name || !pack) { res.writeHead(400, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: 'Missing name or pack' })); }
|
|
4344
|
+
const db = getGlobalDb();
|
|
4345
|
+
db.prepare('DELETE FROM custom_emojis WHERE name = ? AND pack = ?').run(name, pack);
|
|
4346
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4347
|
+
return res.end(JSON.stringify({ ok: true }));
|
|
4356
4348
|
} catch (e) { res.writeHead(500, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: e.message })); }
|
|
4357
4349
|
}
|
|
4358
4350
|
|
|
@@ -4503,6 +4495,9 @@ if (isDirectRun) {
|
|
|
4503
4495
|
|
|
4504
4496
|
// Connect to gateway
|
|
4505
4497
|
app.gatewayClient.connect();
|
|
4498
|
+
|
|
4499
|
+
// Initialize global DB (custom emojis, etc.)
|
|
4500
|
+
getGlobalDb();
|
|
4506
4501
|
});
|
|
4507
4502
|
|
|
4508
4503
|
// Graceful shutdown
|