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,84 @@
#!/usr/bin/env node
/**
* Create Google Slides Presentation
*
* Usage: node create_presentation.js <account> --title <title>
*
* Examples:
* node create_presentation.js psd --title "Q4 Review"
*/
const { google } = require('googleapis');
const path = require('path');
const { getAuthClient } = require(path.join(__dirname, '..', 'auth', 'token_manager'));
async function createPresentation(account, options) {
const auth = await getAuthClient(account);
const slides = google.slides({ version: 'v1', auth });
const response = await slides.presentations.create({
requestBody: {
title: options.title,
},
});
return {
success: true,
account,
presentation: {
id: response.data.presentationId,
title: response.data.title,
url: `https://docs.google.com/presentation/d/${response.data.presentationId}/edit`,
slideCount: (response.data.slides || []).length,
},
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_presentation.js <account> --title <title>'
}));
process.exit(1);
}
// Parse options
const options = {};
for (let i = 1; i < args.length; i++) {
if (args[i] === '--title') {
options.title = args[++i];
}
}
if (!options.title) {
console.error(JSON.stringify({
error: 'Missing --title option',
usage: 'node create_presentation.js <account> --title <title>'
}));
process.exit(1);
}
try {
const result = await createPresentation(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 = { createPresentation };

View File

@@ -0,0 +1,174 @@
#!/usr/bin/env node
/**
* Edit Google Slides Presentation
*
* Usage: node edit_presentation.js <account> <presentation-id> <action> [options]
*
* Actions:
* --add-slide Add a blank slide
* --add-text-slide Add slide with title and body
* --replace-text Replace text across presentation
*
* Examples:
* node edit_presentation.js psd PRES_ID --add-slide
* node edit_presentation.js psd PRES_ID --add-text-slide --title "Agenda" --body "Item 1\nItem 2"
* node edit_presentation.js psd PRES_ID --replace-text --find "2024" --replace "2025"
*/
const { google } = require('googleapis');
const path = require('path');
const { getAuthClient } = require(path.join(__dirname, '..', 'auth', 'token_manager'));
async function editPresentation(account, presentationId, options) {
const auth = await getAuthClient(account);
const slides = google.slides({ version: 'v1', auth });
const requests = [];
if (options.addSlide) {
requests.push({
createSlide: {
slideLayoutReference: {
predefinedLayout: 'BLANK',
},
},
});
}
if (options.addTextSlide) {
const slideId = `slide_${Date.now()}`;
const titleId = `title_${Date.now()}`;
const bodyId = `body_${Date.now()}`;
requests.push({
createSlide: {
objectId: slideId,
slideLayoutReference: {
predefinedLayout: 'TITLE_AND_BODY',
},
placeholderIdMappings: [
{
layoutPlaceholder: { type: 'TITLE' },
objectId: titleId,
},
{
layoutPlaceholder: { type: 'BODY' },
objectId: bodyId,
},
],
},
});
if (options.title) {
requests.push({
insertText: {
objectId: titleId,
text: options.title,
},
});
}
if (options.body) {
requests.push({
insertText: {
objectId: bodyId,
text: options.body,
},
});
}
}
if (options.replaceText && options.find && options.replace) {
requests.push({
replaceAllText: {
containsText: {
text: options.find,
matchCase: true,
},
replaceText: options.replace,
},
});
}
if (requests.length === 0) {
return {
success: false,
error: 'No edit operation specified',
};
}
const response = await slides.presentations.batchUpdate({
presentationId,
requestBody: { requests },
});
return {
success: true,
account,
presentationId,
updates: response.data.replies.length,
metadata: {
timestamp: new Date().toISOString(),
}
};
}
// CLI interface
async function main() {
const args = process.argv.slice(2);
const account = args[0];
const presentationId = args[1];
if (!account || !presentationId) {
console.error(JSON.stringify({
error: 'Missing arguments',
usage: 'node edit_presentation.js <account> <presentation-id> --add-slide'
}));
process.exit(1);
}
// Parse options
const options = {};
for (let i = 2; i < args.length; i++) {
switch (args[i]) {
case '--add-slide':
options.addSlide = true;
break;
case '--add-text-slide':
options.addTextSlide = true;
break;
case '--replace-text':
options.replaceText = true;
break;
case '--title':
options.title = args[++i];
break;
case '--body':
options.body = args[++i];
break;
case '--find':
options.find = args[++i];
break;
case '--replace':
options.replace = args[++i];
break;
}
}
try {
const result = await editPresentation(account, presentationId, options);
console.log(JSON.stringify(result, null, 2));
} catch (error) {
console.error(JSON.stringify({
error: error.message,
account,
presentationId,
}));
process.exit(1);
}
}
main();
module.exports = { editPresentation };

View File

@@ -0,0 +1,93 @@
#!/usr/bin/env node
/**
* Read Google Slides Presentation
*
* Usage: node read_presentation.js <account> <presentation-id>
*
* Examples:
* node read_presentation.js psd 1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms
*/
const { google } = require('googleapis');
const path = require('path');
const { getAuthClient } = require(path.join(__dirname, '..', 'auth', 'token_manager'));
async function readPresentation(account, presentationId) {
const auth = await getAuthClient(account);
const slides = google.slides({ version: 'v1', auth });
const response = await slides.presentations.get({
presentationId,
});
const presentation = response.data;
// Extract slide content
const slideContent = (presentation.slides || []).map((slide, index) => {
const texts = [];
// Extract text from page elements
for (const element of (slide.pageElements || [])) {
if (element.shape && element.shape.text) {
for (const textElement of (element.shape.text.textElements || [])) {
if (textElement.textRun && textElement.textRun.content) {
const text = textElement.textRun.content.trim();
if (text) texts.push(text);
}
}
}
}
return {
slideNumber: index + 1,
objectId: slide.objectId,
texts,
};
});
return {
success: true,
account,
presentation: {
id: presentation.presentationId,
title: presentation.title,
slideCount: (presentation.slides || []).length,
slides: slideContent,
},
metadata: {
timestamp: new Date().toISOString(),
}
};
}
// CLI interface
async function main() {
const args = process.argv.slice(2);
const account = args[0];
const presentationId = args[1];
if (!account || !presentationId) {
console.error(JSON.stringify({
error: 'Missing arguments',
usage: 'node read_presentation.js <account> <presentation-id>'
}));
process.exit(1);
}
try {
const result = await readPresentation(account, presentationId);
console.log(JSON.stringify(result, null, 2));
} catch (error) {
console.error(JSON.stringify({
error: error.message,
account,
presentationId,
}));
process.exit(1);
}
}
main();
module.exports = { readPresentation };