@clawchatsai/connector 0.0.30 → 0.0.32
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/dist/index.js +20 -2
- package/dist/shim.d.ts +1 -0
- package/dist/shim.js +1 -0
- package/package.json +1 -1
- package/server.js +215 -7
package/dist/index.js
CHANGED
|
@@ -488,15 +488,33 @@ async function handleRpcMessage(dc, msg, ctx) {
|
|
|
488
488
|
};
|
|
489
489
|
try {
|
|
490
490
|
const response = await dispatchRpc(rpcReq, app.handleRequest);
|
|
491
|
+
// For binary content types (images, audio, etc.), wrap as _binary envelope
|
|
492
|
+
// so the browser transport can reconstruct a proper Blob with correct MIME type
|
|
493
|
+
const contentType = response.headers['content-type'] || '';
|
|
494
|
+
const isBinaryResponse = /^(image|audio|video|application\/octet-stream|application\/pdf)/.test(contentType);
|
|
495
|
+
let responseBody;
|
|
496
|
+
if (isBinaryResponse && response.rawBody) {
|
|
497
|
+
// Encode raw bytes as base64 in a _binary envelope.
|
|
498
|
+
// The transport layer on the browser side already handles this format,
|
|
499
|
+
// reconstructing a proper Blob with the correct MIME type.
|
|
500
|
+
responseBody = {
|
|
501
|
+
_binary: true,
|
|
502
|
+
contentType,
|
|
503
|
+
data: response.rawBody.toString('base64'),
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
responseBody = response.body;
|
|
508
|
+
}
|
|
491
509
|
const responseMsg = {
|
|
492
510
|
type: 'rpc-res',
|
|
493
511
|
id: response.id,
|
|
494
512
|
status: response.status,
|
|
495
|
-
body:
|
|
513
|
+
body: responseBody,
|
|
496
514
|
};
|
|
497
515
|
const responseStr = JSON.stringify(responseMsg);
|
|
498
516
|
if (responseStr.length > MAX_DC_MESSAGE_SIZE) {
|
|
499
|
-
sendChunked(dc, response.id, response.status,
|
|
517
|
+
sendChunked(dc, response.id, response.status, JSON.stringify(responseBody));
|
|
500
518
|
}
|
|
501
519
|
else {
|
|
502
520
|
dc.send(responseStr);
|
package/dist/shim.d.ts
CHANGED
package/dist/shim.js
CHANGED
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -742,6 +742,9 @@ async function handleUpdateWorkspace(req, res, params) {
|
|
|
742
742
|
if (body.icon !== undefined) {
|
|
743
743
|
ws.workspaces[params.name].icon = body.icon;
|
|
744
744
|
}
|
|
745
|
+
if (body.lastThread !== undefined) {
|
|
746
|
+
ws.workspaces[params.name].lastThread = body.lastThread;
|
|
747
|
+
}
|
|
745
748
|
setWorkspaces(ws);
|
|
746
749
|
send(res, 200, { workspace: ws.workspaces[params.name] });
|
|
747
750
|
}
|
|
@@ -2037,16 +2040,17 @@ async function handleRequest(req, res) {
|
|
|
2037
2040
|
const isIcon = urlPath.startsWith('/icons/');
|
|
2038
2041
|
const isLib = urlPath.startsWith('/lib/');
|
|
2039
2042
|
const isFrontend = urlPath.startsWith('/frontend/');
|
|
2043
|
+
const isEmoji = urlPath.startsWith('/emoji/');
|
|
2040
2044
|
const isConfig = urlPath === '/config.js';
|
|
2041
2045
|
const staticPath = fileName ? path.join(__dirname, fileName)
|
|
2042
|
-
: (isIcon || isLib || isFrontend || isConfig) ? path.join(__dirname, urlPath.slice(1))
|
|
2046
|
+
: (isIcon || isLib || isFrontend || isEmoji || isConfig) ? path.join(__dirname, urlPath.slice(1))
|
|
2043
2047
|
: null;
|
|
2044
2048
|
if (staticPath && fs.existsSync(staticPath) && fs.statSync(staticPath).isFile()) {
|
|
2045
2049
|
const ext = path.extname(staticPath).toLowerCase();
|
|
2046
2050
|
const mimeMap = {
|
|
2047
2051
|
'.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css',
|
|
2048
2052
|
'.json': 'application/json', '.ico': 'image/x-icon',
|
|
2049
|
-
'.png': 'image/png', '.svg': 'image/svg+xml',
|
|
2053
|
+
'.png': 'image/png', '.svg': 'image/svg+xml', '.gif': 'image/gif', '.webp': 'image/webp',
|
|
2050
2054
|
};
|
|
2051
2055
|
const ct = mimeMap[ext] || 'application/octet-stream';
|
|
2052
2056
|
const stat = fs.statSync(staticPath);
|
|
@@ -2065,6 +2069,133 @@ async function handleRequest(req, res) {
|
|
|
2065
2069
|
return handleServeUpload(req, res, p);
|
|
2066
2070
|
}
|
|
2067
2071
|
|
|
2072
|
+
// Custom emoji listing (no auth — public like static assets)
|
|
2073
|
+
if (method === 'GET' && urlPath === '/api/emoji') {
|
|
2074
|
+
const emojiDir = path.join(__dirname, 'emoji');
|
|
2075
|
+
if (!fs.existsSync(emojiDir)) {
|
|
2076
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2077
|
+
return res.end('[]');
|
|
2078
|
+
}
|
|
2079
|
+
try {
|
|
2080
|
+
const packs = fs.readdirSync(emojiDir).filter(d =>
|
|
2081
|
+
fs.statSync(path.join(emojiDir, d)).isDirectory()
|
|
2082
|
+
);
|
|
2083
|
+
const result = [];
|
|
2084
|
+
for (const pack of packs) {
|
|
2085
|
+
const packDir = path.join(emojiDir, pack);
|
|
2086
|
+
const files = fs.readdirSync(packDir).filter(f =>
|
|
2087
|
+
/\.(png|gif|webp|jpg|jpeg)$/i.test(f)
|
|
2088
|
+
);
|
|
2089
|
+
for (const file of files) {
|
|
2090
|
+
const name = file.replace(/\.[^.]+$/, '');
|
|
2091
|
+
result.push({ name, pack, path: `/emoji/${pack}/${file}` });
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'public, max-age=300' });
|
|
2095
|
+
return res.end(JSON.stringify(result));
|
|
2096
|
+
} catch (e) {
|
|
2097
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
2098
|
+
return res.end(JSON.stringify({ error: e.message }));
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
// Search slackmojis.com (scrapes HTML search since JSON API has no search)
|
|
2103
|
+
if (method === 'GET' && urlPath === '/api/emoji/search') {
|
|
2104
|
+
const q = query.q || '';
|
|
2105
|
+
if (!q) {
|
|
2106
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2107
|
+
return res.end(JSON.stringify({ error: 'Missing ?q= parameter' }));
|
|
2108
|
+
}
|
|
2109
|
+
try {
|
|
2110
|
+
const https = await import('https');
|
|
2111
|
+
const fetchUrl = `https://slackmojis.com/emojis/search?query=${encodeURIComponent(q)}`;
|
|
2112
|
+
const html = await new Promise((resolve, reject) => {
|
|
2113
|
+
https.default.get(fetchUrl, (resp) => {
|
|
2114
|
+
let body = '';
|
|
2115
|
+
resp.on('data', chunk => body += chunk);
|
|
2116
|
+
resp.on('end', () => resolve(body));
|
|
2117
|
+
}).on('error', reject);
|
|
2118
|
+
});
|
|
2119
|
+
// Parse emoji entries from HTML:
|
|
2120
|
+
// <li class='emoji name' title='name'>
|
|
2121
|
+
// <a ... data-emoji-id="123" data-emoji-id-name="123-name" href="/emojis/123-name/download">
|
|
2122
|
+
// <img ... src="https://emojis.slackmojis.com/emojis/images/.../name.png?..." />
|
|
2123
|
+
const results = [];
|
|
2124
|
+
const regex = /data-emoji-id-name="([^"]+)"[^>]*href="([^"]+)"[\s\S]*?<img[^>]*src="([^"]+)"/g;
|
|
2125
|
+
let match;
|
|
2126
|
+
while ((match = regex.exec(html)) !== null && results.length < 50) {
|
|
2127
|
+
const idName = match[1]; // e.g. "57350-sextant"
|
|
2128
|
+
const downloadPath = match[2];
|
|
2129
|
+
const imageUrl = match[3];
|
|
2130
|
+
const name = idName.replace(/^\d+-/, '');
|
|
2131
|
+
results.push({
|
|
2132
|
+
name,
|
|
2133
|
+
image_url: imageUrl,
|
|
2134
|
+
download_url: `https://slackmojis.com${downloadPath}`,
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2137
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2138
|
+
return res.end(JSON.stringify(results));
|
|
2139
|
+
} catch (e) {
|
|
2140
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
2141
|
+
return res.end(JSON.stringify({ error: e.message }));
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
// Download emoji from URL and save to emoji/ directory
|
|
2146
|
+
if (method === 'POST' && urlPath === '/api/emoji/add') {
|
|
2147
|
+
if (!checkAuth(req, res)) return;
|
|
2148
|
+
try {
|
|
2149
|
+
const { url, name, pack } = await parseBody(req);
|
|
2150
|
+
if (!url || !name) {
|
|
2151
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2152
|
+
return res.end(JSON.stringify({ error: 'Missing url or name' }));
|
|
2153
|
+
}
|
|
2154
|
+
const targetPack = pack || 'custom';
|
|
2155
|
+
const emojiDir = path.join(__dirname, 'emoji', targetPack);
|
|
2156
|
+
if (!fs.existsSync(emojiDir)) fs.mkdirSync(emojiDir, { recursive: true });
|
|
2157
|
+
|
|
2158
|
+
// Follow redirects and download
|
|
2159
|
+
const https = await import('https');
|
|
2160
|
+
const http = await import('http');
|
|
2161
|
+
const download = (downloadUrl, redirects = 0) => new Promise((resolve, reject) => {
|
|
2162
|
+
if (redirects > 5) return reject(new Error('Too many redirects'));
|
|
2163
|
+
const mod = downloadUrl.startsWith('https') ? https.default : http.default;
|
|
2164
|
+
mod.get(downloadUrl, (resp) => {
|
|
2165
|
+
if (resp.statusCode >= 300 && resp.statusCode < 400 && resp.headers.location) {
|
|
2166
|
+
return resolve(download(resp.headers.location, redirects + 1));
|
|
2167
|
+
}
|
|
2168
|
+
if (resp.statusCode !== 200) return reject(new Error(`HTTP ${resp.statusCode}`));
|
|
2169
|
+
const chunks = [];
|
|
2170
|
+
resp.on('data', chunk => chunks.push(chunk));
|
|
2171
|
+
resp.on('end', () => resolve({ buffer: Buffer.concat(chunks), contentType: resp.headers['content-type'] || '' }));
|
|
2172
|
+
}).on('error', reject);
|
|
2173
|
+
});
|
|
2174
|
+
|
|
2175
|
+
const { buffer, contentType } = await download(url);
|
|
2176
|
+
// Determine extension from content-type or URL
|
|
2177
|
+
let ext = 'png';
|
|
2178
|
+
if (contentType.includes('gif')) ext = 'gif';
|
|
2179
|
+
else if (contentType.includes('webp')) ext = 'webp';
|
|
2180
|
+
else if (contentType.includes('jpeg') || contentType.includes('jpg')) ext = 'jpg';
|
|
2181
|
+
else {
|
|
2182
|
+
const urlExt = url.split('?')[0].split('.').pop().toLowerCase();
|
|
2183
|
+
if (['png', 'gif', 'webp', 'jpg', 'jpeg'].includes(urlExt)) ext = urlExt;
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
const safeName = name.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
2187
|
+
const filePath = path.join(emojiDir, `${safeName}.${ext}`);
|
|
2188
|
+
fs.writeFileSync(filePath, buffer);
|
|
2189
|
+
|
|
2190
|
+
const result = { name: safeName, pack: targetPack, path: `/emoji/${targetPack}/${safeName}.${ext}` };
|
|
2191
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2192
|
+
return res.end(JSON.stringify(result));
|
|
2193
|
+
} catch (e) {
|
|
2194
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
2195
|
+
return res.end(JSON.stringify({ error: e.message }));
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2068
2199
|
// Auth check for all other routes
|
|
2069
2200
|
if (!checkAuth(req, res)) return;
|
|
2070
2201
|
|
|
@@ -3048,6 +3179,7 @@ export function createApp(config = {}) {
|
|
|
3048
3179
|
if (body.label !== undefined) ws.workspaces[params.name].label = body.label;
|
|
3049
3180
|
if (body.color !== undefined) ws.workspaces[params.name].color = body.color;
|
|
3050
3181
|
if (body.icon !== undefined) ws.workspaces[params.name].icon = body.icon;
|
|
3182
|
+
if (body.lastThread !== undefined) ws.workspaces[params.name].lastThread = body.lastThread;
|
|
3051
3183
|
_setWorkspaces(ws);
|
|
3052
3184
|
send(res, 200, { workspace: ws.workspaces[params.name] });
|
|
3053
3185
|
}
|
|
@@ -3247,10 +3379,14 @@ export function createApp(config = {}) {
|
|
|
3247
3379
|
return sendError(res, 400, 'Required: id, role, content, timestamp');
|
|
3248
3380
|
}
|
|
3249
3381
|
const metadata = body.metadata ? JSON.stringify(body.metadata) : null;
|
|
3250
|
-
const existing = db.prepare('SELECT id, status FROM messages WHERE id = ?').get(body.id);
|
|
3382
|
+
const existing = db.prepare('SELECT id, status, metadata FROM messages WHERE id = ?').get(body.id);
|
|
3251
3383
|
if (existing) {
|
|
3252
|
-
|
|
3253
|
-
|
|
3384
|
+
const newStatus = body.status || existing.status;
|
|
3385
|
+
const statusChanged = body.status && body.status !== existing.status;
|
|
3386
|
+
if (statusChanged || metadata) {
|
|
3387
|
+
// Only overwrite metadata if new metadata is provided; otherwise preserve existing
|
|
3388
|
+
const finalMetadata = metadata || existing.metadata || null;
|
|
3389
|
+
db.prepare('UPDATE messages SET status = ?, content = ?, metadata = ? WHERE id = ?').run(newStatus, body.content, finalMetadata, body.id);
|
|
3254
3390
|
}
|
|
3255
3391
|
} else {
|
|
3256
3392
|
db.prepare('INSERT INTO messages (id, thread_id, role, content, status, metadata, seq, timestamp, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)').run(body.id, params.id, body.role, body.content, body.status || 'sent', metadata, body.seq || null, body.timestamp, Date.now());
|
|
@@ -3869,11 +4005,12 @@ export function createApp(config = {}) {
|
|
|
3869
4005
|
const isIcon = urlPath.startsWith('/icons/');
|
|
3870
4006
|
const isLib = urlPath.startsWith('/lib/');
|
|
3871
4007
|
const isFrontend = urlPath.startsWith('/frontend/');
|
|
4008
|
+
const isEmoji = urlPath.startsWith('/emoji/');
|
|
3872
4009
|
const isConfig = urlPath === '/config.js';
|
|
3873
|
-
const staticPath = fileName ? path.join(__dirname, fileName) : (isIcon || isLib || isFrontend || isConfig) ? path.join(__dirname, urlPath.slice(1)) : null;
|
|
4010
|
+
const staticPath = fileName ? path.join(__dirname, fileName) : (isIcon || isLib || isFrontend || isEmoji || isConfig) ? path.join(__dirname, urlPath.slice(1)) : null;
|
|
3874
4011
|
if (staticPath && fs.existsSync(staticPath) && fs.statSync(staticPath).isFile()) {
|
|
3875
4012
|
const ext = path.extname(staticPath).toLowerCase();
|
|
3876
|
-
const mimeMap = { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css', '.json': 'application/json', '.ico': 'image/x-icon', '.png': 'image/png', '.svg': 'image/svg+xml' };
|
|
4013
|
+
const mimeMap = { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css', '.json': 'application/json', '.ico': 'image/x-icon', '.png': 'image/png', '.svg': 'image/svg+xml', '.gif': 'image/gif', '.webp': 'image/webp' };
|
|
3877
4014
|
const ct = mimeMap[ext] || 'application/octet-stream';
|
|
3878
4015
|
const stat = fs.statSync(staticPath);
|
|
3879
4016
|
res.writeHead(200, { 'Content-Type': ct, 'Content-Length': stat.size, 'Cache-Control': ext === '.html' ? 'no-cache' : 'public, max-age=3600' });
|
|
@@ -3884,8 +4021,79 @@ export function createApp(config = {}) {
|
|
|
3884
4021
|
let p;
|
|
3885
4022
|
if ((p = matchRoute(method, urlPath, 'GET /api/uploads/:threadId/:fileId'))) return _handleServeUpload(req, res, p);
|
|
3886
4023
|
|
|
4024
|
+
// Custom emoji listing (no auth)
|
|
4025
|
+
if (method === 'GET' && urlPath === '/api/emoji') {
|
|
4026
|
+
const emojiDir = path.join(__dirname, 'emoji');
|
|
4027
|
+
if (!fs.existsSync(emojiDir)) { res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end('[]'); }
|
|
4028
|
+
try {
|
|
4029
|
+
const packs = fs.readdirSync(emojiDir).filter(d => fs.statSync(path.join(emojiDir, d)).isDirectory());
|
|
4030
|
+
const result = [];
|
|
4031
|
+
for (const pack of packs) {
|
|
4032
|
+
const files = fs.readdirSync(path.join(emojiDir, pack)).filter(f => /\.(png|gif|webp|jpg|jpeg)$/i.test(f));
|
|
4033
|
+
for (const file of files) result.push({ name: file.replace(/\.[^.]+$/, ''), pack, path: `/emoji/${pack}/${file}` });
|
|
4034
|
+
}
|
|
4035
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'public, max-age=300' });
|
|
4036
|
+
return res.end(JSON.stringify(result));
|
|
4037
|
+
} catch (e) { res.writeHead(500, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: e.message })); }
|
|
4038
|
+
}
|
|
4039
|
+
|
|
4040
|
+
// Search slackmojis.com (no auth, proxied)
|
|
4041
|
+
if (method === 'GET' && urlPath === '/api/emoji/search') {
|
|
4042
|
+
const q = query.q || '';
|
|
4043
|
+
if (!q) { res.writeHead(400, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: 'Missing ?q=' })); }
|
|
4044
|
+
try {
|
|
4045
|
+
const https = await import('https');
|
|
4046
|
+
const html = await new Promise((resolve, reject) => {
|
|
4047
|
+
https.default.get(`https://slackmojis.com/emojis/search?query=${encodeURIComponent(q)}`, (resp) => {
|
|
4048
|
+
let body = ''; resp.on('data', c => body += c); resp.on('end', () => resolve(body));
|
|
4049
|
+
}).on('error', reject);
|
|
4050
|
+
});
|
|
4051
|
+
const results = [];
|
|
4052
|
+
const regex = /data-emoji-id-name="([^"]+)"[^>]*href="([^"]+)"[\s\S]*?<img[^>]*src="([^"]+)"/g;
|
|
4053
|
+
let match;
|
|
4054
|
+
while ((match = regex.exec(html)) !== null && results.length < 50) {
|
|
4055
|
+
const name = match[1].replace(/^\d+-/, '');
|
|
4056
|
+
results.push({ name, image_url: match[3], download_url: `https://slackmojis.com${match[2]}` });
|
|
4057
|
+
}
|
|
4058
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4059
|
+
return res.end(JSON.stringify(results));
|
|
4060
|
+
} catch (e) { res.writeHead(500, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: e.message })); }
|
|
4061
|
+
}
|
|
4062
|
+
|
|
3887
4063
|
if (!_checkAuth(req, res)) return;
|
|
3888
4064
|
|
|
4065
|
+
// Download emoji from URL (auth required)
|
|
4066
|
+
if (method === 'POST' && urlPath === '/api/emoji/add') {
|
|
4067
|
+
try {
|
|
4068
|
+
const { url, name, pack } = await parseBody(req);
|
|
4069
|
+
if (!url || !name) { res.writeHead(400, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: 'Missing url or name' })); }
|
|
4070
|
+
const targetPack = pack || 'custom';
|
|
4071
|
+
const emojiDir = path.join(__dirname, 'emoji', targetPack);
|
|
4072
|
+
if (!fs.existsSync(emojiDir)) fs.mkdirSync(emojiDir, { recursive: true });
|
|
4073
|
+
const https = await import('https');
|
|
4074
|
+
const http = await import('http');
|
|
4075
|
+
const download = (dlUrl, redirects = 0) => new Promise((resolve, reject) => {
|
|
4076
|
+
if (redirects > 5) return reject(new Error('Too many redirects'));
|
|
4077
|
+
const mod = dlUrl.startsWith('https') ? https.default : http.default;
|
|
4078
|
+
mod.get(dlUrl, (resp) => {
|
|
4079
|
+
if (resp.statusCode >= 300 && resp.statusCode < 400 && resp.headers.location) return resolve(download(resp.headers.location, redirects + 1));
|
|
4080
|
+
if (resp.statusCode !== 200) return reject(new Error(`HTTP ${resp.statusCode}`));
|
|
4081
|
+
const chunks = []; resp.on('data', c => chunks.push(c)); resp.on('end', () => resolve({ buffer: Buffer.concat(chunks), contentType: resp.headers['content-type'] || '' }));
|
|
4082
|
+
}).on('error', reject);
|
|
4083
|
+
});
|
|
4084
|
+
const { buffer, contentType } = await download(url);
|
|
4085
|
+
let ext = 'png';
|
|
4086
|
+
if (contentType.includes('gif')) ext = 'gif';
|
|
4087
|
+
else if (contentType.includes('webp')) ext = 'webp';
|
|
4088
|
+
else if (contentType.includes('jpeg') || contentType.includes('jpg')) ext = 'jpg';
|
|
4089
|
+
else { const u = url.split('?')[0].split('.').pop().toLowerCase(); if (['png','gif','webp','jpg','jpeg'].includes(u)) ext = u; }
|
|
4090
|
+
const safeName = name.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
4091
|
+
fs.writeFileSync(path.join(emojiDir, `${safeName}.${ext}`), buffer);
|
|
4092
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4093
|
+
return res.end(JSON.stringify({ name: safeName, pack: targetPack, path: `/emoji/${targetPack}/${safeName}.${ext}` }));
|
|
4094
|
+
} catch (e) { res.writeHead(500, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: e.message })); }
|
|
4095
|
+
}
|
|
4096
|
+
|
|
3889
4097
|
try {
|
|
3890
4098
|
if (method === 'GET' && urlPath === '/api/file') return handleServeFile(req, res, query);
|
|
3891
4099
|
if (method === 'GET' && urlPath === '/api/workspace') return handleWorkspaceList(req, res, query);
|