@link-assistant/web-capture 1.7.4 → 1.7.5
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/CHANGELOG.md +6 -0
- package/bin/web-capture.js +1 -2
- package/package.json +1 -1
- package/src/gdocs.js +1 -112
- package/src/index.js +0 -2
- package/tests/integration/api-endpoints.test.js +11 -0
- package/tests/unit/cli.test.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @link-assistant/web-capture
|
|
2
2
|
|
|
3
|
+
## 1.7.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- dfaf007: Remove the unsupported `/gdocs` HTTP API route from the JavaScript and Rust server surfaces while keeping Google Docs URL capture through normal output formats.
|
|
8
|
+
|
|
3
9
|
## 1.7.4
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/bin/web-capture.js
CHANGED
|
@@ -230,7 +230,7 @@ const config = makeConfig({
|
|
|
230
230
|
'Capture private Google Doc with API token'
|
|
231
231
|
)
|
|
232
232
|
.epilogue(
|
|
233
|
-
'API Endpoints (in server mode):\n GET /html?url=<URL>&engine=<ENGINE> Get rendered HTML\n GET /markdown?url=<URL> Get Markdown conversion\n GET /image?url=<URL>&format=png|jpeg&theme=light|dark Screenshot\n GET /archive?url=<URL>&localImages=true&documentFormat=markdown|html ZIP archive\n GET /pdf?url=<URL>&theme=light|dark PDF with embedded images\n GET /docx?url=<URL> DOCX with embedded images\n GET /fetch?url=<URL> Proxy fetch\n GET /stream?url=<URL> Streaming proxy
|
|
233
|
+
'API Endpoints (in server mode):\n GET /html?url=<URL>&engine=<ENGINE> Get rendered HTML\n GET /markdown?url=<URL> Get Markdown conversion\n GET /image?url=<URL>&format=png|jpeg&theme=light|dark Screenshot\n GET /archive?url=<URL>&localImages=true&documentFormat=markdown|html ZIP archive\n GET /pdf?url=<URL>&theme=light|dark PDF with embedded images\n GET /docx?url=<URL> DOCX with embedded images\n GET /fetch?url=<URL> Proxy fetch\n GET /stream?url=<URL> Streaming proxy'
|
|
234
234
|
)
|
|
235
235
|
.strict(),
|
|
236
236
|
lenv: {
|
|
@@ -271,7 +271,6 @@ async function startServer(port) {
|
|
|
271
271
|
console.log(` GET /docx?url=<URL> - DOCX with embedded images`);
|
|
272
272
|
console.log(` GET /fetch?url=<URL> - Proxy fetch content`);
|
|
273
273
|
console.log(` GET /stream?url=<URL> - Stream content`);
|
|
274
|
-
console.log(` GET /gdocs?url=<URL> - Capture Google Docs document`);
|
|
275
274
|
console.log('');
|
|
276
275
|
console.log('Press Ctrl+C to stop the server');
|
|
277
276
|
resolve(server);
|
package/package.json
CHANGED
package/src/gdocs.js
CHANGED
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import fetch from 'node-fetch';
|
|
8
8
|
import he from 'he';
|
|
9
|
-
import
|
|
10
|
-
import { convertHtmlToMarkdown, prettyPrintHtml } from './lib.js';
|
|
9
|
+
import { convertHtmlToMarkdown } from './lib.js';
|
|
11
10
|
|
|
12
11
|
const GDOCS_URL_PATTERN = /docs\.google\.com\/document\/d\/([a-zA-Z0-9_-]+)/;
|
|
13
12
|
|
|
@@ -223,113 +222,3 @@ export async function fetchGoogleDocAsArchive(url, options = {}) {
|
|
|
223
222
|
images,
|
|
224
223
|
};
|
|
225
224
|
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Express handler for the /gdocs API endpoint.
|
|
229
|
-
*
|
|
230
|
-
* Query parameters:
|
|
231
|
-
* - url (required): Google Docs URL
|
|
232
|
-
* - format (optional): Export format (html, markdown, md, txt, pdf, docx)
|
|
233
|
-
* - apiToken (optional): API token for private documents
|
|
234
|
-
*
|
|
235
|
-
* The API token can also be provided via:
|
|
236
|
-
* - Authorization header: Bearer <token>
|
|
237
|
-
* - X-Api-Token header: <token>
|
|
238
|
-
*
|
|
239
|
-
* @param {import('express').Request} req
|
|
240
|
-
* @param {import('express').Response} res
|
|
241
|
-
*/
|
|
242
|
-
export async function gdocsHandler(req, res) {
|
|
243
|
-
const url = req.query.url;
|
|
244
|
-
if (!url) {
|
|
245
|
-
return res.status(400).send('Missing `url` parameter');
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (!isGoogleDocsUrl(url)) {
|
|
249
|
-
return res.status(400).send('URL is not a Google Docs document URL');
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Resolve API token from query, headers, or environment
|
|
253
|
-
const apiToken =
|
|
254
|
-
req.query.apiToken ||
|
|
255
|
-
extractBearerToken(req.headers.authorization) ||
|
|
256
|
-
req.headers['x-api-token'] ||
|
|
257
|
-
undefined;
|
|
258
|
-
|
|
259
|
-
const format = (req.query.format || 'markdown').toLowerCase();
|
|
260
|
-
|
|
261
|
-
try {
|
|
262
|
-
if (format === 'archive' || format === 'zip') {
|
|
263
|
-
return await sendGDocsArchive(res, url, apiToken);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (format === 'markdown' || format === 'md') {
|
|
267
|
-
const result = await fetchGoogleDocAsMarkdown(url, { apiToken });
|
|
268
|
-
return res.type('text/markdown').send(result.markdown);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
return await sendGDocsExport(res, url, format, apiToken);
|
|
272
|
-
} catch (err) {
|
|
273
|
-
console.error('Google Docs capture error:', err.message);
|
|
274
|
-
return res.status(500).send(`Error capturing Google Doc: ${err.message}`);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
async function sendGDocsArchive(res, url, apiToken) {
|
|
279
|
-
const archiveResult = await fetchGoogleDocAsArchive(url, { apiToken });
|
|
280
|
-
|
|
281
|
-
res.set('Content-Type', 'application/zip');
|
|
282
|
-
res.set(
|
|
283
|
-
'Content-Disposition',
|
|
284
|
-
`attachment; filename="gdoc-${archiveResult.documentId}.zip"`
|
|
285
|
-
);
|
|
286
|
-
|
|
287
|
-
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
288
|
-
archive.pipe(res);
|
|
289
|
-
archive.append(archiveResult.markdown, { name: 'document.md' });
|
|
290
|
-
archive.append(prettyPrintHtml(archiveResult.html), {
|
|
291
|
-
name: 'document.html',
|
|
292
|
-
});
|
|
293
|
-
for (const img of archiveResult.images) {
|
|
294
|
-
archive.append(img.data, { name: `images/${img.filename}` });
|
|
295
|
-
}
|
|
296
|
-
await archive.finalize();
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const FORMAT_CONTENT_TYPES = {
|
|
300
|
-
html: 'text/html',
|
|
301
|
-
txt: 'text/plain',
|
|
302
|
-
pdf: 'application/pdf',
|
|
303
|
-
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
const FORMAT_DISPOSITIONS = {
|
|
307
|
-
pdf: 'inline; filename="document.pdf"',
|
|
308
|
-
docx: 'inline; filename="document.docx"',
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
async function sendGDocsExport(res, url, format, apiToken) {
|
|
312
|
-
const result = await fetchGoogleDoc(url, { format, apiToken });
|
|
313
|
-
const contentType = FORMAT_CONTENT_TYPES[format] || 'text/html';
|
|
314
|
-
const disposition = FORMAT_DISPOSITIONS[format];
|
|
315
|
-
|
|
316
|
-
res.type(contentType);
|
|
317
|
-
if (disposition) {
|
|
318
|
-
res.set('Content-Disposition', disposition);
|
|
319
|
-
}
|
|
320
|
-
return res.send(result.content);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* Extract Bearer token from Authorization header value.
|
|
325
|
-
*
|
|
326
|
-
* @param {string|undefined} authHeader - Authorization header value
|
|
327
|
-
* @returns {string|undefined} Token or undefined
|
|
328
|
-
*/
|
|
329
|
-
function extractBearerToken(authHeader) {
|
|
330
|
-
if (!authHeader || typeof authHeader !== 'string') {
|
|
331
|
-
return undefined;
|
|
332
|
-
}
|
|
333
|
-
const match = authHeader.match(/^Bearer\s+(.+)$/i);
|
|
334
|
-
return match ? match[1] : undefined;
|
|
335
|
-
}
|
package/src/index.js
CHANGED
|
@@ -11,7 +11,6 @@ import { docxHandler } from './docx.js';
|
|
|
11
11
|
import { animationHandler } from './animation.js';
|
|
12
12
|
import { figuresHandler } from './figures.js';
|
|
13
13
|
import { themedImageHandler } from './themed-image.js';
|
|
14
|
-
import { gdocsHandler } from './gdocs.js';
|
|
15
14
|
|
|
16
15
|
const app = express();
|
|
17
16
|
const port = process.env.PORT || 3000;
|
|
@@ -28,7 +27,6 @@ app.get('/docx', docxHandler);
|
|
|
28
27
|
app.get('/animation', animationHandler);
|
|
29
28
|
app.get('/figures', figuresHandler);
|
|
30
29
|
app.get('/themed-image', themedImageHandler);
|
|
31
|
-
app.get('/gdocs', gdocsHandler);
|
|
32
30
|
|
|
33
31
|
// Start the server if this is the main module
|
|
34
32
|
const isMainModule =
|
|
@@ -28,6 +28,17 @@ afterEach(() => {
|
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
describe('API Endpoint Tests', () => {
|
|
31
|
+
describe('unsupported routes', () => {
|
|
32
|
+
it('does not expose /gdocs as an output-format endpoint', async () => {
|
|
33
|
+
await request(app)
|
|
34
|
+
.get('/gdocs')
|
|
35
|
+
.query({
|
|
36
|
+
url: 'https://docs.google.com/document/d/test-doc/edit',
|
|
37
|
+
})
|
|
38
|
+
.expect(404);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
31
42
|
describe('GET /image', () => {
|
|
32
43
|
it('rejects invalid format', async () => {
|
|
33
44
|
await request(app)
|
package/tests/unit/cli.test.js
CHANGED