Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:35:59 +08:00
commit 90883a4d25
287 changed files with 75058 additions and 0 deletions

View File

@@ -0,0 +1,140 @@
#!/usr/bin/env node
/**
* Create Drive File (Google Docs, Sheets, Slides)
*
* Usage: node create_file.js <account> --name <name> --type <type> [options]
*
* Options:
* --name File name (required)
* --type File type: doc, sheet, slide (required)
* --content Initial content (for docs)
* --folder Parent folder ID
*
* Examples:
* node create_file.js psd --name "Meeting Notes" --type doc
* node create_file.js personal --name "Budget 2024" --type sheet
* node create_file.js consulting --name "Proposal" --type slide --folder 1abc123
*/
const { google } = require('googleapis');
const path = require('path');
const { getAuthClient } = require(path.join(__dirname, '..', 'auth', 'token_manager'));
const MIME_TYPES = {
doc: 'application/vnd.google-apps.document',
sheet: 'application/vnd.google-apps.spreadsheet',
slide: 'application/vnd.google-apps.presentation',
};
async function createFile(account, options) {
const auth = await getAuthClient(account);
const drive = google.drive({ version: 'v3', auth });
const mimeType = MIME_TYPES[options.type];
if (!mimeType) {
throw new Error(`Invalid type: ${options.type}. Use: doc, sheet, slide`);
}
const fileMetadata = {
name: options.name,
mimeType,
};
if (options.folder) {
fileMetadata.parents = [options.folder];
}
const response = await drive.files.create({
requestBody: fileMetadata,
fields: 'id, name, mimeType, webViewLink',
});
const file = response.data;
// If content provided for doc, update it
if (options.content && options.type === 'doc') {
const docs = google.docs({ version: 'v1', auth });
await docs.documents.batchUpdate({
documentId: file.id,
requestBody: {
requests: [{
insertText: {
location: { index: 1 },
text: options.content,
}
}]
}
});
}
return {
success: true,
account,
file: {
id: file.id,
name: file.name,
mimeType: file.mimeType,
webLink: file.webViewLink,
},
metadata: {
timestamp: new Date().toISOString(),
}
};
}
// CLI interface
async function main() {
const args = process.argv.slice(2);
const account = args[0];
if (!account) {
console.error(JSON.stringify({
error: 'Missing account',
usage: 'node create_file.js <account> --name <name> --type doc|sheet|slide [options]'
}));
process.exit(1);
}
// Parse options
const options = {};
for (let i = 1; i < args.length; i++) {
switch (args[i]) {
case '--name':
options.name = args[++i];
break;
case '--type':
options.type = args[++i];
break;
case '--content':
options.content = args[++i];
break;
case '--folder':
options.folder = args[++i];
break;
}
}
if (!options.name || !options.type) {
console.error(JSON.stringify({
error: 'Missing required options: --name, --type',
usage: 'node create_file.js <account> --name <name> --type doc|sheet|slide'
}));
process.exit(1);
}
try {
const result = await createFile(account, options);
console.log(JSON.stringify(result, null, 2));
} catch (error) {
console.error(JSON.stringify({
error: error.message,
account,
}));
process.exit(1);
}
}
main();
module.exports = { createFile };

View File

@@ -0,0 +1,92 @@
#!/usr/bin/env node
/**
* Download Drive File
*
* Downloads a file from Google Drive to local filesystem.
*
* Usage: bun download_file.js <account> <fileId> <outputPath>
*/
const { google } = require('googleapis');
const fs = require('fs');
const path = require('path');
const { getAuthClient } = require(path.join(__dirname, '..', 'auth', 'token_manager'));
async function downloadFile(account, fileId, outputPath) {
const auth = await getAuthClient(account);
const drive = google.drive({ version: 'v3', auth });
// Get file metadata first
const fileInfo = await drive.files.get({
fileId,
fields: 'name, mimeType',
supportsAllDrives: true
});
// Download file content
const response = await drive.files.get(
{ fileId, alt: 'media', supportsAllDrives: true },
{ responseType: 'stream' }
);
// Ensure output directory exists
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Write to file
const dest = fs.createWriteStream(outputPath);
return new Promise((resolve, reject) => {
response.data
.on('end', () => {
resolve({
success: true,
account,
file: {
id: fileId,
name: fileInfo.data.name,
mimeType: fileInfo.data.mimeType,
outputPath
}
});
})
.on('error', err => {
reject(err);
})
.pipe(dest);
});
}
async function main() {
const args = process.argv.slice(2);
const account = args[0];
const fileId = args[1];
const outputPath = args[2];
if (!account || !fileId || !outputPath) {
console.error(JSON.stringify({
error: 'Missing arguments',
usage: 'bun download_file.js <account> <fileId> <outputPath>'
}));
process.exit(1);
}
try {
const result = await downloadFile(account, fileId, outputPath);
console.log(JSON.stringify(result, null, 2));
} catch (error) {
console.error(JSON.stringify({
error: error.message,
account,
fileId
}));
process.exit(1);
}
}
main();
module.exports = { downloadFile };

View File

