@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.js +83 -88
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawchatsai/connector",
3
- "version": "0.0.37",
3
+ "version": "0.0.38",
4
4
  "type": "module",
5
5
  "description": "ClawChats OpenClaw plugin — P2P tunnel + local API bridge",
6
6
  "main": "dist/index.js",
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 packs = fs.readdirSync(emojiDir).filter(d =>
2100
- fs.statSync(path.join(emojiDir, d)).isDirectory()
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(result));
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
- // Download emoji from URL and save to emoji/ directory
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 targetPack = pack || 'custom';
2174
- const emojiDir = path.join(__dirname, 'emoji', targetPack);
2175
- if (!fs.existsSync(emojiDir)) fs.mkdirSync(emojiDir, { recursive: true });
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
- // Follow redirects and download
2178
- const https = await import('https');
2179
- const http = await import('http');
2180
- const download = (downloadUrl, redirects = 0) => new Promise((resolve, reject) => {
2181
- if (redirects > 5) return reject(new Error('Too many redirects'));
2182
- const mod = downloadUrl.startsWith('https') ? https.default : http.default;
2183
- mod.get(downloadUrl, (resp) => {
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
- const { buffer, contentType } = await download(url);
2195
- // Determine extension from content-type or URL
2196
- let ext = 'png';
2197
- if (contentType.includes('gif')) ext = 'gif';
2198
- else if (contentType.includes('webp')) ext = 'webp';
2199
- else if (contentType.includes('jpeg') || contentType.includes('jpg')) ext = 'jpg';
2200
- else {
2201
- const urlExt = url.split('?')[0].split('.').pop().toLowerCase();
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
- const safeName = name.replace(/[^a-zA-Z0-9_-]/g, '_');
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(result));
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 packs = fs.readdirSync(emojiDir).filter(d => fs.statSync(path.join(emojiDir, d)).isDirectory());
4292
- const result = [];
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(result));
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
- // Download emoji from URL (auth required)
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
- fs.writeFileSync(path.join(emojiDir, `${safeName}.${ext}`), buffer);
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, path: `/emoji/${targetPack}/${safeName}.${ext}` }));
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