better-gdrive 0.1.0
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/LICENSE +21 -0
- package/README.md +158 -0
- package/dist/index.d.mts +37 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +194 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +152 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sn4tchZ
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# better-gdrive
|
|
2
|
+
|
|
3
|
+
[](https://nodejs.org)
|
|
4
|
+
[](https://www.npmjs.com/package/better-gdrive)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
|
|
7
|
+
Download files from **public Google Drive** without OAuth: list a folder, get a file in memory (blob/buffer), read metadata, or save directly to disk. Lightweight, no runtime dependencies, optimized (streaming, low memory).
|
|
8
|
+
|
|
9
|
+
If this package is useful to you, a **⭐ [star](https://github.com/Sn4tchZ/better-gdrive)** on the repo is always appreciated.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install better-gdrive
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Requirements:** Node ≥ 18. Files or folders must be shared with "Anyone with the link".
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## API — Tutorial by function
|
|
24
|
+
|
|
25
|
+
| [listFiles](#listfilesfolderid-options) | [getFile](#getfilefileid-format-options) | [getFileMetadata](#getfilemetadatafileid-options) | [downloadFile](#downloadfilefileid-path-options) | [toFileId](#tofileidinput) |
|
|
26
|
+
|----------------------------------------|----------------------------------------|--------------------------------------------------|--------------------------------------------------|---------------------------|
|
|
27
|
+
|
|
28
|
+
All functions that take a file accept either an **ID** (e.g. `abc123xyz`) or a **share link** (the ID is extracted automatically). Cancellation is supported via `{ signal: AbortSignal }` on every call.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
### `listFiles(folderId, options?)`
|
|
33
|
+
|
|
34
|
+
Lists files in a **public folder** (share link or folder ID).
|
|
35
|
+
|
|
36
|
+
**Returns:** `Promise<ListFileEntry[]>` with `{ id: string, name?: string }` (name is set when Drive’s HTML provides it).
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
import { listFiles } from "better-gdrive";
|
|
40
|
+
|
|
41
|
+
const files = await listFiles("https://drive.google.com/drive/folders/1ABC...");
|
|
42
|
+
// or
|
|
43
|
+
const files = await listFiles("1ABC...");
|
|
44
|
+
|
|
45
|
+
console.log(files);
|
|
46
|
+
// [{ id: "xyz123", name: "report.pdf" }, { id: "abc456", name: "data.csv" }, ...]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
### `getFile(fileId, format, options?)`
|
|
52
|
+
|
|
53
|
+
Gets the file **in memory** as a `Blob` (browser or Node) or `Buffer` (Node).
|
|
54
|
+
|
|
55
|
+
- **format:** `"blob"` | `"buffer"`
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
import { getFile } from "better-gdrive";
|
|
59
|
+
|
|
60
|
+
// As buffer (Node)
|
|
61
|
+
const buffer = await getFile("FILE_ID", "buffer");
|
|
62
|
+
fs.writeFileSync("./out.pdf", buffer);
|
|
63
|
+
|
|
64
|
+
// As blob (Node or browser)
|
|
65
|
+
const blob = await getFile("FILE_ID", "blob");
|
|
66
|
+
const url = URL.createObjectURL(blob);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
### `getFileMetadata(fileId, options?)`
|
|
72
|
+
|
|
73
|
+
Gets **metadata** without downloading the file (HEAD / minimal Range request).
|
|
74
|
+
|
|
75
|
+
**Returns:** `Promise<FileMetadata>` with `filename?`, `size?`, `mimeType?`, `lastModified?` (Date).
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
import { getFileMetadata } from "better-gdrive";
|
|
79
|
+
|
|
80
|
+
const meta = await getFileMetadata("FILE_ID");
|
|
81
|
+
console.log(meta.filename); // "document.pdf"
|
|
82
|
+
console.log(meta.size); // 1024000
|
|
83
|
+
console.log(meta.lastModified); // Date
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
### `downloadFile(fileId, path, options?)`
|
|
89
|
+
|
|
90
|
+
Saves the file **directly to disk** (Node only). Uses streaming to limit memory usage.
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
import { downloadFile } from "better-gdrive";
|
|
94
|
+
|
|
95
|
+
await downloadFile("FILE_ID", "./downloads/document.pdf");
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### `toFileId(input)`
|
|
101
|
+
|
|
102
|
+
Extracts the **Drive ID** from a share link, or returns the string as-is if it’s already an ID.
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
import { toFileId } from "better-gdrive";
|
|
106
|
+
|
|
107
|
+
const id = toFileId("https://drive.google.com/file/d/1abcXYZ/view?usp=sharing");
|
|
108
|
+
// "1abcXYZ"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
### Errors: `GDriveDownloadError`
|
|
114
|
+
|
|
115
|
+
On failure (bad HTTP status, no download link, etc.), the package throws **`GDriveDownloadError`** (subclass of `Error`) with:
|
|
116
|
+
|
|
117
|
+
- **message**
|
|
118
|
+
- **statusCode** (optional)
|
|
119
|
+
- **fileId** (optional)
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
import { getFile, GDriveDownloadError } from "better-gdrive";
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const buffer = await getFile("BAD_ID", "buffer");
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (err instanceof GDriveDownloadError) {
|
|
128
|
+
console.error(err.fileId, err.statusCode);
|
|
129
|
+
}
|
|
130
|
+
throw err;
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
### Cancellation: `signal`
|
|
137
|
+
|
|
138
|
+
All functions that accept `options` support **`options.signal`** (AbortSignal) to cancel the request.
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
const controller = new AbortController();
|
|
142
|
+
setTimeout(() => controller.abort(), 5000);
|
|
143
|
+
|
|
144
|
+
const files = await listFiles("FOLDER_ID", { signal: controller.signal });
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Exported types
|
|
150
|
+
|
|
151
|
+
- **`ListFileEntry`**: `{ id: string; name?: string }` (returned by `listFiles`)
|
|
152
|
+
- **`FileMetadata`**: `{ filename?; size?; mimeType?; lastModified? }` (returned by `getFileMetadata`)
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## License
|
|
157
|
+
|
|
158
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
interface FileMetadata {
|
|
2
|
+
filename?: string;
|
|
3
|
+
size?: number;
|
|
4
|
+
mimeType?: string;
|
|
5
|
+
lastModified?: Date;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface ListFileEntry {
|
|
9
|
+
id: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Error thrown when a Google Drive download fails (wrong status, no link, etc.).
|
|
15
|
+
*/
|
|
16
|
+
declare class GDriveDownloadError extends Error {
|
|
17
|
+
readonly statusCode: number | undefined;
|
|
18
|
+
readonly fileId: string | undefined;
|
|
19
|
+
constructor(message: string, options?: {
|
|
20
|
+
cause?: unknown;
|
|
21
|
+
statusCode?: number;
|
|
22
|
+
fileId?: string;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Normalizes share link to file ID, or returns input if already an ID. */
|
|
27
|
+
declare function toFileId(input: string): string;
|
|
28
|
+
|
|
29
|
+
interface CommonOptions {
|
|
30
|
+
signal?: AbortSignal;
|
|
31
|
+
}
|
|
32
|
+
declare function listFiles(folderId: string, options?: CommonOptions): Promise<ListFileEntry[]>;
|
|
33
|
+
declare function getFile(fileId: string, format: "blob" | "buffer", options?: CommonOptions): Promise<Blob | Buffer>;
|
|
34
|
+
declare function getFileMetadata(fileId: string, options?: CommonOptions): Promise<FileMetadata>;
|
|
35
|
+
declare function downloadFile(fileId: string, path: string, options?: CommonOptions): Promise<void>;
|
|
36
|
+
|
|
37
|
+
export { type CommonOptions, type FileMetadata, GDriveDownloadError, type ListFileEntry, downloadFile, getFile, getFileMetadata, listFiles, toFileId };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
interface FileMetadata {
|
|
2
|
+
filename?: string;
|
|
3
|
+
size?: number;
|
|
4
|
+
mimeType?: string;
|
|
5
|
+
lastModified?: Date;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface ListFileEntry {
|
|
9
|
+
id: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Error thrown when a Google Drive download fails (wrong status, no link, etc.).
|
|
15
|
+
*/
|
|
16
|
+
declare class GDriveDownloadError extends Error {
|
|
17
|
+
readonly statusCode: number | undefined;
|
|
18
|
+
readonly fileId: string | undefined;
|
|
19
|
+
constructor(message: string, options?: {
|
|
20
|
+
cause?: unknown;
|
|
21
|
+
statusCode?: number;
|
|
22
|
+
fileId?: string;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Normalizes share link to file ID, or returns input if already an ID. */
|
|
27
|
+
declare function toFileId(input: string): string;
|
|
28
|
+
|
|
29
|
+
interface CommonOptions {
|
|
30
|
+
signal?: AbortSignal;
|
|
31
|
+
}
|
|
32
|
+
declare function listFiles(folderId: string, options?: CommonOptions): Promise<ListFileEntry[]>;
|
|
33
|
+
declare function getFile(fileId: string, format: "blob" | "buffer", options?: CommonOptions): Promise<Blob | Buffer>;
|
|
34
|
+
declare function getFileMetadata(fileId: string, options?: CommonOptions): Promise<FileMetadata>;
|
|
35
|
+
declare function downloadFile(fileId: string, path: string, options?: CommonOptions): Promise<void>;
|
|
36
|
+
|
|
37
|
+
export { type CommonOptions, type FileMetadata, GDriveDownloadError, type ListFileEntry, downloadFile, getFile, getFileMetadata, listFiles, toFileId };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
GDriveDownloadError: () => GDriveDownloadError,
|
|
34
|
+
downloadFile: () => downloadFile,
|
|
35
|
+
getFile: () => getFile,
|
|
36
|
+
getFileMetadata: () => getFileMetadata,
|
|
37
|
+
listFiles: () => listFiles2,
|
|
38
|
+
toFileId: () => toFileId
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(index_exports);
|
|
41
|
+
|
|
42
|
+
// src/errors.ts
|
|
43
|
+
var GDriveDownloadError = class _GDriveDownloadError extends Error {
|
|
44
|
+
statusCode;
|
|
45
|
+
fileId;
|
|
46
|
+
constructor(message, options) {
|
|
47
|
+
super(message);
|
|
48
|
+
this.name = "GDriveDownloadError";
|
|
49
|
+
this.statusCode = options?.statusCode;
|
|
50
|
+
this.fileId = options?.fileId;
|
|
51
|
+
if (options?.cause !== void 0) {
|
|
52
|
+
this.cause = options.cause;
|
|
53
|
+
}
|
|
54
|
+
Object.setPrototypeOf(this, _GDriveDownloadError.prototype);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// src/resolve.ts
|
|
59
|
+
var FILE_ID_REGEX = /\/d\/([a-zA-Z0-9_-]+)(?:\/|$)/;
|
|
60
|
+
function toFileId(input) {
|
|
61
|
+
const m = input.match(FILE_ID_REGEX);
|
|
62
|
+
return m ? m[1] : input;
|
|
63
|
+
}
|
|
64
|
+
async function getDownloadUrl(fileId, signal) {
|
|
65
|
+
const id = toFileId(fileId);
|
|
66
|
+
const res = await fetch(
|
|
67
|
+
`https://drive.google.com/uc?id=${encodeURIComponent(id)}&export=download`,
|
|
68
|
+
{ redirect: "manual", signal }
|
|
69
|
+
);
|
|
70
|
+
if (res.status === 303) {
|
|
71
|
+
const loc = res.headers.get("Location");
|
|
72
|
+
if (loc) return loc;
|
|
73
|
+
throw new GDriveDownloadError("303 without Location", { statusCode: 303, fileId: id });
|
|
74
|
+
}
|
|
75
|
+
if (res.status === 200) {
|
|
76
|
+
const action = (await res.text()).match(/action="([^"]+)"/)?.[1];
|
|
77
|
+
if (action) return action;
|
|
78
|
+
throw new GDriveDownloadError("200 without form action", { statusCode: 200, fileId: id });
|
|
79
|
+
}
|
|
80
|
+
throw new GDriveDownloadError(`Unexpected status ${res.status}`, { statusCode: res.status, fileId: id });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/metadata.ts
|
|
84
|
+
function parseFilename(h) {
|
|
85
|
+
if (!h) return void 0;
|
|
86
|
+
const u = h.match(/filename\*=UTF-8''([^;]+)/i);
|
|
87
|
+
if (u) {
|
|
88
|
+
try {
|
|
89
|
+
return decodeURIComponent(u[1].trim());
|
|
90
|
+
} catch {
|
|
91
|
+
return void 0;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const q = h.match(/filename="([^"]+)"/);
|
|
95
|
+
return q ? q[1] : void 0;
|
|
96
|
+
}
|
|
97
|
+
function parseSize(headers) {
|
|
98
|
+
const cl = headers.get("Content-Length");
|
|
99
|
+
if (cl != null) {
|
|
100
|
+
const n = parseInt(cl, 10);
|
|
101
|
+
if (!Number.isNaN(n)) return n;
|
|
102
|
+
}
|
|
103
|
+
const cr = headers.get("Content-Range")?.match(/\/\s*(\d+)/);
|
|
104
|
+
return cr ? parseInt(cr[1], 10) : void 0;
|
|
105
|
+
}
|
|
106
|
+
async function getMetadataFromUrl(url, signal) {
|
|
107
|
+
const res = await fetch(url, { method: "HEAD", redirect: "follow", signal });
|
|
108
|
+
const headers = res.ok ? res.headers : res.status === 405 ? (await fetch(url, { method: "GET", redirect: "follow", signal, headers: { Range: "bytes=0-0" } })).headers : new Headers();
|
|
109
|
+
const cd = headers.get("Content-Disposition");
|
|
110
|
+
const lm = headers.get("Last-Modified");
|
|
111
|
+
const ct = headers.get("Content-Type");
|
|
112
|
+
let lastModified;
|
|
113
|
+
if (lm) {
|
|
114
|
+
const d = new Date(lm);
|
|
115
|
+
if (!Number.isNaN(d.getTime())) lastModified = d;
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
filename: parseFilename(cd),
|
|
119
|
+
size: parseSize(headers),
|
|
120
|
+
mimeType: ct ? ct.split(";")[0].trim() || void 0 : void 0,
|
|
121
|
+
lastModified
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/list.ts
|
|
126
|
+
var FOLDER_URL = "https://drive.google.com/drive/folders/";
|
|
127
|
+
var FOLDER_ID_REGEX = /\/folders\/([a-zA-Z0-9_-]+)/;
|
|
128
|
+
var SSK_FILE_ID = /ssk='[^']*:([a-zA-Z0-9_-]+)-0-1'/g;
|
|
129
|
+
var TR_ID_NAME = /<tr[^>]*\sdata-id="([a-zA-Z0-9_-]+)"[^>]*>[\s\S]*?(?:data-tooltip="([^"]+\.(?:csv|txt|pdf|zip|json))"|<strong[^>]*>([^<]+\.(?:csv|txt|pdf|zip|json))<\/strong>)/gi;
|
|
130
|
+
function toFolderId(input) {
|
|
131
|
+
const m = input.match(FOLDER_ID_REGEX);
|
|
132
|
+
return m ? m[1] : input;
|
|
133
|
+
}
|
|
134
|
+
async function listFiles(folderLinkOrId, signal) {
|
|
135
|
+
const folderId = toFolderId(folderLinkOrId);
|
|
136
|
+
const html = await (await fetch(`${FOLDER_URL}${encodeURIComponent(folderId)}`, { signal })).text();
|
|
137
|
+
const idToName = /* @__PURE__ */ new Map();
|
|
138
|
+
let tr;
|
|
139
|
+
TR_ID_NAME.lastIndex = 0;
|
|
140
|
+
while ((tr = TR_ID_NAME.exec(html)) !== null) {
|
|
141
|
+
const id = tr[1];
|
|
142
|
+
const name = tr[2] || tr[3]?.trim();
|
|
143
|
+
if (name && !idToName.has(id)) idToName.set(id, name);
|
|
144
|
+
}
|
|
145
|
+
const seen = /* @__PURE__ */ new Set();
|
|
146
|
+
const out = [];
|
|
147
|
+
let m;
|
|
148
|
+
SSK_FILE_ID.lastIndex = 0;
|
|
149
|
+
while ((m = SSK_FILE_ID.exec(html)) !== null) {
|
|
150
|
+
if (!seen.has(m[1])) {
|
|
151
|
+
seen.add(m[1]);
|
|
152
|
+
out.push({ id: m[1], name: idToName.get(m[1]) });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/index.ts
|
|
159
|
+
async function listFiles2(folderId, options) {
|
|
160
|
+
return listFiles(folderId, options?.signal);
|
|
161
|
+
}
|
|
162
|
+
async function getFile(fileId, format, options) {
|
|
163
|
+
const url = await getDownloadUrl(fileId, options?.signal);
|
|
164
|
+
const res = await fetch(url, { signal: options?.signal });
|
|
165
|
+
if (!res.ok) throw new Error(`Fetch failed: ${res.status}`);
|
|
166
|
+
if (format === "buffer") return Buffer.from(await res.arrayBuffer());
|
|
167
|
+
return res.blob();
|
|
168
|
+
}
|
|
169
|
+
async function getFileMetadata(fileId, options) {
|
|
170
|
+
const url = await getDownloadUrl(fileId, options?.signal);
|
|
171
|
+
return getMetadataFromUrl(url, options?.signal);
|
|
172
|
+
}
|
|
173
|
+
async function downloadFile(fileId, path, options) {
|
|
174
|
+
const url = await getDownloadUrl(fileId, options?.signal);
|
|
175
|
+
const res = await fetch(url, { signal: options?.signal });
|
|
176
|
+
if (!res.ok) throw new Error(`Fetch failed: ${res.status}`);
|
|
177
|
+
if (res.body == null) throw new Error("Response has no body");
|
|
178
|
+
const { Readable } = await import("stream");
|
|
179
|
+
const { createWriteStream } = await import("fs");
|
|
180
|
+
const { pipeline } = await import("stream/promises");
|
|
181
|
+
const nodeReadable = Readable.fromWeb(res.body);
|
|
182
|
+
const dest = createWriteStream(path);
|
|
183
|
+
await pipeline(nodeReadable, dest);
|
|
184
|
+
}
|
|
185
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
186
|
+
0 && (module.exports = {
|
|
187
|
+
GDriveDownloadError,
|
|
188
|
+
downloadFile,
|
|
189
|
+
getFile,
|
|
190
|
+
getFileMetadata,
|
|
191
|
+
listFiles,
|
|
192
|
+
toFileId
|
|
193
|
+
});
|
|
194
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/resolve.ts","../src/metadata.ts","../src/list.ts"],"sourcesContent":["export { GDriveDownloadError } from \"./errors.js\";\r\nexport { toFileId } from \"./resolve.js\";\r\nexport type { FileMetadata } from \"./metadata.js\";\r\nexport type { ListFileEntry } from \"./list.js\";\r\n\r\nimport { getDownloadUrl } from \"./resolve.js\";\r\nimport { getMetadataFromUrl } from \"./metadata.js\";\r\nimport { listFiles as listFilesInFolder } from \"./list.js\";\r\n\r\nexport interface CommonOptions {\r\n signal?: AbortSignal;\r\n}\r\n\r\nexport async function listFiles(\r\n folderId: string,\r\n options?: CommonOptions\r\n): Promise<import(\"./list.js\").ListFileEntry[]> {\r\n return listFilesInFolder(folderId, options?.signal);\r\n}\r\n\r\nexport async function getFile(\r\n fileId: string,\r\n format: \"blob\" | \"buffer\",\r\n options?: CommonOptions\r\n): Promise<Blob | Buffer> {\r\n const url = await getDownloadUrl(fileId, options?.signal);\r\n const res = await fetch(url, { signal: options?.signal });\r\n if (!res.ok) throw new Error(`Fetch failed: ${res.status}`);\r\n if (format === \"buffer\") return Buffer.from(await res.arrayBuffer());\r\n return res.blob();\r\n}\r\n\r\nexport async function getFileMetadata(\r\n fileId: string,\r\n options?: CommonOptions\r\n): Promise<import(\"./metadata.js\").FileMetadata> {\r\n const url = await getDownloadUrl(fileId, options?.signal);\r\n return getMetadataFromUrl(url, options?.signal);\r\n}\r\n\r\nexport async function downloadFile(\r\n fileId: string,\r\n path: string,\r\n options?: CommonOptions\r\n): Promise<void> {\r\n const url = await getDownloadUrl(fileId, options?.signal);\r\n const res = await fetch(url, { signal: options?.signal });\r\n if (!res.ok) throw new Error(`Fetch failed: ${res.status}`);\r\n if (res.body == null) throw new Error(\"Response has no body\");\r\n const { Readable } = await import(\"node:stream\");\r\n const { createWriteStream } = await import(\"node:fs\");\r\n const { pipeline } = await import(\"node:stream/promises\");\r\n const nodeReadable = Readable.fromWeb(res.body as Parameters<typeof Readable.fromWeb>[0]);\r\n const dest = createWriteStream(path);\r\n await pipeline(nodeReadable, dest);\r\n}\r\n","/**\r\n * Error thrown when a Google Drive download fails (wrong status, no link, etc.).\r\n */\r\nexport class GDriveDownloadError extends Error {\r\n readonly statusCode: number | undefined;\r\n readonly fileId: string | undefined;\r\n\r\n constructor(\r\n message: string,\r\n options?: { cause?: unknown; statusCode?: number; fileId?: string }\r\n ) {\r\n super(message);\r\n this.name = \"GDriveDownloadError\";\r\n this.statusCode = options?.statusCode;\r\n this.fileId = options?.fileId;\r\n if (options?.cause !== undefined) {\r\n this.cause = options.cause;\r\n }\r\n Object.setPrototypeOf(this, GDriveDownloadError.prototype);\r\n }\r\n}\r\n","import { GDriveDownloadError } from \"./errors.js\";\r\n\r\nconst FILE_ID_REGEX = /\\/d\\/([a-zA-Z0-9_-]+)(?:\\/|$)/;\r\n\r\n/** Normalizes share link to file ID, or returns input if already an ID. */\r\nexport function toFileId(input: string): string {\r\n const m = input.match(FILE_ID_REGEX);\r\n return m ? m[1] : input;\r\n}\r\n\r\n/** Resolves direct download URL for a public file by ID. */\r\nexport async function getDownloadUrl(\r\n fileId: string,\r\n signal?: AbortSignal\r\n): Promise<string> {\r\n const id = toFileId(fileId);\r\n const res = await fetch(\r\n `https://drive.google.com/uc?id=${encodeURIComponent(id)}&export=download`,\r\n { redirect: \"manual\", signal }\r\n );\r\n if (res.status === 303) {\r\n const loc = res.headers.get(\"Location\");\r\n if (loc) return loc;\r\n throw new GDriveDownloadError(\"303 without Location\", { statusCode: 303, fileId: id });\r\n }\r\n if (res.status === 200) {\r\n const action = (await res.text()).match(/action=\"([^\"]+)\"/)?.[1];\r\n if (action) return action;\r\n throw new GDriveDownloadError(\"200 without form action\", { statusCode: 200, fileId: id });\r\n }\r\n throw new GDriveDownloadError(`Unexpected status ${res.status}`, { statusCode: res.status, fileId: id });\r\n}\r\n","export interface FileMetadata {\r\n filename?: string;\r\n size?: number;\r\n mimeType?: string;\r\n lastModified?: Date;\r\n}\r\n\r\nfunction parseFilename(h: string | null): string | undefined {\r\n if (!h) return undefined;\r\n const u = h.match(/filename\\*=UTF-8''([^;]+)/i);\r\n if (u) {\r\n try { return decodeURIComponent(u[1].trim()); } catch { return undefined; }\r\n }\r\n const q = h.match(/filename=\"([^\"]+)\"/);\r\n return q ? q[1] : undefined;\r\n}\r\n\r\nfunction parseSize(headers: Headers): number | undefined {\r\n const cl = headers.get(\"Content-Length\");\r\n if (cl != null) { const n = parseInt(cl, 10); if (!Number.isNaN(n)) return n; }\r\n const cr = headers.get(\"Content-Range\")?.match(/\\/\\s*(\\d+)/);\r\n return cr ? parseInt(cr[1], 10) : undefined;\r\n}\r\n\r\nexport async function getMetadataFromUrl(\r\n url: string,\r\n signal?: AbortSignal\r\n): Promise<FileMetadata> {\r\n const res = await fetch(url, { method: \"HEAD\", redirect: \"follow\", signal });\r\n const headers = res.ok ? res.headers : (res.status === 405\r\n ? (await fetch(url, { method: \"GET\", redirect: \"follow\", signal, headers: { Range: \"bytes=0-0\" } })).headers\r\n : new Headers());\r\n const cd = headers.get(\"Content-Disposition\");\r\n const lm = headers.get(\"Last-Modified\");\r\n const ct = headers.get(\"Content-Type\");\r\n let lastModified: Date | undefined;\r\n if (lm) { const d = new Date(lm); if (!Number.isNaN(d.getTime())) lastModified = d; }\r\n return {\r\n filename: parseFilename(cd),\r\n size: parseSize(headers),\r\n mimeType: ct ? ct.split(\";\")[0].trim() || undefined : undefined,\r\n lastModified,\r\n };\r\n}\r\n","const FOLDER_URL = \"https://drive.google.com/drive/folders/\";\r\nconst FOLDER_ID_REGEX = /\\/folders\\/([a-zA-Z0-9_-]+)/;\r\nconst SSK_FILE_ID = /ssk='[^']*:([a-zA-Z0-9_-]+)-0-1'/g;\r\n/** Row with data-id and filename in data-tooltip or <strong>. */\r\nconst TR_ID_NAME = /<tr[^>]*\\sdata-id=\"([a-zA-Z0-9_-]+)\"[^>]*>[\\s\\S]*?(?:data-tooltip=\"([^\"]+\\.(?:csv|txt|pdf|zip|json))\"|<strong[^>]*>([^<]+\\.(?:csv|txt|pdf|zip|json))<\\/strong>)/gi;\r\n\r\nexport interface ListFileEntry {\r\n id: string;\r\n name?: string;\r\n}\r\n\r\nfunction toFolderId(input: string): string {\r\n const m = input.match(FOLDER_ID_REGEX);\r\n return m ? m[1] : input;\r\n}\r\n\r\nexport async function listFiles(\r\n folderLinkOrId: string,\r\n signal?: AbortSignal\r\n): Promise<ListFileEntry[]> {\r\n const folderId = toFolderId(folderLinkOrId);\r\n const html = await (await fetch(`${FOLDER_URL}${encodeURIComponent(folderId)}`, { signal })).text();\r\n const idToName = new Map<string, string>();\r\n let tr: RegExpExecArray | null;\r\n TR_ID_NAME.lastIndex = 0;\r\n while ((tr = TR_ID_NAME.exec(html)) !== null) {\r\n const id = tr[1];\r\n const name = tr[2] || tr[3]?.trim();\r\n if (name && !idToName.has(id)) idToName.set(id, name);\r\n }\r\n const seen = new Set<string>();\r\n const out: ListFileEntry[] = [];\r\n let m: RegExpExecArray | null;\r\n SSK_FILE_ID.lastIndex = 0;\r\n while ((m = SSK_FILE_ID.exec(html)) !== null) {\r\n if (!seen.has(m[1])) {\r\n seen.add(m[1]);\r\n out.push({ id: m[1], name: idToName.get(m[1]) });\r\n }\r\n }\r\n return out;\r\n}\r\n"],"mappings":"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","names":["listFiles","listFiles"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var GDriveDownloadError = class _GDriveDownloadError extends Error {
|
|
3
|
+
statusCode;
|
|
4
|
+
fileId;
|
|
5
|
+
constructor(message, options) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "GDriveDownloadError";
|
|
8
|
+
this.statusCode = options?.statusCode;
|
|
9
|
+
this.fileId = options?.fileId;
|
|
10
|
+
if (options?.cause !== void 0) {
|
|
11
|
+
this.cause = options.cause;
|
|
12
|
+
}
|
|
13
|
+
Object.setPrototypeOf(this, _GDriveDownloadError.prototype);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/resolve.ts
|
|
18
|
+
var FILE_ID_REGEX = /\/d\/([a-zA-Z0-9_-]+)(?:\/|$)/;
|
|
19
|
+
function toFileId(input) {
|
|
20
|
+
const m = input.match(FILE_ID_REGEX);
|
|
21
|
+
return m ? m[1] : input;
|
|
22
|
+
}
|
|
23
|
+
async function getDownloadUrl(fileId, signal) {
|
|
24
|
+
const id = toFileId(fileId);
|
|
25
|
+
const res = await fetch(
|
|
26
|
+
`https://drive.google.com/uc?id=${encodeURIComponent(id)}&export=download`,
|
|
27
|
+
{ redirect: "manual", signal }
|
|
28
|
+
);
|
|
29
|
+
if (res.status === 303) {
|
|
30
|
+
const loc = res.headers.get("Location");
|
|
31
|
+
if (loc) return loc;
|
|
32
|
+
throw new GDriveDownloadError("303 without Location", { statusCode: 303, fileId: id });
|
|
33
|
+
}
|
|
34
|
+
if (res.status === 200) {
|
|
35
|
+
const action = (await res.text()).match(/action="([^"]+)"/)?.[1];
|
|
36
|
+
if (action) return action;
|
|
37
|
+
throw new GDriveDownloadError("200 without form action", { statusCode: 200, fileId: id });
|
|
38
|
+
}
|
|
39
|
+
throw new GDriveDownloadError(`Unexpected status ${res.status}`, { statusCode: res.status, fileId: id });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/metadata.ts
|
|
43
|
+
function parseFilename(h) {
|
|
44
|
+
if (!h) return void 0;
|
|
45
|
+
const u = h.match(/filename\*=UTF-8''([^;]+)/i);
|
|
46
|
+
if (u) {
|
|
47
|
+
try {
|
|
48
|
+
return decodeURIComponent(u[1].trim());
|
|
49
|
+
} catch {
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const q = h.match(/filename="([^"]+)"/);
|
|
54
|
+
return q ? q[1] : void 0;
|
|
55
|
+
}
|
|
56
|
+
function parseSize(headers) {
|
|
57
|
+
const cl = headers.get("Content-Length");
|
|
58
|
+
if (cl != null) {
|
|
59
|
+
const n = parseInt(cl, 10);
|
|
60
|
+
if (!Number.isNaN(n)) return n;
|
|
61
|
+
}
|
|
62
|
+
const cr = headers.get("Content-Range")?.match(/\/\s*(\d+)/);
|
|
63
|
+
return cr ? parseInt(cr[1], 10) : void 0;
|
|
64
|
+
}
|
|
65
|
+
async function getMetadataFromUrl(url, signal) {
|
|
66
|
+
const res = await fetch(url, { method: "HEAD", redirect: "follow", signal });
|
|
67
|
+
const headers = res.ok ? res.headers : res.status === 405 ? (await fetch(url, { method: "GET", redirect: "follow", signal, headers: { Range: "bytes=0-0" } })).headers : new Headers();
|
|
68
|
+
const cd = headers.get("Content-Disposition");
|
|
69
|
+
const lm = headers.get("Last-Modified");
|
|
70
|
+
const ct = headers.get("Content-Type");
|
|
71
|
+
let lastModified;
|
|
72
|
+
if (lm) {
|
|
73
|
+
const d = new Date(lm);
|
|
74
|
+
if (!Number.isNaN(d.getTime())) lastModified = d;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
filename: parseFilename(cd),
|
|
78
|
+
size: parseSize(headers),
|
|
79
|
+
mimeType: ct ? ct.split(";")[0].trim() || void 0 : void 0,
|
|
80
|
+
lastModified
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/list.ts
|
|
85
|
+
var FOLDER_URL = "https://drive.google.com/drive/folders/";
|
|
86
|
+
var FOLDER_ID_REGEX = /\/folders\/([a-zA-Z0-9_-]+)/;
|
|
87
|
+
var SSK_FILE_ID = /ssk='[^']*:([a-zA-Z0-9_-]+)-0-1'/g;
|
|
88
|
+
var TR_ID_NAME = /<tr[^>]*\sdata-id="([a-zA-Z0-9_-]+)"[^>]*>[\s\S]*?(?:data-tooltip="([^"]+\.(?:csv|txt|pdf|zip|json))"|<strong[^>]*>([^<]+\.(?:csv|txt|pdf|zip|json))<\/strong>)/gi;
|
|
89
|
+
function toFolderId(input) {
|
|
90
|
+
const m = input.match(FOLDER_ID_REGEX);
|
|
91
|
+
return m ? m[1] : input;
|
|
92
|
+
}
|
|
93
|
+
async function listFiles(folderLinkOrId, signal) {
|
|
94
|
+
const folderId = toFolderId(folderLinkOrId);
|
|
95
|
+
const html = await (await fetch(`${FOLDER_URL}${encodeURIComponent(folderId)}`, { signal })).text();
|
|
96
|
+
const idToName = /* @__PURE__ */ new Map();
|
|
97
|
+
let tr;
|
|
98
|
+
TR_ID_NAME.lastIndex = 0;
|
|
99
|
+
while ((tr = TR_ID_NAME.exec(html)) !== null) {
|
|
100
|
+
const id = tr[1];
|
|
101
|
+
const name = tr[2] || tr[3]?.trim();
|
|
102
|
+
if (name && !idToName.has(id)) idToName.set(id, name);
|
|
103
|
+
}
|
|
104
|
+
const seen = /* @__PURE__ */ new Set();
|
|
105
|
+
const out = [];
|
|
106
|
+
let m;
|
|
107
|
+
SSK_FILE_ID.lastIndex = 0;
|
|
108
|
+
while ((m = SSK_FILE_ID.exec(html)) !== null) {
|
|
109
|
+
if (!seen.has(m[1])) {
|
|
110
|
+
seen.add(m[1]);
|
|
111
|
+
out.push({ id: m[1], name: idToName.get(m[1]) });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/index.ts
|
|
118
|
+
async function listFiles2(folderId, options) {
|
|
119
|
+
return listFiles(folderId, options?.signal);
|
|
120
|
+
}
|
|
121
|
+
async function getFile(fileId, format, options) {
|
|
122
|
+
const url = await getDownloadUrl(fileId, options?.signal);
|
|
123
|
+
const res = await fetch(url, { signal: options?.signal });
|
|
124
|
+
if (!res.ok) throw new Error(`Fetch failed: ${res.status}`);
|
|
125
|
+
if (format === "buffer") return Buffer.from(await res.arrayBuffer());
|
|
126
|
+
return res.blob();
|
|
127
|
+
}
|
|
128
|
+
async function getFileMetadata(fileId, options) {
|
|
129
|
+
const url = await getDownloadUrl(fileId, options?.signal);
|
|
130
|
+
return getMetadataFromUrl(url, options?.signal);
|
|
131
|
+
}
|
|
132
|
+
async function downloadFile(fileId, path, options) {
|
|
133
|
+
const url = await getDownloadUrl(fileId, options?.signal);
|
|
134
|
+
const res = await fetch(url, { signal: options?.signal });
|
|
135
|
+
if (!res.ok) throw new Error(`Fetch failed: ${res.status}`);
|
|
136
|
+
if (res.body == null) throw new Error("Response has no body");
|
|
137
|
+
const { Readable } = await import("stream");
|
|
138
|
+
const { createWriteStream } = await import("fs");
|
|
139
|
+
const { pipeline } = await import("stream/promises");
|
|
140
|
+
const nodeReadable = Readable.fromWeb(res.body);
|
|
141
|
+
const dest = createWriteStream(path);
|
|
142
|
+
await pipeline(nodeReadable, dest);
|
|
143
|
+
}
|
|
144
|
+
export {
|
|
145
|
+
GDriveDownloadError,
|
|
146
|
+
downloadFile,
|
|
147
|
+
getFile,
|
|
148
|
+
getFileMetadata,
|
|
149
|
+
listFiles2 as listFiles,
|
|
150
|
+
toFileId
|
|
151
|
+
};
|
|
152
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/resolve.ts","../src/metadata.ts","../src/list.ts","../src/index.ts"],"sourcesContent":["/**\r\n * Error thrown when a Google Drive download fails (wrong status, no link, etc.).\r\n */\r\nexport class GDriveDownloadError extends Error {\r\n readonly statusCode: number | undefined;\r\n readonly fileId: string | undefined;\r\n\r\n constructor(\r\n message: string,\r\n options?: { cause?: unknown; statusCode?: number; fileId?: string }\r\n ) {\r\n super(message);\r\n this.name = \"GDriveDownloadError\";\r\n this.statusCode = options?.statusCode;\r\n this.fileId = options?.fileId;\r\n if (options?.cause !== undefined) {\r\n this.cause = options.cause;\r\n }\r\n Object.setPrototypeOf(this, GDriveDownloadError.prototype);\r\n }\r\n}\r\n","import { GDriveDownloadError } from \"./errors.js\";\r\n\r\nconst FILE_ID_REGEX = /\\/d\\/([a-zA-Z0-9_-]+)(?:\\/|$)/;\r\n\r\n/** Normalizes share link to file ID, or returns input if already an ID. */\r\nexport function toFileId(input: string): string {\r\n const m = input.match(FILE_ID_REGEX);\r\n return m ? m[1] : input;\r\n}\r\n\r\n/** Resolves direct download URL for a public file by ID. */\r\nexport async function getDownloadUrl(\r\n fileId: string,\r\n signal?: AbortSignal\r\n): Promise<string> {\r\n const id = toFileId(fileId);\r\n const res = await fetch(\r\n `https://drive.google.com/uc?id=${encodeURIComponent(id)}&export=download`,\r\n { redirect: \"manual\", signal }\r\n );\r\n if (res.status === 303) {\r\n const loc = res.headers.get(\"Location\");\r\n if (loc) return loc;\r\n throw new GDriveDownloadError(\"303 without Location\", { statusCode: 303, fileId: id });\r\n }\r\n if (res.status === 200) {\r\n const action = (await res.text()).match(/action=\"([^\"]+)\"/)?.[1];\r\n if (action) return action;\r\n throw new GDriveDownloadError(\"200 without form action\", { statusCode: 200, fileId: id });\r\n }\r\n throw new GDriveDownloadError(`Unexpected status ${res.status}`, { statusCode: res.status, fileId: id });\r\n}\r\n","export interface FileMetadata {\r\n filename?: string;\r\n size?: number;\r\n mimeType?: string;\r\n lastModified?: Date;\r\n}\r\n\r\nfunction parseFilename(h: string | null): string | undefined {\r\n if (!h) return undefined;\r\n const u = h.match(/filename\\*=UTF-8''([^;]+)/i);\r\n if (u) {\r\n try { return decodeURIComponent(u[1].trim()); } catch { return undefined; }\r\n }\r\n const q = h.match(/filename=\"([^\"]+)\"/);\r\n return q ? q[1] : undefined;\r\n}\r\n\r\nfunction parseSize(headers: Headers): number | undefined {\r\n const cl = headers.get(\"Content-Length\");\r\n if (cl != null) { const n = parseInt(cl, 10); if (!Number.isNaN(n)) return n; }\r\n const cr = headers.get(\"Content-Range\")?.match(/\\/\\s*(\\d+)/);\r\n return cr ? parseInt(cr[1], 10) : undefined;\r\n}\r\n\r\nexport async function getMetadataFromUrl(\r\n url: string,\r\n signal?: AbortSignal\r\n): Promise<FileMetadata> {\r\n const res = await fetch(url, { method: \"HEAD\", redirect: \"follow\", signal });\r\n const headers = res.ok ? res.headers : (res.status === 405\r\n ? (await fetch(url, { method: \"GET\", redirect: \"follow\", signal, headers: { Range: \"bytes=0-0\" } })).headers\r\n : new Headers());\r\n const cd = headers.get(\"Content-Disposition\");\r\n const lm = headers.get(\"Last-Modified\");\r\n const ct = headers.get(\"Content-Type\");\r\n let lastModified: Date | undefined;\r\n if (lm) { const d = new Date(lm); if (!Number.isNaN(d.getTime())) lastModified = d; }\r\n return {\r\n filename: parseFilename(cd),\r\n size: parseSize(headers),\r\n mimeType: ct ? ct.split(\";\")[0].trim() || undefined : undefined,\r\n lastModified,\r\n };\r\n}\r\n","const FOLDER_URL = \"https://drive.google.com/drive/folders/\";\r\nconst FOLDER_ID_REGEX = /\\/folders\\/([a-zA-Z0-9_-]+)/;\r\nconst SSK_FILE_ID = /ssk='[^']*:([a-zA-Z0-9_-]+)-0-1'/g;\r\n/** Row with data-id and filename in data-tooltip or <strong>. */\r\nconst TR_ID_NAME = /<tr[^>]*\\sdata-id=\"([a-zA-Z0-9_-]+)\"[^>]*>[\\s\\S]*?(?:data-tooltip=\"([^\"]+\\.(?:csv|txt|pdf|zip|json))\"|<strong[^>]*>([^<]+\\.(?:csv|txt|pdf|zip|json))<\\/strong>)/gi;\r\n\r\nexport interface ListFileEntry {\r\n id: string;\r\n name?: string;\r\n}\r\n\r\nfunction toFolderId(input: string): string {\r\n const m = input.match(FOLDER_ID_REGEX);\r\n return m ? m[1] : input;\r\n}\r\n\r\nexport async function listFiles(\r\n folderLinkOrId: string,\r\n signal?: AbortSignal\r\n): Promise<ListFileEntry[]> {\r\n const folderId = toFolderId(folderLinkOrId);\r\n const html = await (await fetch(`${FOLDER_URL}${encodeURIComponent(folderId)}`, { signal })).text();\r\n const idToName = new Map<string, string>();\r\n let tr: RegExpExecArray | null;\r\n TR_ID_NAME.lastIndex = 0;\r\n while ((tr = TR_ID_NAME.exec(html)) !== null) {\r\n const id = tr[1];\r\n const name = tr[2] || tr[3]?.trim();\r\n if (name && !idToName.has(id)) idToName.set(id, name);\r\n }\r\n const seen = new Set<string>();\r\n const out: ListFileEntry[] = [];\r\n let m: RegExpExecArray | null;\r\n SSK_FILE_ID.lastIndex = 0;\r\n while ((m = SSK_FILE_ID.exec(html)) !== null) {\r\n if (!seen.has(m[1])) {\r\n seen.add(m[1]);\r\n out.push({ id: m[1], name: idToName.get(m[1]) });\r\n }\r\n }\r\n return out;\r\n}\r\n","export { GDriveDownloadError } from \"./errors.js\";\r\nexport { toFileId } from \"./resolve.js\";\r\nexport type { FileMetadata } from \"./metadata.js\";\r\nexport type { ListFileEntry } from \"./list.js\";\r\n\r\nimport { getDownloadUrl } from \"./resolve.js\";\r\nimport { getMetadataFromUrl } from \"./metadata.js\";\r\nimport { listFiles as listFilesInFolder } from \"./list.js\";\r\n\r\nexport interface CommonOptions {\r\n signal?: AbortSignal;\r\n}\r\n\r\nexport async function listFiles(\r\n folderId: string,\r\n options?: CommonOptions\r\n): Promise<import(\"./list.js\").ListFileEntry[]> {\r\n return listFilesInFolder(folderId, options?.signal);\r\n}\r\n\r\nexport async function getFile(\r\n fileId: string,\r\n format: \"blob\" | \"buffer\",\r\n options?: CommonOptions\r\n): Promise<Blob | Buffer> {\r\n const url = await getDownloadUrl(fileId, options?.signal);\r\n const res = await fetch(url, { signal: options?.signal });\r\n if (!res.ok) throw new Error(`Fetch failed: ${res.status}`);\r\n if (format === \"buffer\") return Buffer.from(await res.arrayBuffer());\r\n return res.blob();\r\n}\r\n\r\nexport async function getFileMetadata(\r\n fileId: string,\r\n options?: CommonOptions\r\n): Promise<import(\"./metadata.js\").FileMetadata> {\r\n const url = await getDownloadUrl(fileId, options?.signal);\r\n return getMetadataFromUrl(url, options?.signal);\r\n}\r\n\r\nexport async function downloadFile(\r\n fileId: string,\r\n path: string,\r\n options?: CommonOptions\r\n): Promise<void> {\r\n const url = await getDownloadUrl(fileId, options?.signal);\r\n const res = await fetch(url, { signal: options?.signal });\r\n if (!res.ok) throw new Error(`Fetch failed: ${res.status}`);\r\n if (res.body == null) throw new Error(\"Response has no body\");\r\n const { Readable } = await import(\"node:stream\");\r\n const { createWriteStream } = await import(\"node:fs\");\r\n const { pipeline } = await import(\"node:stream/promises\");\r\n const nodeReadable = Readable.fromWeb(res.body as Parameters<typeof Readable.fromWeb>[0]);\r\n const dest = createWriteStream(path);\r\n await pipeline(nodeReadable, dest);\r\n}\r\n"],"mappings":"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","names":["listFiles"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "better-gdrive",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Download files from public Google Drive. Get direct download URLs or fetch as Blob/Buffer. No OAuth required.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"google-drive",
|
|
7
|
+
"download",
|
|
8
|
+
"drive",
|
|
9
|
+
"public",
|
|
10
|
+
"file",
|
|
11
|
+
"blob",
|
|
12
|
+
"fetch",
|
|
13
|
+
"no-oauth"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/Sn4tchZ/better-gdrive.git"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/Sn4tchZ/better-gdrive/issues"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/Sn4tchZ/better-gdrive#readme",
|
|
24
|
+
"author": "Sn4tchZ",
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"main": "./dist/index.js",
|
|
29
|
+
"module": "./dist/index.mjs",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"import": {
|
|
34
|
+
"types": "./dist/index.d.mts",
|
|
35
|
+
"default": "./dist/index.mjs"
|
|
36
|
+
},
|
|
37
|
+
"require": {
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"default": "./dist/index.js"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsup",
|
|
45
|
+
"dev": "tsup --watch",
|
|
46
|
+
"test:behavior": "node scripts/test-gdrive-behavior.mjs",
|
|
47
|
+
"probe": "node scripts/fetch-probe.mjs"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=18"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/node": "^22.10.1",
|
|
54
|
+
"tsup": "^8.3.5",
|
|
55
|
+
"typescript": "^5.7.2"
|
|
56
|
+
}
|
|
57
|
+
}
|