@@ -0,0 +1,133 @@
#!/usr/bin/env node
/**
* List Drive Files
*
* Usage: node list_files.js <account> [options]
*
* Options:
* --query Search query (Drive search syntax)
* --type File type: doc, sheet, slide, pdf, folder
* --max Maximum files to return (default: 20)
* --folder Folder ID to list contents of
*
* Examples:
* node list_files.js psd
* node list_files.js psd --query "budget"
* node list_files.js personal --type sheet
* node list_files.js consulting --type pdf --query "contract"
*/
const { google } = require('googleapis');
const path = require('path');
const { getAuthClient } = require(path.join(__dirname, '..', 'auth', 'token_manager'));
const MIME_TYPES = {
doc: 'application/vnd.google-apps.document',
sheet: 'application/vnd.google-apps.spreadsheet',
slide: 'application/vnd.google-apps.presentation',
pdf: 'application/pdf',
folder: 'application/vnd.google-apps.folder',
};
async function listFiles(account, options = {}) {
const auth = await getAuthClient(account);
const drive = google.drive({ version: 'v3', auth });
// Build query
const queryParts = [];
if (options.query) {
queryParts.push(`fullText contains '${options.query}'`);
}
if (options.type && MIME_TYPES[options.type]) {
queryParts.push(`mimeType = '${MIME_TYPES[options.type]}'`);
}
if (options.folder) {
queryParts.push(`'${options.folder}' in parents`);
}
// Exclude trashed files
queryParts.push('trashed = false');
const response = await drive.files.list({
q: queryParts.join(' and '),
pageSize: options.max || 20,
fields: 'files(id, name, mimeType, size, createdTime, modifiedTime, webViewLink, parents)',
orderBy: 'modifiedTime desc',
supportsAllDrives: true,
includeItemsFromAllDrives: true,
});
const files = (response.data.files || []).map(file => ({
id: file.id,
name: file.name,
mimeType: file.mimeType,
size: file.size,
created: file.createdTime,
modified: file.modifiedTime,
webLink: file.webViewLink,
parents: file.parents,
}));
return {
success: true,
account,
files,
metadata: {
timestamp: new Date().toISOString(),
count: files.length,
query: options.query || '(all)',
}
};
}
// CLI interface
async function main() {
const args = process.argv.slice(2);
const account = args[0];
if (!account) {
console.error(JSON.stringify({
error: 'Missing account',
usage: 'node list_files.js <account> [--query "..."] [--type doc|sheet|slide|pdf|folder] [--max N]'
}));
process.exit(1);
}
// Parse options
const options = {};
for (let i = 1; i < args.length; i++) {
switch (args[i]) {
case '--query':
options.query = args[++i];
break;
case '--type':
options.type = args[++i];
break;
case '--max':
options.max = parseInt(args[++i], 10);
break;
case '--folder':
options.folder = args[++i];
break;
}
}
try {
const result = await listFiles(account, options);
console.log(JSON.stringify(result, null, 2));
} catch (error) {
console.error(JSON.stringify({
error: error.message,
account,
}));
process.exit(1);
}
}
main();
module.exports = { listFiles };

View File

@@ -0,0 +1,130 @@
#!/usr/bin/env node
/**
* Read Drive File Content
*
* Usage: node read_file.js <account> <file-id> [options]
*
* Options:
* --format Export format for Google Docs (text, html, pdf)
*
* Examples:
* node read_file.js psd 1abc123def456
* node read_file.js personal 1xyz789 --format html
*/
const { google } = require('googleapis');
const path = require('path');
const { getAuthClient } = require(path.join(__dirname, '..', 'auth', 'token_manager'));
const EXPORT_FORMATS = {
'application/vnd.google-apps.document': {
text: 'text/plain',
html: 'text/html',
pdf: 'application/pdf',
},
'application/vnd.google-apps.spreadsheet': {
csv: 'text/csv',
pdf: 'application/pdf',
},
'application/vnd.google-apps.presentation': {
text: 'text/plain',
pdf: 'application/pdf',
},
};
async function readFile(account, fileId, options = {}) {
const auth = await getAuthClient(account);
const drive = google.drive({ version: 'v3', auth });
// Get file metadata
const metadata = await drive.files.get({
fileId,
fields: 'id, name, mimeType, size, webViewLink',
supportsAllDrives: true,
});
const file = metadata.data;
let content = '';
// Check if it's a Google Workspace file (needs export)
if (file.mimeType.startsWith('application/vnd.google-apps.')) {
const exportFormats = EXPORT_FORMATS[file.mimeType];
if (exportFormats) {
const format = options.format || 'text';
const mimeType = exportFormats[format] || exportFormats.text || Object.values(exportFormats)[0];
const response = await drive.files.export({
fileId,
mimeType,
}, { responseType: 'text' });
content = response.data;
} else {
content = '[Cannot export this file type]';
}
} else {
// Regular file - download content
const response = await drive.files.get({
fileId,
alt: 'media',
supportsAllDrives: true,
}, { responseType: 'text' });
content = response.data;
}
return {
success: true,
account,
file: {
id: file.id,
name: file.name,
mimeType: file.mimeType,
webLink: file.webViewLink,
content,
},
metadata: {
timestamp: new Date().toISOString(),
}
};
}
// CLI interface
async function main() {
const args = process.argv.slice(2);
const account = args[0];
const fileId = args[1];
if (!account || !fileId) {
console.error(JSON.stringify({
error: 'Missing arguments',
usage: 'node read_file.js <account> <file-id> [--format text|html|pdf]'
}));
process.exit(1);
}
// Parse options
const options = {};
for (let i = 2; i < args.length; i++) {
if (args[i] === '--format') {
options.format = args[++i];
}
}
try {
const result = await readFile(account, fileId, options);
console.log(JSON.stringify(result, null, 2));
} catch (error) {
console.error(JSON.stringify({
error: error.message,
account,
fileId,
}));
process.exit(1);
}
}
main();
module.exports = { readFile };