Feature/Add teams, gmail, outlook tools (#4577)
* add teams, gmail, outlook tools * update docs link * update credentials for oauth2 * add jira tool * add google drive, google calendar, google sheets tools, powerpoint, excel, word doc loader * update jira logo * Refactor Gmail and Outlook tools to remove maxOutputLength parameter and enhance request handling. Update response formatting to include parameters in the output. Adjust Google Drive tools to simplify success messages by removing unnecessary parameter details.
@@ -0,0 +1,698 @@
|
||||
import { convertMultiOptionsToStringArray, getCredentialData, getCredentialParam, refreshOAuth2Token } from '../../../src/utils'
|
||||
import { createGmailTools } from './core'
|
||||
import type { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
|
||||
class Gmail_Tools implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
description: string
|
||||
baseClasses: string[]
|
||||
credential: INodeParams
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Gmail'
|
||||
this.name = 'gmail'
|
||||
this.version = 1.0
|
||||
this.type = 'Gmail'
|
||||
this.icon = 'gmail.svg'
|
||||
this.category = 'Tools'
|
||||
this.description = 'Perform Gmail operations for drafts, messages, labels, and threads'
|
||||
this.baseClasses = [this.type, 'Tool']
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['gmailOAuth2']
|
||||
}
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Type',
|
||||
name: 'gmailType',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
label: 'Drafts',
|
||||
name: 'drafts'
|
||||
},
|
||||
{
|
||||
label: 'Messages',
|
||||
name: 'messages'
|
||||
},
|
||||
{
|
||||
label: 'Labels',
|
||||
name: 'labels'
|
||||
},
|
||||
{
|
||||
label: 'Threads',
|
||||
name: 'threads'
|
||||
}
|
||||
]
|
||||
},
|
||||
// Draft Actions
|
||||
{
|
||||
label: 'Draft Actions',
|
||||
name: 'draftActions',
|
||||
type: 'multiOptions',
|
||||
options: [
|
||||
{
|
||||
label: 'List Drafts',
|
||||
name: 'listDrafts'
|
||||
},
|
||||
{
|
||||
label: 'Create Draft',
|
||||
name: 'createDraft'
|
||||
},
|
||||
{
|
||||
label: 'Get Draft',
|
||||
name: 'getDraft'
|
||||
},
|
||||
{
|
||||
label: 'Update Draft',
|
||||
name: 'updateDraft'
|
||||
},
|
||||
{
|
||||
label: 'Send Draft',
|
||||
name: 'sendDraft'
|
||||
},
|
||||
{
|
||||
label: 'Delete Draft',
|
||||
name: 'deleteDraft'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
gmailType: ['drafts']
|
||||
}
|
||||
},
|
||||
// Message Actions
|
||||
{
|
||||
label: 'Message Actions',
|
||||
name: 'messageActions',
|
||||
type: 'multiOptions',
|
||||
options: [
|
||||
{
|
||||
label: 'List Messages',
|
||||
name: 'listMessages'
|
||||
},
|
||||
{
|
||||
label: 'Get Message',
|
||||
name: 'getMessage'
|
||||
},
|
||||
{
|
||||
label: 'Send Message',
|
||||
name: 'sendMessage'
|
||||
},
|
||||
{
|
||||
label: 'Modify Message',
|
||||
name: 'modifyMessage'
|
||||
},
|
||||
{
|
||||
label: 'Trash Message',
|
||||
name: 'trashMessage'
|
||||
},
|
||||
{
|
||||
label: 'Untrash Message',
|
||||
name: 'untrashMessage'
|
||||
},
|
||||
{
|
||||
label: 'Delete Message',
|
||||
name: 'deleteMessage'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
gmailType: ['messages']
|
||||
}
|
||||
},
|
||||
// Label Actions
|
||||
{
|
||||
label: 'Label Actions',
|
||||
name: 'labelActions',
|
||||
type: 'multiOptions',
|
||||
options: [
|
||||
{
|
||||
label: 'List Labels',
|
||||
name: 'listLabels'
|
||||
},
|
||||
{
|
||||
label: 'Get Label',
|
||||
name: 'getLabel'
|
||||
},
|
||||
{
|
||||
label: 'Create Label',
|
||||
name: 'createLabel'
|
||||
},
|
||||
{
|
||||
label: 'Update Label',
|
||||
name: 'updateLabel'
|
||||
},
|
||||
{
|
||||
label: 'Delete Label',
|
||||
name: 'deleteLabel'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
gmailType: ['labels']
|
||||
}
|
||||
},
|
||||
// Thread Actions
|
||||
{
|
||||
label: 'Thread Actions',
|
||||
name: 'threadActions',
|
||||
type: 'multiOptions',
|
||||
options: [
|
||||
{
|
||||
label: 'List Threads',
|
||||
name: 'listThreads'
|
||||
},
|
||||
{
|
||||
label: 'Get Thread',
|
||||
name: 'getThread'
|
||||
},
|
||||
{
|
||||
label: 'Modify Thread',
|
||||
name: 'modifyThread'
|
||||
},
|
||||
{
|
||||
label: 'Trash Thread',
|
||||
name: 'trashThread'
|
||||
},
|
||||
{
|
||||
label: 'Untrash Thread',
|
||||
name: 'untrashThread'
|
||||
},
|
||||
{
|
||||
label: 'Delete Thread',
|
||||
name: 'deleteThread'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
gmailType: ['threads']
|
||||
}
|
||||
},
|
||||
// DRAFT PARAMETERS
|
||||
// List Drafts Parameters
|
||||
{
|
||||
label: 'Max Results',
|
||||
name: 'draftMaxResults',
|
||||
type: 'number',
|
||||
description: 'Maximum number of drafts to return',
|
||||
default: 100,
|
||||
show: {
|
||||
draftActions: ['listDrafts']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Create Draft Parameters
|
||||
{
|
||||
label: 'To',
|
||||
name: 'draftTo',
|
||||
type: 'string',
|
||||
description: 'Recipient email address(es), comma-separated',
|
||||
placeholder: 'user1@example.com,user2@example.com',
|
||||
show: {
|
||||
draftActions: ['createDraft']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Subject',
|
||||
name: 'draftSubject',
|
||||
type: 'string',
|
||||
description: 'Email subject',
|
||||
placeholder: 'Email Subject',
|
||||
show: {
|
||||
draftActions: ['createDraft']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Body',
|
||||
name: 'draftBody',
|
||||
type: 'string',
|
||||
description: 'Email body content',
|
||||
placeholder: 'Email content',
|
||||
rows: 4,
|
||||
show: {
|
||||
draftActions: ['createDraft']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'CC',
|
||||
name: 'draftCc',
|
||||
type: 'string',
|
||||
description: 'CC email address(es), comma-separated',
|
||||
placeholder: 'cc1@example.com,cc2@example.com',
|
||||
show: {
|
||||
draftActions: ['createDraft']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'BCC',
|
||||
name: 'draftBcc',
|
||||
type: 'string',
|
||||
description: 'BCC email address(es), comma-separated',
|
||||
placeholder: 'bcc1@example.com,bcc2@example.com',
|
||||
show: {
|
||||
draftActions: ['createDraft']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Draft ID for Get/Update/Send/Delete
|
||||
{
|
||||
label: 'Draft ID',
|
||||
name: 'draftId',
|
||||
type: 'string',
|
||||
description: 'ID of the draft',
|
||||
show: {
|
||||
draftActions: ['getDraft', 'updateDraft', 'sendDraft', 'deleteDraft']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Update Draft Parameters
|
||||
{
|
||||
label: 'To (Update)',
|
||||
name: 'draftUpdateTo',
|
||||
type: 'string',
|
||||
description: 'Recipient email address(es), comma-separated',
|
||||
placeholder: 'user1@example.com,user2@example.com',
|
||||
show: {
|
||||
draftActions: ['updateDraft']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Subject (Update)',
|
||||
name: 'draftUpdateSubject',
|
||||
type: 'string',
|
||||
description: 'Email subject',
|
||||
placeholder: 'Email Subject',
|
||||
show: {
|
||||
draftActions: ['updateDraft']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Body (Update)',
|
||||
name: 'draftUpdateBody',
|
||||
type: 'string',
|
||||
description: 'Email body content',
|
||||
placeholder: 'Email content',
|
||||
rows: 4,
|
||||
show: {
|
||||
draftActions: ['updateDraft']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// MESSAGE PARAMETERS
|
||||
// List Messages Parameters
|
||||
{
|
||||
label: 'Max Results',
|
||||
name: 'messageMaxResults',
|
||||
type: 'number',
|
||||
description: 'Maximum number of messages to return',
|
||||
default: 100,
|
||||
show: {
|
||||
messageActions: ['listMessages']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Query',
|
||||
name: 'messageQuery',
|
||||
type: 'string',
|
||||
description: 'Query string for filtering results (Gmail search syntax)',
|
||||
placeholder: 'is:unread from:example@gmail.com',
|
||||
show: {
|
||||
messageActions: ['listMessages']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Send Message Parameters
|
||||
{
|
||||
label: 'To',
|
||||
name: 'messageTo',
|
||||
type: 'string',
|
||||
description: 'Recipient email address(es), comma-separated',
|
||||
placeholder: 'user1@example.com,user2@example.com',
|
||||
show: {
|
||||
messageActions: ['sendMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Subject',
|
||||
name: 'messageSubject',
|
||||
type: 'string',
|
||||
description: 'Email subject',
|
||||
placeholder: 'Email Subject',
|
||||
show: {
|
||||
messageActions: ['sendMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Body',
|
||||
name: 'messageBody',
|
||||
type: 'string',
|
||||
description: 'Email body content',
|
||||
placeholder: 'Email content',
|
||||
rows: 4,
|
||||
show: {
|
||||
messageActions: ['sendMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'CC',
|
||||
name: 'messageCc',
|
||||
type: 'string',
|
||||
description: 'CC email address(es), comma-separated',
|
||||
placeholder: 'cc1@example.com,cc2@example.com',
|
||||
show: {
|
||||
messageActions: ['sendMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'BCC',
|
||||
name: 'messageBcc',
|
||||
type: 'string',
|
||||
description: 'BCC email address(es), comma-separated',
|
||||
placeholder: 'bcc1@example.com,bcc2@example.com',
|
||||
show: {
|
||||
messageActions: ['sendMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Message ID for Get/Modify/Trash/Untrash/Delete
|
||||
{
|
||||
label: 'Message ID',
|
||||
name: 'messageId',
|
||||
type: 'string',
|
||||
description: 'ID of the message',
|
||||
show: {
|
||||
messageActions: ['getMessage', 'modifyMessage', 'trashMessage', 'untrashMessage', 'deleteMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Message Label Modification
|
||||
{
|
||||
label: 'Add Label IDs',
|
||||
name: 'messageAddLabelIds',
|
||||
type: 'string',
|
||||
description: 'Comma-separated label IDs to add',
|
||||
placeholder: 'INBOX,STARRED',
|
||||
show: {
|
||||
messageActions: ['modifyMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Remove Label IDs',
|
||||
name: 'messageRemoveLabelIds',
|
||||
type: 'string',
|
||||
description: 'Comma-separated label IDs to remove',
|
||||
placeholder: 'UNREAD,SPAM',
|
||||
show: {
|
||||
messageActions: ['modifyMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// LABEL PARAMETERS
|
||||
// Create Label Parameters
|
||||
{
|
||||
label: 'Label Name',
|
||||
name: 'labelName',
|
||||
type: 'string',
|
||||
description: 'Name of the label',
|
||||
placeholder: 'Important',
|
||||
show: {
|
||||
labelActions: ['createLabel', 'updateLabel']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Label Color',
|
||||
name: 'labelColor',
|
||||
type: 'string',
|
||||
description: 'Color of the label (hex color code)',
|
||||
placeholder: '#ff0000',
|
||||
show: {
|
||||
labelActions: ['createLabel', 'updateLabel']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Label ID for Get/Update/Delete
|
||||
{
|
||||
label: 'Label ID',
|
||||
name: 'labelId',
|
||||
type: 'string',
|
||||
description: 'ID of the label',
|
||||
show: {
|
||||
labelActions: ['getLabel', 'updateLabel', 'deleteLabel']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// THREAD PARAMETERS
|
||||
// List Threads Parameters
|
||||
{
|
||||
label: 'Max Results',
|
||||
name: 'threadMaxResults',
|
||||
type: 'number',
|
||||
description: 'Maximum number of threads to return',
|
||||
default: 100,
|
||||
show: {
|
||||
threadActions: ['listThreads']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Query',
|
||||
name: 'threadQuery',
|
||||
type: 'string',
|
||||
description: 'Query string for filtering results (Gmail search syntax)',
|
||||
placeholder: 'is:unread from:example@gmail.com',
|
||||
show: {
|
||||
threadActions: ['listThreads']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Thread ID for Get/Modify/Trash/Untrash/Delete
|
||||
{
|
||||
label: 'Thread ID',
|
||||
name: 'threadId',
|
||||
type: 'string',
|
||||
description: 'ID of the thread',
|
||||
show: {
|
||||
threadActions: ['getThread', 'modifyThread', 'trashThread', 'untrashThread', 'deleteThread']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Thread Label Modification
|
||||
{
|
||||
label: 'Add Label IDs',
|
||||
name: 'threadAddLabelIds',
|
||||
type: 'string',
|
||||
description: 'Comma-separated label IDs to add',
|
||||
placeholder: 'INBOX,STARRED',
|
||||
show: {
|
||||
threadActions: ['modifyThread']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Remove Label IDs',
|
||||
name: 'threadRemoveLabelIds',
|
||||
type: 'string',
|
||||
description: 'Comma-separated label IDs to remove',
|
||||
placeholder: 'UNREAD,SPAM',
|
||||
show: {
|
||||
threadActions: ['modifyThread']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
let credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
credentialData = await refreshOAuth2Token(nodeData.credential ?? '', credentialData, options)
|
||||
const accessToken = getCredentialParam('access_token', credentialData, nodeData)
|
||||
|
||||
if (!accessToken) {
|
||||
throw new Error('No access token found in credential')
|
||||
}
|
||||
|
||||
// Get all actions based on type
|
||||
const gmailType = nodeData.inputs?.gmailType as string
|
||||
let actions: string[] = []
|
||||
|
||||
if (gmailType === 'drafts') {
|
||||
actions = convertMultiOptionsToStringArray(nodeData.inputs?.draftActions)
|
||||
} else if (gmailType === 'messages') {
|
||||
actions = convertMultiOptionsToStringArray(nodeData.inputs?.messageActions)
|
||||
} else if (gmailType === 'labels') {
|
||||
actions = convertMultiOptionsToStringArray(nodeData.inputs?.labelActions)
|
||||
} else if (gmailType === 'threads') {
|
||||
actions = convertMultiOptionsToStringArray(nodeData.inputs?.threadActions)
|
||||
}
|
||||
|
||||
// Prepare default parameters for each action
|
||||
const defaultParams: ICommonObject = {}
|
||||
|
||||
// Draft parameters
|
||||
const draftMaxResults = nodeData.inputs?.draftMaxResults
|
||||
const draftTo = nodeData.inputs?.draftTo
|
||||
const draftSubject = nodeData.inputs?.draftSubject
|
||||
const draftBody = nodeData.inputs?.draftBody
|
||||
const draftCc = nodeData.inputs?.draftCc
|
||||
const draftBcc = nodeData.inputs?.draftBcc
|
||||
const draftId = nodeData.inputs?.draftId
|
||||
const draftUpdateTo = nodeData.inputs?.draftUpdateTo
|
||||
const draftUpdateSubject = nodeData.inputs?.draftUpdateSubject
|
||||
const draftUpdateBody = nodeData.inputs?.draftUpdateBody
|
||||
|
||||
// Message parameters
|
||||
const messageMaxResults = nodeData.inputs?.messageMaxResults
|
||||
const messageQuery = nodeData.inputs?.messageQuery
|
||||
const messageTo = nodeData.inputs?.messageTo
|
||||
const messageSubject = nodeData.inputs?.messageSubject
|
||||
const messageBody = nodeData.inputs?.messageBody
|
||||
const messageCc = nodeData.inputs?.messageCc
|
||||
const messageBcc = nodeData.inputs?.messageBcc
|
||||
const messageId = nodeData.inputs?.messageId
|
||||
const messageAddLabelIds = nodeData.inputs?.messageAddLabelIds
|
||||
const messageRemoveLabelIds = nodeData.inputs?.messageRemoveLabelIds
|
||||
|
||||
// Label parameters
|
||||
const labelName = nodeData.inputs?.labelName
|
||||
const labelColor = nodeData.inputs?.labelColor
|
||||
const labelId = nodeData.inputs?.labelId
|
||||
|
||||
// Thread parameters
|
||||
const threadMaxResults = nodeData.inputs?.threadMaxResults
|
||||
const threadQuery = nodeData.inputs?.threadQuery
|
||||
const threadId = nodeData.inputs?.threadId
|
||||
const threadAddLabelIds = nodeData.inputs?.threadAddLabelIds
|
||||
const threadRemoveLabelIds = nodeData.inputs?.threadRemoveLabelIds
|
||||
|
||||
// Set default parameters based on actions
|
||||
actions.forEach((action) => {
|
||||
const params: ICommonObject = {}
|
||||
|
||||
// Draft action parameters
|
||||
if (action.startsWith('list') && draftMaxResults) params.maxResults = draftMaxResults
|
||||
if (action === 'createDraft') {
|
||||
if (draftTo) params.to = draftTo
|
||||
if (draftSubject) params.subject = draftSubject
|
||||
if (draftBody) params.body = draftBody
|
||||
if (draftCc) params.cc = draftCc
|
||||
if (draftBcc) params.bcc = draftBcc
|
||||
}
|
||||
if (action === 'updateDraft') {
|
||||
if (draftId) params.draftId = draftId
|
||||
if (draftUpdateTo) params.to = draftUpdateTo
|
||||
if (draftUpdateSubject) params.subject = draftUpdateSubject
|
||||
if (draftUpdateBody) params.body = draftUpdateBody
|
||||
}
|
||||
if (['getDraft', 'sendDraft', 'deleteDraft'].includes(action) && draftId) {
|
||||
params.draftId = draftId
|
||||
}
|
||||
|
||||
// Message action parameters
|
||||
if (action === 'listMessages') {
|
||||
if (messageMaxResults) params.maxResults = messageMaxResults
|
||||
if (messageQuery) params.query = messageQuery
|
||||
}
|
||||
if (action === 'sendMessage') {
|
||||
if (messageTo) params.to = messageTo
|
||||
if (messageSubject) params.subject = messageSubject
|
||||
if (messageBody) params.body = messageBody
|
||||
if (messageCc) params.cc = messageCc
|
||||
if (messageBcc) params.bcc = messageBcc
|
||||
}
|
||||
if (['getMessage', 'trashMessage', 'untrashMessage', 'deleteMessage'].includes(action) && messageId) {
|
||||
params.messageId = messageId
|
||||
}
|
||||
if (action === 'modifyMessage') {
|
||||
if (messageId) params.messageId = messageId
|
||||
if (messageAddLabelIds) params.addLabelIds = messageAddLabelIds.split(',').map((id: string) => id.trim())
|
||||
if (messageRemoveLabelIds) params.removeLabelIds = messageRemoveLabelIds.split(',').map((id: string) => id.trim())
|
||||
}
|
||||
|
||||
// Label action parameters
|
||||
if (action === 'createLabel') {
|
||||
if (labelName) params.labelName = labelName
|
||||
if (labelColor) params.labelColor = labelColor
|
||||
}
|
||||
if (['getLabel', 'updateLabel', 'deleteLabel'].includes(action) && labelId) {
|
||||
params.labelId = labelId
|
||||
}
|
||||
if (action === 'updateLabel') {
|
||||
if (labelName) params.labelName = labelName
|
||||
if (labelColor) params.labelColor = labelColor
|
||||
}
|
||||
|
||||
// Thread action parameters
|
||||
if (action === 'listThreads') {
|
||||
if (threadMaxResults) params.maxResults = threadMaxResults
|
||||
if (threadQuery) params.query = threadQuery
|
||||
}
|
||||
if (['getThread', 'trashThread', 'untrashThread', 'deleteThread'].includes(action) && threadId) {
|
||||
params.threadId = threadId
|
||||
}
|
||||
if (action === 'modifyThread') {
|
||||
if (threadId) params.threadId = threadId
|
||||
if (threadAddLabelIds) params.addLabelIds = threadAddLabelIds.split(',').map((id: string) => id.trim())
|
||||
if (threadRemoveLabelIds) params.removeLabelIds = threadRemoveLabelIds.split(',').map((id: string) => id.trim())
|
||||
}
|
||||
|
||||
defaultParams[action] = params
|
||||
})
|
||||
|
||||
// Create and return tools based on selected actions
|
||||
const tools = createGmailTools({
|
||||
actions,
|
||||
accessToken,
|
||||
defaultParams
|
||||
})
|
||||
|
||||
return tools
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: Gmail_Tools }
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><path fill="#4caf50" d="M45,16.2l-5,2.75l-5,4.75L35,40h7c1.657,0,3-1.343,3-3V16.2z"/><path fill="#1e88e5" d="M3,16.2l3.614,1.71L13,23.7V40H6c-1.657,0-3-1.343-3-3V16.2z"/><polygon fill="#e53935" points="35,11.2 24,19.45 13,11.2 12,17 13,23.7 24,31.95 35,23.7 36,17"/><path fill="#c62828" d="M3,12.298V16.2l10,7.5V11.2L9.876,8.859C9.132,8.301,8.228,8,7.298,8h0C4.924,8,3,9.924,3,12.298z"/><path fill="#fbc02d" d="M45,12.298V16.2l-10,7.5V11.2l3.124-2.341C38.868,8.301,39.772,8,40.702,8h0 C43.076,8,45,9.924,45,12.298z"/></svg>
|
||||
|
After Width: | Height: | Size: 611 B |
@@ -0,0 +1,655 @@
|
||||
import { convertMultiOptionsToStringArray, getCredentialData, getCredentialParam, refreshOAuth2Token } from '../../../src/utils'
|
||||
import { createGoogleCalendarTools } from './core'
|
||||
import type { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
|
||||
class GoogleCalendar_Tools implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
description: string
|
||||
baseClasses: string[]
|
||||
credential: INodeParams
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Google Calendar'
|
||||
this.name = 'googleCalendarTool'
|
||||
this.version = 1.0
|
||||
this.type = 'GoogleCalendar'
|
||||
this.icon = 'google-calendar.svg'
|
||||
this.category = 'Tools'
|
||||
this.description = 'Perform Google Calendar operations such as managing events, calendars, and checking availability'
|
||||
this.baseClasses = ['Tool']
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['googleCalendarOAuth2']
|
||||
}
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Type',
|
||||
name: 'calendarType',
|
||||
type: 'options',
|
||||
description: 'Type of Google Calendar operation',
|
||||
options: [
|
||||
{
|
||||
label: 'Event',
|
||||
name: 'event'
|
||||
},
|
||||
{
|
||||
label: 'Calendar',
|
||||
name: 'calendar'
|
||||
},
|
||||
{
|
||||
label: 'Freebusy',
|
||||
name: 'freebusy'
|
||||
}
|
||||
]
|
||||
},
|
||||
// Event Actions
|
||||
{
|
||||
label: 'Event Actions',
|
||||
name: 'eventActions',
|
||||
type: 'multiOptions',
|
||||
description: 'Actions to perform',
|
||||
options: [
|
||||
{
|
||||
label: 'List Events',
|
||||
name: 'listEvents'
|
||||
},
|
||||
{
|
||||
label: 'Create Event',
|
||||
name: 'createEvent'
|
||||
},
|
||||
{
|
||||
label: 'Get Event',
|
||||
name: 'getEvent'
|
||||
},
|
||||
{
|
||||
label: 'Update Event',
|
||||
name: 'updateEvent'
|
||||
},
|
||||
{
|
||||
label: 'Delete Event',
|
||||
name: 'deleteEvent'
|
||||
},
|
||||
{
|
||||
label: 'Quick Add Event',
|
||||
name: 'quickAddEvent'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
calendarType: ['event']
|
||||
}
|
||||
},
|
||||
// Calendar Actions
|
||||
{
|
||||
label: 'Calendar Actions',
|
||||
name: 'calendarActions',
|
||||
type: 'multiOptions',
|
||||
description: 'Actions to perform',
|
||||
options: [
|
||||
{
|
||||
label: 'List Calendars',
|
||||
name: 'listCalendars'
|
||||
},
|
||||
{
|
||||
label: 'Create Calendar',
|
||||
name: 'createCalendar'
|
||||
},
|
||||
{
|
||||
label: 'Get Calendar',
|
||||
name: 'getCalendar'
|
||||
},
|
||||
{
|
||||
label: 'Update Calendar',
|
||||
name: 'updateCalendar'
|
||||
},
|
||||
{
|
||||
label: 'Delete Calendar',
|
||||
name: 'deleteCalendar'
|
||||
},
|
||||
{
|
||||
label: 'Clear Calendar',
|
||||
name: 'clearCalendar'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
calendarType: ['calendar']
|
||||
}
|
||||
},
|
||||
// Freebusy Actions
|
||||
{
|
||||
label: 'Freebusy Actions',
|
||||
name: 'freebusyActions',
|
||||
type: 'multiOptions',
|
||||
description: 'Actions to perform',
|
||||
options: [
|
||||
{
|
||||
label: 'Query Freebusy',
|
||||
name: 'queryFreebusy'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
calendarType: ['freebusy']
|
||||
}
|
||||
},
|
||||
// Event Parameters
|
||||
{
|
||||
label: 'Calendar ID',
|
||||
name: 'calendarId',
|
||||
type: 'string',
|
||||
description: 'Calendar ID (use "primary" for primary calendar)',
|
||||
default: 'primary',
|
||||
show: {
|
||||
calendarType: ['event']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Event ID',
|
||||
name: 'eventId',
|
||||
type: 'string',
|
||||
description: 'Event ID for operations on specific events',
|
||||
show: {
|
||||
eventActions: ['getEvent', 'updateEvent', 'deleteEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Summary',
|
||||
name: 'summary',
|
||||
type: 'string',
|
||||
description: 'Event title/summary',
|
||||
show: {
|
||||
eventActions: ['createEvent', 'updateEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Description',
|
||||
name: 'description',
|
||||
type: 'string',
|
||||
description: 'Event description',
|
||||
show: {
|
||||
eventActions: ['createEvent', 'updateEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Location',
|
||||
name: 'location',
|
||||
type: 'string',
|
||||
description: 'Event location',
|
||||
show: {
|
||||
eventActions: ['createEvent', 'updateEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Start Date Time',
|
||||
name: 'startDateTime',
|
||||
type: 'string',
|
||||
description: 'Event start time (ISO 8601 format: 2023-12-25T10:00:00)',
|
||||
show: {
|
||||
eventActions: ['createEvent', 'updateEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'End Date Time',
|
||||
name: 'endDateTime',
|
||||
type: 'string',
|
||||
description: 'Event end time (ISO 8601 format: 2023-12-25T11:00:00)',
|
||||
show: {
|
||||
eventActions: ['createEvent', 'updateEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Time Zone',
|
||||
name: 'timeZone',
|
||||
type: 'string',
|
||||
description: 'Time zone (e.g., America/New_York)',
|
||||
show: {
|
||||
eventActions: ['createEvent', 'updateEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'All Day Event',
|
||||
name: 'allDay',
|
||||
type: 'boolean',
|
||||
description: 'Whether this is an all-day event',
|
||||
show: {
|
||||
eventActions: ['createEvent', 'updateEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Start Date',
|
||||
name: 'startDate',
|
||||
type: 'string',
|
||||
description: 'Start date for all-day events (YYYY-MM-DD format)',
|
||||
show: {
|
||||
eventActions: ['createEvent', 'updateEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'End Date',
|
||||
name: 'endDate',
|
||||
type: 'string',
|
||||
description: 'End date for all-day events (YYYY-MM-DD format)',
|
||||
show: {
|
||||
eventActions: ['createEvent', 'updateEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Attendees',
|
||||
name: 'attendees',
|
||||
type: 'string',
|
||||
description: 'Comma-separated list of attendee emails',
|
||||
show: {
|
||||
eventActions: ['createEvent', 'updateEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Recurrence Rules',
|
||||
name: 'recurrence',
|
||||
type: 'string',
|
||||
description: 'Recurrence rules (RRULE format)',
|
||||
show: {
|
||||
eventActions: ['createEvent', 'updateEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Reminder Minutes',
|
||||
name: 'reminderMinutes',
|
||||
type: 'number',
|
||||
description: 'Minutes before event to send reminder',
|
||||
show: {
|
||||
eventActions: ['createEvent', 'updateEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Visibility',
|
||||
name: 'visibility',
|
||||
type: 'options',
|
||||
description: 'Event visibility',
|
||||
options: [
|
||||
{ label: 'Default', name: 'default' },
|
||||
{ label: 'Public', name: 'public' },
|
||||
{ label: 'Private', name: 'private' },
|
||||
{ label: 'Confidential', name: 'confidential' }
|
||||
],
|
||||
show: {
|
||||
eventActions: ['createEvent', 'updateEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Quick Add Text',
|
||||
name: 'quickAddText',
|
||||
type: 'string',
|
||||
description: 'Natural language text for quick event creation (e.g., "Lunch with John tomorrow at 12pm")',
|
||||
show: {
|
||||
eventActions: ['quickAddEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Time Min',
|
||||
name: 'timeMin',
|
||||
type: 'string',
|
||||
description: 'Lower bound for event search (ISO 8601 format)',
|
||||
show: {
|
||||
eventActions: ['listEvents']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Time Max',
|
||||
name: 'timeMax',
|
||||
type: 'string',
|
||||
description: 'Upper bound for event search (ISO 8601 format)',
|
||||
show: {
|
||||
eventActions: ['listEvents']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Max Results',
|
||||
name: 'maxResults',
|
||||
type: 'number',
|
||||
description: 'Maximum number of events to return',
|
||||
default: 250,
|
||||
show: {
|
||||
eventActions: ['listEvents']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Single Events',
|
||||
name: 'singleEvents',
|
||||
type: 'boolean',
|
||||
description: 'Whether to expand recurring events into instances',
|
||||
default: true,
|
||||
show: {
|
||||
eventActions: ['listEvents']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Order By',
|
||||
name: 'orderBy',
|
||||
type: 'options',
|
||||
description: 'Order of events returned',
|
||||
options: [
|
||||
{ label: 'Start Time', name: 'startTime' },
|
||||
{ label: 'Updated', name: 'updated' }
|
||||
],
|
||||
show: {
|
||||
eventActions: ['listEvents']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
description: 'Free text search terms',
|
||||
show: {
|
||||
eventActions: ['listEvents']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Calendar Parameters
|
||||
{
|
||||
label: 'Calendar ID',
|
||||
name: 'calendarIdForCalendar',
|
||||
type: 'string',
|
||||
description: 'Calendar ID for operations on specific calendars',
|
||||
show: {
|
||||
calendarActions: ['getCalendar', 'updateCalendar', 'deleteCalendar', 'clearCalendar']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Calendar Summary',
|
||||
name: 'calendarSummary',
|
||||
type: 'string',
|
||||
description: 'Calendar title/name',
|
||||
show: {
|
||||
calendarActions: ['createCalendar', 'updateCalendar']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Calendar Description',
|
||||
name: 'calendarDescription',
|
||||
type: 'string',
|
||||
description: 'Calendar description',
|
||||
show: {
|
||||
calendarActions: ['createCalendar', 'updateCalendar']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Calendar Location',
|
||||
name: 'calendarLocation',
|
||||
type: 'string',
|
||||
description: 'Calendar location',
|
||||
show: {
|
||||
calendarActions: ['createCalendar', 'updateCalendar']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Calendar Time Zone',
|
||||
name: 'calendarTimeZone',
|
||||
type: 'string',
|
||||
description: 'Calendar time zone (e.g., America/New_York)',
|
||||
show: {
|
||||
calendarActions: ['createCalendar', 'updateCalendar']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Show Hidden',
|
||||
name: 'showHidden',
|
||||
type: 'boolean',
|
||||
description: 'Whether to show hidden calendars',
|
||||
show: {
|
||||
calendarActions: ['listCalendars']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Min Access Role',
|
||||
name: 'minAccessRole',
|
||||
type: 'options',
|
||||
description: 'Minimum access role for calendar list',
|
||||
options: [
|
||||
{ label: 'Free/Busy Reader', name: 'freeBusyReader' },
|
||||
{ label: 'Reader', name: 'reader' },
|
||||
{ label: 'Writer', name: 'writer' },
|
||||
{ label: 'Owner', name: 'owner' }
|
||||
],
|
||||
show: {
|
||||
calendarActions: ['listCalendars']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Freebusy Parameters
|
||||
{
|
||||
label: 'Time Min',
|
||||
name: 'freebusyTimeMin',
|
||||
type: 'string',
|
||||
description: 'Lower bound for freebusy query (ISO 8601 format)',
|
||||
show: {
|
||||
freebusyActions: ['queryFreebusy']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Time Max',
|
||||
name: 'freebusyTimeMax',
|
||||
type: 'string',
|
||||
description: 'Upper bound for freebusy query (ISO 8601 format)',
|
||||
show: {
|
||||
freebusyActions: ['queryFreebusy']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Calendar IDs',
|
||||
name: 'calendarIds',
|
||||
type: 'string',
|
||||
description: 'Comma-separated list of calendar IDs to check for free/busy info',
|
||||
show: {
|
||||
freebusyActions: ['queryFreebusy']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Group Expansion Max',
|
||||
name: 'groupExpansionMax',
|
||||
type: 'number',
|
||||
description: 'Maximum number of calendars for which FreeBusy information is to be provided',
|
||||
show: {
|
||||
freebusyActions: ['queryFreebusy']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Calendar Expansion Max',
|
||||
name: 'calendarExpansionMax',
|
||||
type: 'number',
|
||||
description: 'Maximum number of events that can be expanded for each calendar',
|
||||
show: {
|
||||
freebusyActions: ['queryFreebusy']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
const calendarType = nodeData.inputs?.calendarType as string
|
||||
|
||||
let credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
credentialData = await refreshOAuth2Token(nodeData.credential ?? '', credentialData, options)
|
||||
const accessToken = getCredentialParam('access_token', credentialData, nodeData)
|
||||
|
||||
if (!accessToken) {
|
||||
throw new Error('No access token found in credential')
|
||||
}
|
||||
|
||||
// Get all actions based on type
|
||||
let actions: string[] = []
|
||||
|
||||
if (calendarType === 'event') {
|
||||
actions = convertMultiOptionsToStringArray(nodeData.inputs?.eventActions)
|
||||
} else if (calendarType === 'calendar') {
|
||||
actions = convertMultiOptionsToStringArray(nodeData.inputs?.calendarActions)
|
||||
} else if (calendarType === 'freebusy') {
|
||||
actions = convertMultiOptionsToStringArray(nodeData.inputs?.freebusyActions)
|
||||
}
|
||||
|
||||
// Create default params object based on inputs
|
||||
const defaultParams: any = {}
|
||||
|
||||
// Event-specific default params
|
||||
if (calendarType === 'event') {
|
||||
actions.forEach((action) => {
|
||||
const params: any = {}
|
||||
|
||||
if (nodeData.inputs?.calendarId) params.calendarId = nodeData.inputs.calendarId
|
||||
if (action === 'getEvent' || action === 'updateEvent' || action === 'deleteEvent') {
|
||||
if (nodeData.inputs?.eventId) params.eventId = nodeData.inputs.eventId
|
||||
}
|
||||
if (action === 'createEvent' || action === 'updateEvent') {
|
||||
if (nodeData.inputs?.summary) params.summary = nodeData.inputs.summary
|
||||
if (nodeData.inputs?.description) params.description = nodeData.inputs.description
|
||||
if (nodeData.inputs?.location) params.location = nodeData.inputs.location
|
||||
if (nodeData.inputs?.startDateTime) params.startDateTime = nodeData.inputs.startDateTime
|
||||
if (nodeData.inputs?.endDateTime) params.endDateTime = nodeData.inputs.endDateTime
|
||||
if (nodeData.inputs?.timeZone) params.timeZone = nodeData.inputs.timeZone
|
||||
if (nodeData.inputs?.allDay !== undefined) params.allDay = nodeData.inputs.allDay
|
||||
if (nodeData.inputs?.startDate) params.startDate = nodeData.inputs.startDate
|
||||
if (nodeData.inputs?.endDate) params.endDate = nodeData.inputs.endDate
|
||||
if (nodeData.inputs?.attendees) params.attendees = nodeData.inputs.attendees
|
||||
if (nodeData.inputs?.recurrence) params.recurrence = nodeData.inputs.recurrence
|
||||
if (nodeData.inputs?.reminderMinutes) params.reminderMinutes = nodeData.inputs.reminderMinutes
|
||||
if (nodeData.inputs?.visibility) params.visibility = nodeData.inputs.visibility
|
||||
}
|
||||
if (action === 'quickAddEvent') {
|
||||
if (nodeData.inputs?.quickAddText) params.quickAddText = nodeData.inputs.quickAddText
|
||||
}
|
||||
if (action === 'listEvents') {
|
||||
if (nodeData.inputs?.timeMin) params.timeMin = nodeData.inputs.timeMin
|
||||
if (nodeData.inputs?.timeMax) params.timeMax = nodeData.inputs.timeMax
|
||||
if (nodeData.inputs?.maxResults) params.maxResults = nodeData.inputs.maxResults
|
||||
if (nodeData.inputs?.singleEvents !== undefined) params.singleEvents = nodeData.inputs.singleEvents
|
||||
if (nodeData.inputs?.orderBy) params.orderBy = nodeData.inputs.orderBy
|
||||
if (nodeData.inputs?.query) params.query = nodeData.inputs.query
|
||||
}
|
||||
|
||||
defaultParams[action] = params
|
||||
})
|
||||
}
|
||||
|
||||
// Calendar-specific default params
|
||||
if (calendarType === 'calendar') {
|
||||
actions.forEach((action) => {
|
||||
const params: any = {}
|
||||
|
||||
if (['getCalendar', 'updateCalendar', 'deleteCalendar', 'clearCalendar'].includes(action)) {
|
||||
if (nodeData.inputs?.calendarIdForCalendar) params.calendarId = nodeData.inputs.calendarIdForCalendar
|
||||
}
|
||||
if (action === 'createCalendar' || action === 'updateCalendar') {
|
||||
if (nodeData.inputs?.calendarSummary) params.summary = nodeData.inputs.calendarSummary
|
||||
if (nodeData.inputs?.calendarDescription) params.description = nodeData.inputs.calendarDescription
|
||||
if (nodeData.inputs?.calendarLocation) params.location = nodeData.inputs.calendarLocation
|
||||
if (nodeData.inputs?.calendarTimeZone) params.timeZone = nodeData.inputs.calendarTimeZone
|
||||
}
|
||||
if (action === 'listCalendars') {
|
||||
if (nodeData.inputs?.showHidden !== undefined) params.showHidden = nodeData.inputs.showHidden
|
||||
if (nodeData.inputs?.minAccessRole) params.minAccessRole = nodeData.inputs.minAccessRole
|
||||
}
|
||||
|
||||
defaultParams[action] = params
|
||||
})
|
||||
}
|
||||
|
||||
// Freebusy-specific default params
|
||||
if (calendarType === 'freebusy') {
|
||||
actions.forEach((action) => {
|
||||
const params: any = {}
|
||||
|
||||
if (action === 'queryFreebusy') {
|
||||
if (nodeData.inputs?.freebusyTimeMin) params.timeMin = nodeData.inputs.freebusyTimeMin
|
||||
if (nodeData.inputs?.freebusyTimeMax) params.timeMax = nodeData.inputs.freebusyTimeMax
|
||||
if (nodeData.inputs?.calendarIds) params.calendarIds = nodeData.inputs.calendarIds
|
||||
if (nodeData.inputs?.groupExpansionMax) params.groupExpansionMax = nodeData.inputs.groupExpansionMax
|
||||
if (nodeData.inputs?.calendarExpansionMax) params.calendarExpansionMax = nodeData.inputs.calendarExpansionMax
|
||||
}
|
||||
|
||||
defaultParams[action] = params
|
||||
})
|
||||
}
|
||||
|
||||
const tools = createGoogleCalendarTools({
|
||||
accessToken,
|
||||
actions,
|
||||
defaultParams
|
||||
})
|
||||
|
||||
return tools
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: GoogleCalendar_Tools }
|
||||
@@ -0,0 +1,864 @@
|
||||
import { z } from 'zod'
|
||||
import fetch from 'node-fetch'
|
||||
import { DynamicStructuredTool } from '../OpenAPIToolkit/core'
|
||||
import { TOOL_ARGS_PREFIX } from '../../../src/agents'
|
||||
|
||||
export const desc = `Use this when you want to access Google Calendar API for managing events and calendars`
|
||||
|
||||
export interface Headers {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
export interface Body {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface RequestParameters {
|
||||
headers?: Headers
|
||||
body?: Body
|
||||
url?: string
|
||||
description?: string
|
||||
name?: string
|
||||
actions?: string[]
|
||||
accessToken?: string
|
||||
defaultParams?: any
|
||||
}
|
||||
|
||||
// Define schemas for different Google Calendar operations
|
||||
|
||||
// Event Schemas
|
||||
const ListEventsSchema = z.object({
|
||||
calendarId: z.string().default('primary').describe('Calendar ID (use "primary" for primary calendar)'),
|
||||
timeMin: z.string().optional().describe('Lower bound for event search (RFC3339 timestamp)'),
|
||||
timeMax: z.string().optional().describe('Upper bound for event search (RFC3339 timestamp)'),
|
||||
maxResults: z.number().optional().default(250).describe('Maximum number of events to return'),
|
||||
singleEvents: z.boolean().optional().default(true).describe('Whether to expand recurring events into instances'),
|
||||
orderBy: z.enum(['startTime', 'updated']).optional().describe('Order of events returned'),
|
||||
query: z.string().optional().describe('Free text search terms')
|
||||
})
|
||||
|
||||
const CreateEventSchema = z.object({
|
||||
calendarId: z.string().default('primary').describe('Calendar ID where the event will be created'),
|
||||
summary: z.string().describe('Event title/summary'),
|
||||
description: z.string().optional().describe('Event description'),
|
||||
location: z.string().optional().describe('Event location'),
|
||||
startDateTime: z.string().optional().describe('Event start time (ISO 8601 format)'),
|
||||
endDateTime: z.string().optional().describe('Event end time (ISO 8601 format)'),
|
||||
startDate: z.string().optional().describe('Start date for all-day events (YYYY-MM-DD)'),
|
||||
endDate: z.string().optional().describe('End date for all-day events (YYYY-MM-DD)'),
|
||||
timeZone: z.string().optional().describe('Time zone (e.g., America/New_York)'),
|
||||
attendees: z.string().optional().describe('Comma-separated list of attendee emails'),
|
||||
recurrence: z.string().optional().describe('Recurrence rules (RRULE format)'),
|
||||
reminderMinutes: z.number().optional().describe('Minutes before event to send reminder'),
|
||||
visibility: z.enum(['default', 'public', 'private', 'confidential']).optional().describe('Event visibility')
|
||||
})
|
||||
|
||||
const GetEventSchema = z.object({
|
||||
calendarId: z.string().default('primary').describe('Calendar ID'),
|
||||
eventId: z.string().describe('Event ID')
|
||||
})
|
||||
|
||||
const UpdateEventSchema = z.object({
|
||||
calendarId: z.string().default('primary').describe('Calendar ID'),
|
||||
eventId: z.string().describe('Event ID'),
|
||||
summary: z.string().optional().describe('Updated event title/summary'),
|
||||
description: z.string().optional().describe('Updated event description'),
|
||||
location: z.string().optional().describe('Updated event location'),
|
||||
startDateTime: z.string().optional().describe('Updated event start time (ISO 8601 format)'),
|
||||
endDateTime: z.string().optional().describe('Updated event end time (ISO 8601 format)'),
|
||||
startDate: z.string().optional().describe('Updated start date for all-day events (YYYY-MM-DD)'),
|
||||
endDate: z.string().optional().describe('Updated end date for all-day events (YYYY-MM-DD)'),
|
||||
timeZone: z.string().optional().describe('Updated time zone'),
|
||||
attendees: z.string().optional().describe('Updated comma-separated list of attendee emails'),
|
||||
recurrence: z.string().optional().describe('Updated recurrence rules'),
|
||||
reminderMinutes: z.number().optional().describe('Updated reminder minutes'),
|
||||
visibility: z.enum(['default', 'public', 'private', 'confidential']).optional().describe('Updated event visibility')
|
||||
})
|
||||
|
||||
const DeleteEventSchema = z.object({
|
||||
calendarId: z.string().default('primary').describe('Calendar ID'),
|
||||
eventId: z.string().describe('Event ID to delete')
|
||||
})
|
||||
|
||||
const QuickAddEventSchema = z.object({
|
||||
calendarId: z.string().default('primary').describe('Calendar ID'),
|
||||
quickAddText: z.string().describe('Natural language text for quick event creation')
|
||||
})
|
||||
|
||||
// Calendar Schemas
|
||||
const ListCalendarsSchema = z.object({
|
||||
showHidden: z.boolean().optional().describe('Whether to show hidden calendars'),
|
||||
minAccessRole: z.enum(['freeBusyReader', 'reader', 'writer', 'owner']).optional().describe('Minimum access role')
|
||||
})
|
||||
|
||||
const CreateCalendarSchema = z.object({
|
||||
summary: z.string().describe('Calendar title/name'),
|
||||
description: z.string().optional().describe('Calendar description'),
|
||||
location: z.string().optional().describe('Calendar location'),
|
||||
timeZone: z.string().optional().describe('Calendar time zone (e.g., America/New_York)')
|
||||
})
|
||||
|
||||
const GetCalendarSchema = z.object({
|
||||
calendarId: z.string().describe('Calendar ID')
|
||||
})
|
||||
|
||||
const UpdateCalendarSchema = z.object({
|
||||
calendarId: z.string().describe('Calendar ID'),
|
||||
summary: z.string().optional().describe('Updated calendar title/name'),
|
||||
description: z.string().optional().describe('Updated calendar description'),
|
||||
location: z.string().optional().describe('Updated calendar location'),
|
||||
timeZone: z.string().optional().describe('Updated calendar time zone')
|
||||
})
|
||||
|
||||
const DeleteCalendarSchema = z.object({
|
||||
calendarId: z.string().describe('Calendar ID to delete')
|
||||
})
|
||||
|
||||
const ClearCalendarSchema = z.object({
|
||||
calendarId: z.string().describe('Calendar ID to clear (removes all events)')
|
||||
})
|
||||
|
||||
// Freebusy Schemas
|
||||
const QueryFreebusySchema = z.object({
|
||||
timeMin: z.string().describe('Lower bound for freebusy query (RFC3339 timestamp)'),
|
||||
timeMax: z.string().describe('Upper bound for freebusy query (RFC3339 timestamp)'),
|
||||
calendarIds: z.string().describe('Comma-separated list of calendar IDs to check for free/busy info'),
|
||||
groupExpansionMax: z.number().optional().describe('Maximum number of calendars for which FreeBusy information is to be provided'),
|
||||
calendarExpansionMax: z.number().optional().describe('Maximum number of events that can be expanded for each calendar')
|
||||
})
|
||||
|
||||
class BaseGoogleCalendarTool extends DynamicStructuredTool {
|
||||
protected accessToken: string = ''
|
||||
|
||||
constructor(args: any) {
|
||||
super(args)
|
||||
this.accessToken = args.accessToken ?? ''
|
||||
}
|
||||
|
||||
async makeGoogleCalendarRequest({
|
||||
endpoint,
|
||||
method = 'GET',
|
||||
body,
|
||||
params
|
||||
}: {
|
||||
endpoint: string
|
||||
method?: string
|
||||
body?: any
|
||||
params?: any
|
||||
}): Promise<string> {
|
||||
const url = `https://www.googleapis.com/calendar/v3/${endpoint}`
|
||||
|
||||
const headers = {
|
||||
Authorization: `Bearer ${this.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
...this.headers
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers,
|
||||
body: body ? JSON.stringify(body) : undefined
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Google Calendar API Error ${response.status}: ${response.statusText} - ${errorText}`)
|
||||
}
|
||||
|
||||
const data = await response.text()
|
||||
return data + TOOL_ARGS_PREFIX + JSON.stringify(params)
|
||||
}
|
||||
}
|
||||
|
||||
// Event Tools
|
||||
class ListEventsTool extends BaseGoogleCalendarTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'list_events',
|
||||
description: 'List events from Google Calendar',
|
||||
schema: ListEventsSchema,
|
||||
baseUrl: '',
|
||||
method: 'GET',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.timeMin) queryParams.append('timeMin', params.timeMin)
|
||||
if (params.timeMax) queryParams.append('timeMax', params.timeMax)
|
||||
if (params.maxResults) queryParams.append('maxResults', params.maxResults.toString())
|
||||
if (params.singleEvents !== undefined) queryParams.append('singleEvents', params.singleEvents.toString())
|
||||
if (params.orderBy) queryParams.append('orderBy', params.orderBy)
|
||||
if (params.query) queryParams.append('q', params.query)
|
||||
|
||||
const endpoint = `calendars/${encodeURIComponent(params.calendarId)}/events?${queryParams.toString()}`
|
||||
|
||||
try {
|
||||
const response = await this.makeGoogleCalendarRequest({ endpoint, params })
|
||||
return response
|
||||
} catch (error) {
|
||||
return `Error listing events: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CreateEventTool extends BaseGoogleCalendarTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'create_event',
|
||||
description: 'Create a new event in Google Calendar',
|
||||
schema: CreateEventSchema,
|
||||
baseUrl: '',
|
||||
method: 'POST',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const eventData: any = {
|
||||
summary: params.summary
|
||||
}
|
||||
|
||||
if (params.description) eventData.description = params.description
|
||||
if (params.location) eventData.location = params.location
|
||||
|
||||
// Handle date/time
|
||||
if (params.startDate && params.endDate) {
|
||||
// All-day event
|
||||
eventData.start = { date: params.startDate }
|
||||
eventData.end = { date: params.endDate }
|
||||
} else if (params.startDateTime && params.endDateTime) {
|
||||
// Timed event
|
||||
eventData.start = {
|
||||
dateTime: params.startDateTime,
|
||||
timeZone: params.timeZone || 'UTC'
|
||||
}
|
||||
eventData.end = {
|
||||
dateTime: params.endDateTime,
|
||||
timeZone: params.timeZone || 'UTC'
|
||||
}
|
||||
}
|
||||
|
||||
// Handle attendees
|
||||
if (params.attendees) {
|
||||
eventData.attendees = params.attendees.split(',').map((email: string) => ({
|
||||
email: email.trim()
|
||||
}))
|
||||
}
|
||||
|
||||
// Handle recurrence
|
||||
if (params.recurrence) {
|
||||
eventData.recurrence = [params.recurrence]
|
||||
}
|
||||
|
||||
// Handle reminders
|
||||
if (params.reminderMinutes !== undefined) {
|
||||
eventData.reminders = {
|
||||
useDefault: false,
|
||||
overrides: [
|
||||
{
|
||||
method: 'popup',
|
||||
minutes: params.reminderMinutes
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
if (params.visibility) eventData.visibility = params.visibility
|
||||
|
||||
const endpoint = `calendars/${encodeURIComponent(params.calendarId)}/events`
|
||||
const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'POST', body: eventData, params })
|
||||
return response
|
||||
} catch (error) {
|
||||
return `Error creating event: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GetEventTool extends BaseGoogleCalendarTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'get_event',
|
||||
description: 'Get a specific event from Google Calendar',
|
||||
schema: GetEventSchema,
|
||||
baseUrl: '',
|
||||
method: 'GET',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const endpoint = `calendars/${encodeURIComponent(params.calendarId)}/events/${encodeURIComponent(params.eventId)}`
|
||||
const response = await this.makeGoogleCalendarRequest({ endpoint, params })
|
||||
return response
|
||||
} catch (error) {
|
||||
return `Error getting event: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UpdateEventTool extends BaseGoogleCalendarTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'update_event',
|
||||
description: 'Update an existing event in Google Calendar',
|
||||
schema: UpdateEventSchema,
|
||||
baseUrl: '',
|
||||
method: 'PUT',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const updateData: any = {}
|
||||
|
||||
if (params.summary) updateData.summary = params.summary
|
||||
if (params.description) updateData.description = params.description
|
||||
if (params.location) updateData.location = params.location
|
||||
|
||||
// Handle date/time updates
|
||||
if (params.startDate && params.endDate) {
|
||||
updateData.start = { date: params.startDate }
|
||||
updateData.end = { date: params.endDate }
|
||||
} else if (params.startDateTime && params.endDateTime) {
|
||||
updateData.start = {
|
||||
dateTime: params.startDateTime,
|
||||
timeZone: params.timeZone || 'UTC'
|
||||
}
|
||||
updateData.end = {
|
||||
dateTime: params.endDateTime,
|
||||
timeZone: params.timeZone || 'UTC'
|
||||
}
|
||||
}
|
||||
|
||||
if (params.attendees) {
|
||||
updateData.attendees = params.attendees.split(',').map((email: string) => ({
|
||||
email: email.trim()
|
||||
}))
|
||||
}
|
||||
|
||||
if (params.recurrence) {
|
||||
updateData.recurrence = [params.recurrence]
|
||||
}
|
||||
|
||||
if (params.reminderMinutes !== undefined) {
|
||||
updateData.reminders = {
|
||||
useDefault: false,
|
||||
overrides: [
|
||||
{
|
||||
method: 'popup',
|
||||
minutes: params.reminderMinutes
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
if (params.visibility) updateData.visibility = params.visibility
|
||||
|
||||
const endpoint = `calendars/${encodeURIComponent(params.calendarId)}/events/${encodeURIComponent(params.eventId)}`
|
||||
const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'PUT', body: updateData, params })
|
||||
return response
|
||||
} catch (error) {
|
||||
return `Error updating event: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DeleteEventTool extends BaseGoogleCalendarTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'delete_event',
|
||||
description: 'Delete an event from Google Calendar',
|
||||
schema: DeleteEventSchema,
|
||||
baseUrl: '',
|
||||
method: 'DELETE',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const endpoint = `calendars/${encodeURIComponent(params.calendarId)}/events/${encodeURIComponent(params.eventId)}`
|
||||
const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'DELETE', params })
|
||||
return response || 'Event deleted successfully'
|
||||
} catch (error) {
|
||||
return `Error deleting event: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class QuickAddEventTool extends BaseGoogleCalendarTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'quick_add_event',
|
||||
description: 'Quick add event to Google Calendar using natural language',
|
||||
schema: QuickAddEventSchema,
|
||||
baseUrl: '',
|
||||
method: 'POST',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const queryParams = new URLSearchParams()
|
||||
queryParams.append('text', params.quickAddText)
|
||||
|
||||
const endpoint = `calendars/${encodeURIComponent(params.calendarId)}/events/quickAdd?${queryParams.toString()}`
|
||||
const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'POST', params })
|
||||
return response
|
||||
} catch (error) {
|
||||
return `Error quick adding event: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calendar Tools
|
||||
class ListCalendarsTool extends BaseGoogleCalendarTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'list_calendars',
|
||||
description: 'List calendars from Google Calendar',
|
||||
schema: ListCalendarsSchema,
|
||||
baseUrl: '',
|
||||
method: 'GET',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.showHidden !== undefined) queryParams.append('showHidden', params.showHidden.toString())
|
||||
if (params.minAccessRole) queryParams.append('minAccessRole', params.minAccessRole)
|
||||
|
||||
const endpoint = `users/me/calendarList?${queryParams.toString()}`
|
||||
|
||||
try {
|
||||
const response = await this.makeGoogleCalendarRequest({ endpoint, params })
|
||||
return response
|
||||
} catch (error) {
|
||||
return `Error listing calendars: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CreateCalendarTool extends BaseGoogleCalendarTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'create_calendar',
|
||||
description: 'Create a new calendar in Google Calendar',
|
||||
schema: CreateCalendarSchema,
|
||||
baseUrl: '',
|
||||
method: 'POST',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const calendarData: any = {
|
||||
summary: params.summary
|
||||
}
|
||||
|
||||
if (params.description) calendarData.description = params.description
|
||||
if (params.location) calendarData.location = params.location
|
||||
if (params.timeZone) calendarData.timeZone = params.timeZone
|
||||
|
||||
const endpoint = 'calendars'
|
||||
const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'POST', body: calendarData, params })
|
||||
return response
|
||||
} catch (error) {
|
||||
return `Error creating calendar: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GetCalendarTool extends BaseGoogleCalendarTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'get_calendar',
|
||||
description: 'Get a specific calendar from Google Calendar',
|
||||
schema: GetCalendarSchema,
|
||||
baseUrl: '',
|
||||
method: 'GET',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const endpoint = `calendars/${encodeURIComponent(params.calendarId)}`
|
||||
const response = await this.makeGoogleCalendarRequest({ endpoint, params })
|
||||
return response
|
||||
} catch (error) {
|
||||
return `Error getting calendar: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UpdateCalendarTool extends BaseGoogleCalendarTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'update_calendar',
|
||||
description: 'Update an existing calendar in Google Calendar',
|
||||
schema: UpdateCalendarSchema,
|
||||
baseUrl: '',
|
||||
method: 'PUT',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const updateData: any = {}
|
||||
|
||||
if (params.summary) updateData.summary = params.summary
|
||||
if (params.description) updateData.description = params.description
|
||||
if (params.location) updateData.location = params.location
|
||||
if (params.timeZone) updateData.timeZone = params.timeZone
|
||||
|
||||
const endpoint = `calendars/${encodeURIComponent(params.calendarId)}`
|
||||
const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'PUT', body: updateData, params })
|
||||
return response
|
||||
} catch (error) {
|
||||
return `Error updating calendar: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DeleteCalendarTool extends BaseGoogleCalendarTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'delete_calendar',
|
||||
description: 'Delete a calendar from Google Calendar',
|
||||
schema: DeleteCalendarSchema,
|
||||
baseUrl: '',
|
||||
method: 'DELETE',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const endpoint = `calendars/${encodeURIComponent(params.calendarId)}`
|
||||
const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'DELETE', params })
|
||||
return response || 'Calendar deleted successfully'
|
||||
} catch (error) {
|
||||
return `Error deleting calendar: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ClearCalendarTool extends BaseGoogleCalendarTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'clear_calendar',
|
||||
description: 'Clear all events from a Google Calendar',
|
||||
schema: ClearCalendarSchema,
|
||||
baseUrl: '',
|
||||
method: 'POST',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const endpoint = `calendars/${encodeURIComponent(params.calendarId)}/clear`
|
||||
const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'POST', params })
|
||||
return response || 'Calendar cleared successfully'
|
||||
} catch (error) {
|
||||
return `Error clearing calendar: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Freebusy Tools
|
||||
class QueryFreebusyTool extends BaseGoogleCalendarTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'query_freebusy',
|
||||
description: 'Query free/busy information for a set of calendars',
|
||||
schema: QueryFreebusySchema,
|
||||
baseUrl: '',
|
||||
method: 'POST',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const freebusyData: any = {
|
||||
timeMin: params.timeMin,
|
||||
timeMax: params.timeMax,
|
||||
items: params.calendarIds.split(',').map((id: string) => ({
|
||||
id: id.trim()
|
||||
}))
|
||||
}
|
||||
|
||||
if (params.groupExpansionMax !== undefined) {
|
||||
freebusyData.groupExpansionMax = params.groupExpansionMax
|
||||
}
|
||||
|
||||
if (params.calendarExpansionMax !== undefined) {
|
||||
freebusyData.calendarExpansionMax = params.calendarExpansionMax
|
||||
}
|
||||
|
||||
const endpoint = 'freeBusy'
|
||||
const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'POST', body: freebusyData, params })
|
||||
return response
|
||||
} catch (error) {
|
||||
return `Error querying freebusy: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const createGoogleCalendarTools = (args?: RequestParameters): DynamicStructuredTool[] => {
|
||||
const tools: DynamicStructuredTool[] = []
|
||||
const actions = args?.actions || []
|
||||
const accessToken = args?.accessToken || ''
|
||||
const defaultParams = args?.defaultParams || {}
|
||||
|
||||
// Event tools
|
||||
if (actions.includes('listEvents')) {
|
||||
tools.push(
|
||||
new ListEventsTool({
|
||||
accessToken,
|
||||
defaultParams: defaultParams.listEvents
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (actions.includes('createEvent')) {
|
||||
tools.push(
|
||||
new CreateEventTool({
|
||||
accessToken,
|
||||
defaultParams: defaultParams.createEvent
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (actions.includes('getEvent')) {
|
||||
tools.push(
|
||||
new GetEventTool({
|
||||
accessToken,
|
||||
defaultParams: defaultParams.getEvent
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (actions.includes('updateEvent')) {
|
||||
tools.push(
|
||||
new UpdateEventTool({
|
||||
accessToken,
|
||||
defaultParams: defaultParams.updateEvent
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (actions.includes('deleteEvent')) {
|
||||
tools.push(
|
||||
new DeleteEventTool({
|
||||
accessToken,
|
||||
defaultParams: defaultParams.deleteEvent
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (actions.includes('quickAddEvent')) {
|
||||
tools.push(
|
||||
new QuickAddEventTool({
|
||||
accessToken,
|
||||
defaultParams: defaultParams.quickAddEvent
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// Calendar tools
|
||||
if (actions.includes('listCalendars')) {
|
||||
tools.push(
|
||||
new ListCalendarsTool({
|
||||
accessToken,
|
||||
defaultParams: defaultParams.listCalendars
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (actions.includes('createCalendar')) {
|
||||
tools.push(
|
||||
new CreateCalendarTool({
|
||||
accessToken,
|
||||
defaultParams: defaultParams.createCalendar
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (actions.includes('getCalendar')) {
|
||||
tools.push(
|
||||
new GetCalendarTool({
|
||||
accessToken,
|
||||
defaultParams: defaultParams.getCalendar
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (actions.includes('updateCalendar')) {
|
||||
tools.push(
|
||||
new UpdateCalendarTool({
|
||||
accessToken,
|
||||
defaultParams: defaultParams.updateCalendar
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (actions.includes('deleteCalendar')) {
|
||||
tools.push(
|
||||
new DeleteCalendarTool({
|
||||
accessToken,
|
||||
defaultParams: defaultParams.deleteCalendar
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (actions.includes('clearCalendar')) {
|
||||
tools.push(
|
||||
new ClearCalendarTool({
|
||||
accessToken,
|
||||
defaultParams: defaultParams.clearCalendar
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// Freebusy tools
|
||||
if (actions.includes('queryFreebusy')) {
|
||||
tools.push(
|
||||
new QueryFreebusyTool({
|
||||
accessToken,
|
||||
defaultParams: defaultParams.queryFreebusy
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return tools
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><rect width="22" height="22" x="13" y="13" fill="#fff"/><polygon fill="#1e88e5" points="25.68,20.92 26.688,22.36 28.272,21.208 28.272,29.56 30,29.56 30,18.616 28.56,18.616"/><path fill="#1e88e5" d="M22.943,23.745c0.625-0.574,1.013-1.37,1.013-2.249c0-1.747-1.533-3.168-3.417-3.168 c-1.602,0-2.972,1.009-3.33,2.453l1.657,0.421c0.165-0.664,0.868-1.146,1.673-1.146c0.942,0,1.709,0.646,1.709,1.44 c0,0.794-0.767,1.44-1.709,1.44h-0.997v1.728h0.997c1.081,0,1.993,0.751,1.993,1.64c0,0.904-0.866,1.64-1.931,1.64 c-0.962,0-1.784-0.61-1.914-1.418L17,26.802c0.262,1.636,1.81,2.87,3.6,2.87c2.007,0,3.64-1.511,3.64-3.368 C24.24,25.281,23.736,24.363,22.943,23.745z"/><polygon fill="#fbc02d" points="34,42 14,42 13,38 14,34 34,34 35,38"/><polygon fill="#4caf50" points="38,35 42,34 42,14 38,13 34,14 34,34"/><path fill="#1e88e5" d="M34,14l1-4l-1-4H9C7.343,6,6,7.343,6,9v25l4,1l4-1V14H34z"/><polygon fill="#e53935" points="34,34 34,42 42,34"/><path fill="#1565c0" d="M39,6h-5v8h8V9C42,7.343,40.657,6,39,6z"/><path fill="#1565c0" d="M9,42h5v-8H6v5C6,40.657,7.343,42,9,42z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,657 @@
|
||||
import { convertMultiOptionsToStringArray, getCredentialData, getCredentialParam, refreshOAuth2Token } from '../../../src/utils'
|
||||
import { createGoogleDriveTools } from './core'
|
||||
import type { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
|
||||
class GoogleDrive_Tools implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
description: string
|
||||
baseClasses: string[]
|
||||
credential: INodeParams
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Google Drive'
|
||||
this.name = 'googleDriveTool'
|
||||
this.version = 1.0
|
||||
this.type = 'GoogleDrive'
|
||||
this.icon = 'google-drive.svg'
|
||||
this.category = 'Tools'
|
||||
this.description = 'Perform Google Drive operations such as managing files, folders, sharing, and searching'
|
||||
this.baseClasses = ['Tool']
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['googleDriveOAuth2']
|
||||
}
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Type',
|
||||
name: 'driveType',
|
||||
type: 'options',
|
||||
description: 'Type of Google Drive operation',
|
||||
options: [
|
||||
{
|
||||
label: 'File',
|
||||
name: 'file'
|
||||
},
|
||||
{
|
||||
label: 'Folder',
|
||||
name: 'folder'
|
||||
},
|
||||
{
|
||||
label: 'Search',
|
||||
name: 'search'
|
||||
},
|
||||
{
|
||||
label: 'Share',
|
||||
name: 'share'
|
||||
}
|
||||
]
|
||||
},
|
||||
// File Actions
|
||||
{
|
||||
label: 'File Actions',
|
||||
name: 'fileActions',
|
||||
type: 'multiOptions',
|
||||
description: 'Actions to perform on files',
|
||||
options: [
|
||||
{
|
||||
label: 'List Files',
|
||||
name: 'listFiles'
|
||||
},
|
||||
{
|
||||
label: 'Get File',
|
||||
name: 'getFile'
|
||||
},
|
||||
{
|
||||
label: 'Create File',
|
||||
name: 'createFile'
|
||||
},
|
||||
{
|
||||
label: 'Update File',
|
||||
name: 'updateFile'
|
||||
},
|
||||
{
|
||||
label: 'Delete File',
|
||||
name: 'deleteFile'
|
||||
},
|
||||
{
|
||||
label: 'Copy File',
|
||||
name: 'copyFile'
|
||||
},
|
||||
{
|
||||
label: 'Download File',
|
||||
name: 'downloadFile'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
driveType: ['file']
|
||||
}
|
||||
},
|
||||
// Folder Actions
|
||||
{
|
||||
label: 'Folder Actions',
|
||||
name: 'folderActions',
|
||||
type: 'multiOptions',
|
||||
description: 'Actions to perform on folders',
|
||||
options: [
|
||||
{
|
||||
label: 'Create Folder',
|
||||
name: 'createFolder'
|
||||
},
|
||||
{
|
||||
label: 'List Folder Contents',
|
||||
name: 'listFolderContents'
|
||||
},
|
||||
{
|
||||
label: 'Delete Folder',
|
||||
name: 'deleteFolder'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
driveType: ['folder']
|
||||
}
|
||||
},
|
||||
// Search Actions
|
||||
{
|
||||
label: 'Search Actions',
|
||||
name: 'searchActions',
|
||||
type: 'multiOptions',
|
||||
description: 'Search operations',
|
||||
options: [
|
||||
{
|
||||
label: 'Search Files',
|
||||
name: 'searchFiles'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
driveType: ['search']
|
||||
}
|
||||
},
|
||||
// Share Actions
|
||||
{
|
||||
label: 'Share Actions',
|
||||
name: 'shareActions',
|
||||
type: 'multiOptions',
|
||||
description: 'Sharing operations',
|
||||
options: [
|
||||
{
|
||||
label: 'Share File',
|
||||
name: 'shareFile'
|
||||
},
|
||||
{
|
||||
label: 'Get Permissions',
|
||||
name: 'getPermissions'
|
||||
},
|
||||
{
|
||||
label: 'Remove Permission',
|
||||
name: 'removePermission'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
driveType: ['share']
|
||||
}
|
||||
},
|
||||
// File Parameters
|
||||
{
|
||||
label: 'File ID',
|
||||
name: 'fileId',
|
||||
type: 'string',
|
||||
description: 'File ID for file operations',
|
||||
show: {
|
||||
fileActions: ['getFile', 'updateFile', 'deleteFile', 'copyFile', 'downloadFile']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'File ID',
|
||||
name: 'fileId',
|
||||
type: 'string',
|
||||
description: 'File ID for sharing operations',
|
||||
show: {
|
||||
shareActions: ['shareFile', 'getPermissions', 'removePermission']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Folder ID',
|
||||
name: 'folderId',
|
||||
type: 'string',
|
||||
description: 'Folder ID for folder operations',
|
||||
show: {
|
||||
folderActions: ['listFolderContents', 'deleteFolder']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Permission ID',
|
||||
name: 'permissionId',
|
||||
type: 'string',
|
||||
description: 'Permission ID to remove',
|
||||
show: {
|
||||
shareActions: ['removePermission']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'File Name',
|
||||
name: 'fileName',
|
||||
type: 'string',
|
||||
description: 'Name of the file',
|
||||
show: {
|
||||
fileActions: ['createFile', 'copyFile']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Folder Name',
|
||||
name: 'fileName',
|
||||
type: 'string',
|
||||
description: 'Name of the folder',
|
||||
show: {
|
||||
folderActions: ['createFolder']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'File Content',
|
||||
name: 'fileContent',
|
||||
type: 'string',
|
||||
description: 'Content of the file (for text files)',
|
||||
show: {
|
||||
fileActions: ['createFile']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'MIME Type',
|
||||
name: 'mimeType',
|
||||
type: 'string',
|
||||
description: 'MIME type of the file (e.g., text/plain, application/pdf)',
|
||||
show: {
|
||||
fileActions: ['createFile']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Parent Folder ID',
|
||||
name: 'parentFolderId',
|
||||
type: 'string',
|
||||
description: 'ID of the parent folder (comma-separated for multiple parents)',
|
||||
show: {
|
||||
fileActions: ['createFile', 'copyFile']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Parent Folder ID',
|
||||
name: 'parentFolderId',
|
||||
type: 'string',
|
||||
description: 'ID of the parent folder for the new folder',
|
||||
show: {
|
||||
folderActions: ['createFolder']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'File Description',
|
||||
name: 'description',
|
||||
type: 'string',
|
||||
description: 'File description',
|
||||
show: {
|
||||
fileActions: ['createFile', 'updateFile']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Folder Description',
|
||||
name: 'description',
|
||||
type: 'string',
|
||||
description: 'Folder description',
|
||||
show: {
|
||||
folderActions: ['createFolder']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Search Parameters
|
||||
{
|
||||
label: 'Search Query',
|
||||
name: 'searchQuery',
|
||||
type: 'string',
|
||||
description: 'Search query using Google Drive search syntax',
|
||||
show: {
|
||||
searchActions: ['searchFiles']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Max Results',
|
||||
name: 'maxResults',
|
||||
type: 'number',
|
||||
description: 'Maximum number of results to return (1-1000)',
|
||||
default: 10,
|
||||
show: {
|
||||
fileActions: ['listFiles']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Max Results',
|
||||
name: 'maxResults',
|
||||
type: 'number',
|
||||
description: 'Maximum number of results to return (1-1000)',
|
||||
default: 10,
|
||||
show: {
|
||||
searchActions: ['searchFiles']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Order By',
|
||||
name: 'orderBy',
|
||||
type: 'options',
|
||||
description: 'Sort order for file results',
|
||||
options: [
|
||||
{
|
||||
label: 'Name',
|
||||
name: 'name'
|
||||
},
|
||||
{
|
||||
label: 'Created Time',
|
||||
name: 'createdTime'
|
||||
},
|
||||
{
|
||||
label: 'Modified Time',
|
||||
name: 'modifiedTime'
|
||||
},
|
||||
{
|
||||
label: 'Size',
|
||||
name: 'quotaBytesUsed'
|
||||
},
|
||||
{
|
||||
label: 'Folder',
|
||||
name: 'folder'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
fileActions: ['listFiles']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Order By',
|
||||
name: 'orderBy',
|
||||
type: 'options',
|
||||
description: 'Sort order for search results',
|
||||
options: [
|
||||
{
|
||||
label: 'Name',
|
||||
name: 'name'
|
||||
},
|
||||
{
|
||||
label: 'Created Time',
|
||||
name: 'createdTime'
|
||||
},
|
||||
{
|
||||
label: 'Modified Time',
|
||||
name: 'modifiedTime'
|
||||
},
|
||||
{
|
||||
label: 'Size',
|
||||
name: 'quotaBytesUsed'
|
||||
},
|
||||
{
|
||||
label: 'Folder',
|
||||
name: 'folder'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
searchActions: ['searchFiles']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Share Parameters
|
||||
{
|
||||
label: 'Share Role',
|
||||
name: 'shareRole',
|
||||
type: 'options',
|
||||
description: 'Permission role for sharing',
|
||||
options: [
|
||||
{
|
||||
label: 'Reader',
|
||||
name: 'reader'
|
||||
},
|
||||
{
|
||||
label: 'Writer',
|
||||
name: 'writer'
|
||||
},
|
||||
{
|
||||
label: 'Commenter',
|
||||
name: 'commenter'
|
||||
},
|
||||
{
|
||||
label: 'Owner',
|
||||
name: 'owner'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
shareActions: ['shareFile']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Share Type',
|
||||
name: 'shareType',
|
||||
type: 'options',
|
||||
description: 'Type of permission',
|
||||
options: [
|
||||
{
|
||||
label: 'User',
|
||||
name: 'user'
|
||||
},
|
||||
{
|
||||
label: 'Group',
|
||||
name: 'group'
|
||||
},
|
||||
{
|
||||
label: 'Domain',
|
||||
name: 'domain'
|
||||
},
|
||||
{
|
||||
label: 'Anyone',
|
||||
name: 'anyone'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
shareActions: ['shareFile']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Email Address',
|
||||
name: 'emailAddress',
|
||||
type: 'string',
|
||||
description: 'Email address for user/group sharing',
|
||||
show: {
|
||||
shareActions: ['shareFile']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Domain Name',
|
||||
name: 'domainName',
|
||||
type: 'string',
|
||||
description: 'Domain name for domain sharing',
|
||||
show: {
|
||||
shareActions: ['shareFile']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Send Notification Email',
|
||||
name: 'sendNotificationEmail',
|
||||
type: 'boolean',
|
||||
description: 'Whether to send notification emails when sharing',
|
||||
default: true,
|
||||
show: {
|
||||
shareActions: ['shareFile']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Email Message',
|
||||
name: 'emailMessage',
|
||||
type: 'string',
|
||||
description: 'Custom message to include in notification email',
|
||||
show: {
|
||||
shareActions: ['shareFile']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Advanced Parameters for File Actions
|
||||
{
|
||||
label: 'Include Items From All Drives',
|
||||
name: 'includeItemsFromAllDrives',
|
||||
type: 'boolean',
|
||||
description: 'Include items from all drives (shared drives)',
|
||||
show: {
|
||||
fileActions: ['listFiles']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Include Items From All Drives',
|
||||
name: 'includeItemsFromAllDrives',
|
||||
type: 'boolean',
|
||||
description: 'Include items from all drives (shared drives)',
|
||||
show: {
|
||||
searchActions: ['searchFiles']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Supports All Drives',
|
||||
name: 'supportsAllDrives',
|
||||
type: 'boolean',
|
||||
description: 'Whether the application supports both My Drives and shared drives',
|
||||
show: {
|
||||
fileActions: ['listFiles', 'getFile', 'createFile', 'updateFile', 'deleteFile', 'copyFile', 'downloadFile']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Supports All Drives',
|
||||
name: 'supportsAllDrives',
|
||||
type: 'boolean',
|
||||
description: 'Whether the application supports both My Drives and shared drives',
|
||||
show: {
|
||||
folderActions: ['createFolder', 'listFolderContents', 'deleteFolder']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Supports All Drives',
|
||||
name: 'supportsAllDrives',
|
||||
type: 'boolean',
|
||||
description: 'Whether the application supports both My Drives and shared drives',
|
||||
show: {
|
||||
searchActions: ['searchFiles']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Supports All Drives',
|
||||
name: 'supportsAllDrives',
|
||||
type: 'boolean',
|
||||
description: 'Whether the application supports both My Drives and shared drives',
|
||||
show: {
|
||||
shareActions: ['shareFile', 'getPermissions', 'removePermission']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Fields',
|
||||
name: 'fields',
|
||||
type: 'string',
|
||||
description: 'Specific fields to include in response (e.g., "files(id,name,mimeType)")',
|
||||
show: {
|
||||
fileActions: ['listFiles', 'getFile']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Acknowledge Abuse',
|
||||
name: 'acknowledgeAbuse',
|
||||
type: 'boolean',
|
||||
description: 'Acknowledge the risk of downloading known malware or abusive files',
|
||||
show: {
|
||||
fileActions: ['getFile', 'downloadFile']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
let credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
credentialData = await refreshOAuth2Token(nodeData.credential ?? '', credentialData, options)
|
||||
const accessToken = getCredentialParam('access_token', credentialData, nodeData)
|
||||
|
||||
if (!accessToken) {
|
||||
throw new Error('No access token found in credential')
|
||||
}
|
||||
|
||||
const driveType = nodeData.inputs?.driveType as string
|
||||
const fileActions = convertMultiOptionsToStringArray(nodeData.inputs?.fileActions)
|
||||
const folderActions = convertMultiOptionsToStringArray(nodeData.inputs?.folderActions)
|
||||
const searchActions = convertMultiOptionsToStringArray(nodeData.inputs?.searchActions)
|
||||
const shareActions = convertMultiOptionsToStringArray(nodeData.inputs?.shareActions)
|
||||
|
||||
// Combine all actions based on type
|
||||
let actions: string[] = []
|
||||
if (driveType === 'file') {
|
||||
actions = fileActions
|
||||
} else if (driveType === 'folder') {
|
||||
actions = folderActions
|
||||
} else if (driveType === 'search') {
|
||||
actions = searchActions
|
||||
} else if (driveType === 'share') {
|
||||
actions = shareActions
|
||||
}
|
||||
|
||||
// Collect default parameters from inputs
|
||||
const defaultParams: any = {}
|
||||
|
||||
// Add parameters based on the inputs provided
|
||||
if (nodeData.inputs?.fileId) defaultParams.fileId = nodeData.inputs.fileId
|
||||
if (nodeData.inputs?.folderId) defaultParams.folderId = nodeData.inputs.folderId
|
||||
if (nodeData.inputs?.permissionId) defaultParams.permissionId = nodeData.inputs.permissionId
|
||||
if (nodeData.inputs?.fileName) defaultParams.name = nodeData.inputs.fileName
|
||||
if (nodeData.inputs?.fileContent) defaultParams.content = nodeData.inputs.fileContent
|
||||
if (nodeData.inputs?.mimeType) defaultParams.mimeType = nodeData.inputs.mimeType
|
||||
if (nodeData.inputs?.parentFolderId) defaultParams.parents = nodeData.inputs.parentFolderId
|
||||
if (nodeData.inputs?.description) defaultParams.description = nodeData.inputs.description
|
||||
if (nodeData.inputs?.searchQuery) defaultParams.query = nodeData.inputs.searchQuery
|
||||
if (nodeData.inputs?.maxResults) defaultParams.pageSize = nodeData.inputs.maxResults
|
||||
if (nodeData.inputs?.orderBy) defaultParams.orderBy = nodeData.inputs.orderBy
|
||||
if (nodeData.inputs?.shareRole) defaultParams.role = nodeData.inputs.shareRole
|
||||
if (nodeData.inputs?.shareType) defaultParams.type = nodeData.inputs.shareType
|
||||
if (nodeData.inputs?.emailAddress) defaultParams.emailAddress = nodeData.inputs.emailAddress
|
||||
if (nodeData.inputs?.domainName) defaultParams.domain = nodeData.inputs.domainName
|
||||
if (nodeData.inputs?.sendNotificationEmail !== undefined)
|
||||
defaultParams.sendNotificationEmail = nodeData.inputs.sendNotificationEmail
|
||||
if (nodeData.inputs?.emailMessage) defaultParams.emailMessage = nodeData.inputs.emailMessage
|
||||
if (nodeData.inputs?.includeItemsFromAllDrives !== undefined)
|
||||
defaultParams.includeItemsFromAllDrives = nodeData.inputs.includeItemsFromAllDrives
|
||||
if (nodeData.inputs?.supportsAllDrives !== undefined) defaultParams.supportsAllDrives = nodeData.inputs.supportsAllDrives
|
||||
if (nodeData.inputs?.fields) defaultParams.fields = nodeData.inputs.fields
|
||||
if (nodeData.inputs?.acknowledgeAbuse !== undefined) defaultParams.acknowledgeAbuse = nodeData.inputs.acknowledgeAbuse
|
||||
|
||||
const tools = createGoogleDriveTools({
|
||||
accessToken,
|
||||
actions,
|
||||
defaultParams
|
||||
})
|
||||
|
||||
return tools
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: GoogleDrive_Tools }
|
||||
@@ -0,0 +1,982 @@
|
||||
import { z } from 'zod'
|
||||
import fetch from 'node-fetch'
|
||||
import { DynamicStructuredTool } from '../OpenAPIToolkit/core'
|
||||
import { TOOL_ARGS_PREFIX } from '../../../src/agents'
|
||||
|
||||
export const desc = `Use this when you want to access Google Drive API for managing files and folders`
|
||||
|
||||
export interface Headers {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
export interface Body {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface RequestParameters {
|
||||
headers?: Headers
|
||||
body?: Body
|
||||
url?: string
|
||||
description?: string
|
||||
name?: string
|
||||
actions?: string[]
|
||||
accessToken?: string
|
||||
defaultParams?: any
|
||||
}
|
||||
|
||||
// Define schemas for different Google Drive operations
|
||||
|
||||
// File Schemas
|
||||
const ListFilesSchema = z.object({
|
||||
pageSize: z.number().optional().default(10).describe('Maximum number of files to return (1-1000)'),
|
||||
pageToken: z.string().optional().describe('Token for next page of results'),
|
||||
orderBy: z.string().optional().describe('Sort order (name, folder, createdTime, modifiedTime, etc.)'),
|
||||
query: z.string().optional().describe('Search query (e.g., "name contains \'hello\'")'),
|
||||
spaces: z.string().optional().default('drive').describe('Spaces to search (drive, appDataFolder, photos)'),
|
||||
fields: z.string().optional().describe('Fields to include in response'),
|
||||
includeItemsFromAllDrives: z.boolean().optional().describe('Include items from all drives'),
|
||||
supportsAllDrives: z.boolean().optional().describe('Whether the requesting application supports both My Drives and shared drives')
|
||||
})
|
||||
|
||||
const GetFileSchema = z.object({
|
||||
fileId: z.string().describe('File ID'),
|
||||
fields: z.string().optional().describe('Fields to include in response'),
|
||||
supportsAllDrives: z.boolean().optional().describe('Whether the requesting application supports both My Drives and shared drives'),
|
||||
acknowledgeAbuse: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether the user is acknowledging the risk of downloading known malware or other abusive files')
|
||||
})
|
||||
|
||||
const CreateFileSchema = z.object({
|
||||
name: z.string().describe('File name'),
|
||||
parents: z.string().optional().describe('Comma-separated list of parent folder IDs'),
|
||||
mimeType: z.string().optional().describe('MIME type of the file'),
|
||||
description: z.string().optional().describe('File description'),
|
||||
content: z.string().optional().describe('File content (for text files)'),
|
||||
supportsAllDrives: z.boolean().optional().describe('Whether the requesting application supports both My Drives and shared drives')
|
||||
})
|
||||
|
||||
const UpdateFileSchema = z.object({
|
||||
fileId: z.string().describe('File ID to update'),
|
||||
name: z.string().optional().describe('New file name'),
|
||||
description: z.string().optional().describe('New file description'),
|
||||
starred: z.boolean().optional().describe('Whether the file is starred'),
|
||||
trashed: z.boolean().optional().describe('Whether the file is trashed'),
|
||||
parents: z.string().optional().describe('Comma-separated list of new parent folder IDs'),
|
||||
supportsAllDrives: z.boolean().optional().describe('Whether the requesting application supports both My Drives and shared drives')
|
||||
})
|
||||
|
||||
const DeleteFileSchema = z.object({
|
||||
fileId: z.string().describe('File ID to delete'),
|
||||
supportsAllDrives: z.boolean().optional().describe('Whether the requesting application supports both My Drives and shared drives')
|
||||
})
|
||||
|
||||
const CopyFileSchema = z.object({
|
||||
fileId: z.string().describe('File ID to copy'),
|
||||
name: z.string().describe('Name for the copied file'),
|
||||
parents: z.string().optional().describe('Comma-separated list of parent folder IDs for the copy'),
|
||||
supportsAllDrives: z.boolean().optional().describe('Whether the requesting application supports both My Drives and shared drives')
|
||||
})
|
||||
|
||||
const DownloadFileSchema = z.object({
|
||||
fileId: z.string().describe('File ID to download'),
|
||||
acknowledgeAbuse: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether the user is acknowledging the risk of downloading known malware or other abusive files'),
|
||||
supportsAllDrives: z.boolean().optional().describe('Whether the requesting application supports both My Drives and shared drives')
|
||||
})
|
||||
|
||||
const CreateFolderSchema = z.object({
|
||||
name: z.string().describe('Folder name'),
|
||||
parents: z.string().optional().describe('Comma-separated list of parent folder IDs'),
|
||||
description: z.string().optional().describe('Folder description'),
|
||||
supportsAllDrives: z.boolean().optional().describe('Whether the requesting application supports both My Drives and shared drives')
|
||||
})
|
||||
|
||||
const SearchFilesSchema = z.object({
|
||||
query: z.string().describe('Search query using Google Drive search syntax'),
|
||||
pageSize: z.number().optional().default(10).describe('Maximum number of files to return'),
|
||||
orderBy: z.string().optional().describe('Sort order'),
|
||||
includeItemsFromAllDrives: z.boolean().optional().describe('Include items from all drives'),
|
||||
supportsAllDrives: z.boolean().optional().describe('Whether the requesting application supports both My Drives and shared drives')
|
||||
})
|
||||
|
||||
const ShareFileSchema = z.object({
|
||||
fileId: z.string().describe('File ID to share'),
|
||||
role: z.enum(['reader', 'writer', 'commenter', 'owner']).describe('Permission role'),
|
||||
type: z.enum(['user', 'group', 'domain', 'anyone']).describe('Permission type'),
|
||||
emailAddress: z.string().optional().describe('Email address (required for user/group types)'),
|
||||
domain: z.string().optional().describe('Domain name (required for domain type)'),
|
||||
allowFileDiscovery: z.boolean().optional().describe('Whether the file can be discovered by search'),
|
||||
sendNotificationEmail: z.boolean().optional().default(true).describe('Whether to send notification emails'),
|
||||
emailMessage: z.string().optional().describe('Custom message to include in notification email'),
|
||||
supportsAllDrives: z.boolean().optional().describe('Whether the requesting application supports both My Drives and shared drives')
|
||||
})
|
||||
|
||||
class BaseGoogleDriveTool extends DynamicStructuredTool {
|
||||
protected accessToken: string = ''
|
||||
|
||||
constructor(args: any) {
|
||||
super(args)
|
||||
this.accessToken = args.accessToken ?? ''
|
||||
}
|
||||
|
||||
async makeGoogleDriveRequest({
|
||||
endpoint,
|
||||
method = 'GET',
|
||||
body,
|
||||
params
|
||||
}: {
|
||||
endpoint: string
|
||||
method?: string
|
||||
body?: any
|
||||
params?: any
|
||||
}): Promise<string> {
|
||||
const baseUrl = 'https://www.googleapis.com/drive/v3'
|
||||
const url = `${baseUrl}/${endpoint}`
|
||||
|
||||
const headers: { [key: string]: string } = {
|
||||
Authorization: `Bearer ${this.accessToken}`,
|
||||
Accept: 'application/json',
|
||||
...this.headers
|
||||
}
|
||||
|
||||
if (method !== 'GET' && body) {
|
||||
headers['Content-Type'] = 'application/json'
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers,
|
||||
body: body ? (typeof body === 'string' ? body : JSON.stringify(body)) : undefined
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Google Drive API Error ${response.status}: ${response.statusText} - ${errorText}`)
|
||||
}
|
||||
|
||||
const data = await response.text()
|
||||
return data + TOOL_ARGS_PREFIX + JSON.stringify(params)
|
||||
}
|
||||
}
|
||||
|
||||
// File Tools
|
||||
class ListFilesTool extends BaseGoogleDriveTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'list_files',
|
||||
description: 'List files and folders from Google Drive',
|
||||
schema: ListFilesSchema,
|
||||
baseUrl: '',
|
||||
method: 'GET',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.pageSize) queryParams.append('pageSize', params.pageSize.toString())
|
||||
if (params.pageToken) queryParams.append('pageToken', params.pageToken)
|
||||
if (params.orderBy) queryParams.append('orderBy', params.orderBy)
|
||||
if (params.query) queryParams.append('q', params.query)
|
||||
if (params.spaces) queryParams.append('spaces', params.spaces)
|
||||
if (params.fields) queryParams.append('fields', params.fields)
|
||||
if (params.includeItemsFromAllDrives) queryParams.append('includeItemsFromAllDrives', params.includeItemsFromAllDrives.toString())
|
||||
if (params.supportsAllDrives) queryParams.append('supportsAllDrives', params.supportsAllDrives.toString())
|
||||
|
||||
const endpoint = `files?${queryParams.toString()}`
|
||||
|
||||
try {
|
||||
const response = await this.makeGoogleDriveRequest({ endpoint, params })
|
||||
return response
|
||||
} catch (error) {
|
||||
return `Error listing files: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GetFileTool extends BaseGoogleDriveTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'get_file',
|
||||
description: 'Get file metadata from Google Drive',
|
||||
schema: GetFileSchema,
|
||||
baseUrl: '',
|
||||
method: 'GET',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.fields) queryParams.append('fields', params.fields)
|
||||
if (params.supportsAllDrives) queryParams.append('supportsAllDrives', params.supportsAllDrives.toString())
|
||||
if (params.acknowledgeAbuse) queryParams.append('acknowledgeAbuse', params.acknowledgeAbuse.toString())
|
||||
|
||||
const endpoint = `files/${encodeURIComponent(params.fileId)}?${queryParams.toString()}`
|
||||
|
||||
try {
|
||||
const response = await this.makeGoogleDriveRequest({ endpoint, params })
|
||||
return response
|
||||
} catch (error) {
|
||||
return `Error getting file: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CreateFileTool extends BaseGoogleDriveTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'create_file',
|
||||
description: 'Create a new file in Google Drive',
|
||||
schema: CreateFileSchema,
|
||||
baseUrl: '',
|
||||
method: 'POST',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
// Validate required parameters
|
||||
if (!params.name) {
|
||||
throw new Error('File name is required')
|
||||
}
|
||||
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.supportsAllDrives) queryParams.append('supportsAllDrives', params.supportsAllDrives.toString())
|
||||
|
||||
// Prepare metadata
|
||||
const fileMetadata: any = {
|
||||
name: params.name
|
||||
}
|
||||
|
||||
if (params.parents) {
|
||||
// Validate parent folder IDs format
|
||||
const parentIds = params.parents
|
||||
.split(',')
|
||||
.map((p: string) => p.trim())
|
||||
.filter((p: string) => p.length > 0)
|
||||
if (parentIds.length > 0) {
|
||||
fileMetadata.parents = parentIds
|
||||
}
|
||||
}
|
||||
if (params.mimeType) fileMetadata.mimeType = params.mimeType
|
||||
if (params.description) fileMetadata.description = params.description
|
||||
|
||||
// Determine upload type based on content and metadata
|
||||
if (!params.content) {
|
||||
// Metadata-only upload (no file content) - standard endpoint
|
||||
const endpoint = `files?${queryParams.toString()}`
|
||||
const response = await this.makeGoogleDriveRequest({
|
||||
endpoint,
|
||||
method: 'POST',
|
||||
body: fileMetadata,
|
||||
params
|
||||
})
|
||||
return response
|
||||
} else {
|
||||
// Validate content
|
||||
if (typeof params.content !== 'string') {
|
||||
throw new Error('File content must be a string')
|
||||
}
|
||||
|
||||
// Check if we have metadata beyond just the name
|
||||
const hasAdditionalMetadata = params.parents || params.description || params.mimeType
|
||||
|
||||
if (!hasAdditionalMetadata) {
|
||||
// Simple upload (uploadType=media) - only file content, basic metadata
|
||||
return await this.performSimpleUpload(params, queryParams)
|
||||
} else {
|
||||
// Multipart upload (uploadType=multipart) - file content + metadata
|
||||
return await this.performMultipartUpload(params, fileMetadata, queryParams)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return `Error creating file: ${error}`
|
||||
}
|
||||
}
|
||||
|
||||
private async performSimpleUpload(params: any, queryParams: URLSearchParams): Promise<string> {
|
||||
// Simple upload: POST https://www.googleapis.com/upload/drive/v3/files?uploadType=media
|
||||
queryParams.append('uploadType', 'media')
|
||||
const url = `https://www.googleapis.com/upload/drive/v3/files?${queryParams.toString()}`
|
||||
|
||||
const headers: { [key: string]: string } = {
|
||||
Authorization: `Bearer ${this.accessToken}`,
|
||||
'Content-Type': params.mimeType || 'application/octet-stream',
|
||||
'Content-Length': Buffer.byteLength(params.content, 'utf8').toString()
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: params.content
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Google Drive API Error ${response.status}: ${response.statusText} - ${errorText}`)
|
||||
}
|
||||
|
||||
const data = await response.text()
|
||||
return data + TOOL_ARGS_PREFIX + JSON.stringify(params)
|
||||
}
|
||||
|
||||
private async performMultipartUpload(params: any, fileMetadata: any, queryParams: URLSearchParams): Promise<string> {
|
||||
// Multipart upload: POST https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart
|
||||
queryParams.append('uploadType', 'multipart')
|
||||
const url = `https://www.googleapis.com/upload/drive/v3/files?${queryParams.toString()}`
|
||||
|
||||
// Create multipart/related body according to RFC 2387
|
||||
const boundary = '-------314159265358979323846'
|
||||
|
||||
// Build multipart body - RFC 2387 format
|
||||
let body = `--${boundary}\r\n`
|
||||
|
||||
// Part 1: Metadata (application/json; charset=UTF-8)
|
||||
body += 'Content-Type: application/json; charset=UTF-8\r\n\r\n'
|
||||
body += JSON.stringify(fileMetadata) + '\r\n'
|
||||
|
||||
// Part 2: Media content (any MIME type)
|
||||
body += `--${boundary}\r\n`
|
||||
body += `Content-Type: ${params.mimeType || 'application/octet-stream'}\r\n\r\n`
|
||||
body += params.content + '\r\n'
|
||||
|
||||
// Close boundary
|
||||
body += `--${boundary}--`
|
||||
|
||||
const headers: { [key: string]: string } = {
|
||||
Authorization: `Bearer ${this.accessToken}`,
|
||||
'Content-Type': `multipart/related; boundary="${boundary}"`,
|
||||
'Content-Length': Buffer.byteLength(body, 'utf8').toString()
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: body
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
console.error('Multipart upload failed:', {
|
||||
url,
|
||||
headers: { ...headers, Authorization: '[REDACTED]' },
|
||||
metadata: fileMetadata,
|
||||
contentLength: params.content?.length || 0,
|
||||
error: errorText
|
||||
})
|
||||
throw new Error(`Google Drive API Error ${response.status}: ${response.statusText} - ${errorText}`)
|
||||
}
|
||||
|
||||
const data = await response.text()
|
||||
return data + TOOL_ARGS_PREFIX + JSON.stringify(params)
|
||||
} catch (error) {
|
||||
throw new Error(`Multipart upload failed: ${error}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UpdateFileTool extends BaseGoogleDriveTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'update_file',
|
||||
description: 'Update file metadata in Google Drive',
|
||||
schema: UpdateFileSchema,
|
||||
baseUrl: '',
|
||||
method: 'PATCH',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const updateData: any = {}
|
||||
|
||||
if (params.name) updateData.name = params.name
|
||||
if (params.description) updateData.description = params.description
|
||||
if (params.starred !== undefined) updateData.starred = params.starred
|
||||
if (params.trashed !== undefined) updateData.trashed = params.trashed
|
||||
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.supportsAllDrives) queryParams.append('supportsAllDrives', params.supportsAllDrives.toString())
|
||||
|
||||
const endpoint = `files/${encodeURIComponent(params.fileId)}?${queryParams.toString()}`
|
||||
|
||||
const response = await this.makeGoogleDriveRequest({
|
||||
endpoint,
|
||||
method: 'PATCH',
|
||||
body: updateData,
|
||||
params
|
||||
})
|
||||
return response
|
||||
} catch (error) {
|
||||
return `Error updating file: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DeleteFileTool extends BaseGoogleDriveTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'delete_file',
|
||||
description: 'Delete a file from Google Drive',
|
||||
schema: DeleteFileSchema,
|
||||
baseUrl: '',
|
||||
method: 'DELETE',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.supportsAllDrives) queryParams.append('supportsAllDrives', params.supportsAllDrives.toString())
|
||||
|
||||
const endpoint = `files/${encodeURIComponent(params.fileId)}?${queryParams.toString()}`
|
||||
|
||||
await this.makeGoogleDriveRequest({
|
||||
endpoint,
|
||||
method: 'DELETE',
|
||||
params
|
||||
})
|
||||
return `File deleted successfully`
|
||||
} catch (error) {
|
||||
return `Error deleting file: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CopyFileTool extends BaseGoogleDriveTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'copy_file',
|
||||
description: 'Copy a file in Google Drive',
|
||||
schema: CopyFileSchema,
|
||||
baseUrl: '',
|
||||
method: 'POST',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const copyData: any = {
|
||||
name: params.name
|
||||
}
|
||||
|
||||
if (params.parents) {
|
||||
copyData.parents = params.parents.split(',').map((p: string) => p.trim())
|
||||
}
|
||||
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.supportsAllDrives) queryParams.append('supportsAllDrives', params.supportsAllDrives.toString())
|
||||
|
||||
const endpoint = `files/${encodeURIComponent(params.fileId)}/copy?${queryParams.toString()}`
|
||||
|
||||
const response = await this.makeGoogleDriveRequest({
|
||||
endpoint,
|
||||
method: 'POST',
|
||||
body: copyData,
|
||||
params
|
||||
})
|
||||
return response
|
||||
} catch (error) {
|
||||
return `Error copying file: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DownloadFileTool extends BaseGoogleDriveTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'download_file',
|
||||
description: 'Download a file from Google Drive',
|
||||
schema: DownloadFileSchema,
|
||||
baseUrl: '',
|
||||
method: 'GET',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const queryParams = new URLSearchParams()
|
||||
queryParams.append('alt', 'media')
|
||||
if (params.acknowledgeAbuse) queryParams.append('acknowledgeAbuse', params.acknowledgeAbuse.toString())
|
||||
if (params.supportsAllDrives) queryParams.append('supportsAllDrives', params.supportsAllDrives.toString())
|
||||
|
||||
const endpoint = `files/${encodeURIComponent(params.fileId)}?${queryParams.toString()}`
|
||||
|
||||
const response = await this.makeGoogleDriveRequest({ endpoint, params })
|
||||
return response
|
||||
} catch (error) {
|
||||
return `Error downloading file: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CreateFolderTool extends BaseGoogleDriveTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'create_folder',
|
||||
description: 'Create a new folder in Google Drive',
|
||||
schema: CreateFolderSchema,
|
||||
baseUrl: '',
|
||||
method: 'POST',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const folderData: any = {
|
||||
name: params.name,
|
||||
mimeType: 'application/vnd.google-apps.folder'
|
||||
}
|
||||
|
||||
if (params.parents) {
|
||||
folderData.parents = params.parents.split(',').map((p: string) => p.trim())
|
||||
}
|
||||
if (params.description) folderData.description = params.description
|
||||
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.supportsAllDrives) queryParams.append('supportsAllDrives', params.supportsAllDrives.toString())
|
||||
|
||||
const endpoint = `files?${queryParams.toString()}`
|
||||
|
||||
const response = await this.makeGoogleDriveRequest({
|
||||
endpoint,
|
||||
method: 'POST',
|
||||
body: folderData,
|
||||
params
|
||||
})
|
||||
return response
|
||||
} catch (error) {
|
||||
return `Error creating folder: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SearchFilesTool extends BaseGoogleDriveTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'search_files',
|
||||
description: 'Search files in Google Drive',
|
||||
schema: SearchFilesSchema,
|
||||
baseUrl: '',
|
||||
method: 'GET',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const queryParams = new URLSearchParams()
|
||||
queryParams.append('q', params.query)
|
||||
if (params.pageSize) queryParams.append('pageSize', params.pageSize.toString())
|
||||
if (params.orderBy) queryParams.append('orderBy', params.orderBy)
|
||||
if (params.includeItemsFromAllDrives)
|
||||
queryParams.append('includeItemsFromAllDrives', params.includeItemsFromAllDrives.toString())
|
||||
if (params.supportsAllDrives) queryParams.append('supportsAllDrives', params.supportsAllDrives.toString())
|
||||
|
||||
const endpoint = `files?${queryParams.toString()}`
|
||||
|
||||
const response = await this.makeGoogleDriveRequest({ endpoint, params })
|
||||
return response
|
||||
} catch (error) {
|
||||
return `Error searching files: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ShareFileTool extends BaseGoogleDriveTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'share_file',
|
||||
description: 'Share a file in Google Drive',
|
||||
schema: ShareFileSchema,
|
||||
baseUrl: '',
|
||||
method: 'POST',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const permissionData: any = {
|
||||
role: params.role,
|
||||
type: params.type
|
||||
}
|
||||
|
||||
if (params.emailAddress) permissionData.emailAddress = params.emailAddress
|
||||
if (params.domain) permissionData.domain = params.domain
|
||||
if (params.allowFileDiscovery !== undefined) permissionData.allowFileDiscovery = params.allowFileDiscovery
|
||||
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.sendNotificationEmail !== undefined)
|
||||
queryParams.append('sendNotificationEmail', params.sendNotificationEmail.toString())
|
||||
if (params.emailMessage) queryParams.append('emailMessage', params.emailMessage)
|
||||
if (params.supportsAllDrives) queryParams.append('supportsAllDrives', params.supportsAllDrives.toString())
|
||||
|
||||
const endpoint = `files/${encodeURIComponent(params.fileId)}/permissions?${queryParams.toString()}`
|
||||
|
||||
const response = await this.makeGoogleDriveRequest({
|
||||
endpoint,
|
||||
method: 'POST',
|
||||
body: permissionData,
|
||||
params
|
||||
})
|
||||
return response
|
||||
} catch (error) {
|
||||
return `Error sharing file: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ListFolderContentsTool extends BaseGoogleDriveTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'list_folder_contents',
|
||||
description: 'List contents of a specific folder in Google Drive',
|
||||
schema: z.object({
|
||||
folderId: z.string().describe('Folder ID to list contents from'),
|
||||
pageSize: z.number().optional().default(10).describe('Maximum number of files to return'),
|
||||
orderBy: z.string().optional().describe('Sort order'),
|
||||
includeItemsFromAllDrives: z.boolean().optional().describe('Include items from all drives'),
|
||||
supportsAllDrives: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether the requesting application supports both My Drives and shared drives')
|
||||
}),
|
||||
baseUrl: '',
|
||||
method: 'GET',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const queryParams = new URLSearchParams()
|
||||
queryParams.append('q', `'${params.folderId}' in parents`)
|
||||
if (params.pageSize) queryParams.append('pageSize', params.pageSize.toString())
|
||||
if (params.orderBy) queryParams.append('orderBy', params.orderBy)
|
||||
if (params.includeItemsFromAllDrives)
|
||||
queryParams.append('includeItemsFromAllDrives', params.includeItemsFromAllDrives.toString())
|
||||
if (params.supportsAllDrives) queryParams.append('supportsAllDrives', params.supportsAllDrives.toString())
|
||||
|
||||
const endpoint = `files?${queryParams.toString()}`
|
||||
|
||||
const response = await this.makeGoogleDriveRequest({ endpoint, params })
|
||||
return response
|
||||
} catch (error) {
|
||||
return `Error listing folder contents: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DeleteFolderTool extends BaseGoogleDriveTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'delete_folder',
|
||||
description: 'Delete a folder from Google Drive',
|
||||
schema: z.object({
|
||||
folderId: z.string().describe('Folder ID to delete'),
|
||||
supportsAllDrives: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether the requesting application supports both My Drives and shared drives')
|
||||
}),
|
||||
baseUrl: '',
|
||||
method: 'DELETE',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.supportsAllDrives) queryParams.append('supportsAllDrives', params.supportsAllDrives.toString())
|
||||
|
||||
const endpoint = `files/${encodeURIComponent(params.folderId)}?${queryParams.toString()}`
|
||||
|
||||
await this.makeGoogleDriveRequest({
|
||||
endpoint,
|
||||
method: 'DELETE',
|
||||
params
|
||||
})
|
||||
return `Folder deleted successfully`
|
||||
} catch (error) {
|
||||
return `Error deleting folder: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GetPermissionsTool extends BaseGoogleDriveTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'get_permissions',
|
||||
description: 'Get permissions for a file in Google Drive',
|
||||
schema: z.object({
|
||||
fileId: z.string().describe('File ID to get permissions for'),
|
||||
supportsAllDrives: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether the requesting application supports both My Drives and shared drives')
|
||||
}),
|
||||
baseUrl: '',
|
||||
method: 'GET',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.supportsAllDrives) queryParams.append('supportsAllDrives', params.supportsAllDrives.toString())
|
||||
|
||||
const endpoint = `files/${encodeURIComponent(params.fileId)}/permissions?${queryParams.toString()}`
|
||||
|
||||
const response = await this.makeGoogleDriveRequest({ endpoint, params })
|
||||
return response
|
||||
} catch (error) {
|
||||
return `Error getting permissions: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RemovePermissionTool extends BaseGoogleDriveTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'remove_permission',
|
||||
description: 'Remove a permission from a file in Google Drive',
|
||||
schema: z.object({
|
||||
fileId: z.string().describe('File ID to remove permission from'),
|
||||
permissionId: z.string().describe('Permission ID to remove'),
|
||||
supportsAllDrives: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether the requesting application supports both My Drives and shared drives')
|
||||
}),
|
||||
baseUrl: '',
|
||||
method: 'DELETE',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
try {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.supportsAllDrives) queryParams.append('supportsAllDrives', params.supportsAllDrives.toString())
|
||||
|
||||
const endpoint = `files/${encodeURIComponent(params.fileId)}/permissions/${encodeURIComponent(
|
||||
params.permissionId
|
||||
)}?${queryParams.toString()}`
|
||||
|
||||
await this.makeGoogleDriveRequest({
|
||||
endpoint,
|
||||
method: 'DELETE',
|
||||
params
|
||||
})
|
||||
return `Permission removed successfully`
|
||||
} catch (error) {
|
||||
return `Error removing permission: ${error}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const createGoogleDriveTools = (args?: RequestParameters): DynamicStructuredTool[] => {
|
||||
const tools: DynamicStructuredTool[] = []
|
||||
const actions = args?.actions || []
|
||||
const accessToken = args?.accessToken || ''
|
||||
const defaultParams = args?.defaultParams || {}
|
||||
|
||||
if (actions.includes('listFiles')) {
|
||||
tools.push(new ListFilesTool({ accessToken, defaultParams }))
|
||||
}
|
||||
|
||||
if (actions.includes('getFile')) {
|
||||
tools.push(new GetFileTool({ accessToken, defaultParams }))
|
||||
}
|
||||
|
||||
if (actions.includes('createFile')) {
|
||||
tools.push(new CreateFileTool({ accessToken, defaultParams }))
|
||||
}
|
||||
|
||||
if (actions.includes('updateFile')) {
|
||||
tools.push(new UpdateFileTool({ accessToken, defaultParams }))
|
||||
}
|
||||
|
||||
if (actions.includes('deleteFile')) {
|
||||
tools.push(new DeleteFileTool({ accessToken, defaultParams }))
|
||||
}
|
||||
|
||||
if (actions.includes('copyFile')) {
|
||||
tools.push(new CopyFileTool({ accessToken, defaultParams }))
|
||||
}
|
||||
|
||||
if (actions.includes('downloadFile')) {
|
||||
tools.push(new DownloadFileTool({ accessToken, defaultParams }))
|
||||
}
|
||||
|
||||
if (actions.includes('createFolder')) {
|
||||
tools.push(new CreateFolderTool({ accessToken, defaultParams }))
|
||||
}
|
||||
|
||||
if (actions.includes('listFolderContents')) {
|
||||
tools.push(new ListFolderContentsTool({ accessToken, defaultParams }))
|
||||
}
|
||||
|
||||
if (actions.includes('deleteFolder')) {
|
||||
tools.push(new DeleteFolderTool({ accessToken, defaultParams }))
|
||||
}
|
||||
|
||||
if (actions.includes('searchFiles')) {
|
||||
tools.push(new SearchFilesTool({ accessToken, defaultParams }))
|
||||
}
|
||||
|
||||
if (actions.includes('shareFile')) {
|
||||
tools.push(new ShareFileTool({ accessToken, defaultParams }))
|
||||
}
|
||||
|
||||
if (actions.includes('getPermissions')) {
|
||||
tools.push(new GetPermissionsTool({ accessToken, defaultParams }))
|
||||
}
|
||||
|
||||
if (actions.includes('removePermission')) {
|
||||
tools.push(new RemovePermissionTool({ accessToken, defaultParams }))
|
||||
}
|
||||
|
||||
return tools
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><path fill="#1e88e5" d="M38.59,39c-0.535,0.93-0.298,1.68-1.195,2.197C36.498,41.715,35.465,42,34.39,42H13.61 c-1.074,0-2.106-0.285-3.004-0.802C9.708,40.681,9.945,39.93,9.41,39l7.67-9h13.84L38.59,39z"/><path fill="#fbc02d" d="M27.463,6.999c1.073-0.002,2.104-0.716,3.001-0.198c0.897,0.519,1.66,1.27,2.197,2.201l10.39,17.996 c0.537,0.93,0.807,1.967,0.808,3.002c0.001,1.037-1.267,2.073-1.806,3.001l-11.127-3.005l-6.924-11.993L27.463,6.999z"/><path fill="#e53935" d="M43.86,30c0,1.04-0.27,2.07-0.81,3l-3.67,6.35c-0.53,0.78-1.21,1.4-1.99,1.85L30.92,30H43.86z"/><path fill="#4caf50" d="M5.947,33.001c-0.538-0.928-1.806-1.964-1.806-3c0.001-1.036,0.27-2.073,0.808-3.004l10.39-17.996 c0.537-0.93,1.3-1.682,2.196-2.2c0.897-0.519,1.929,0.195,3.002,0.197l3.459,11.009l-6.922,11.989L5.947,33.001z"/><path fill="#1565c0" d="M17.08,30l-6.47,11.2c-0.78-0.45-1.46-1.07-1.99-1.85L4.95,33c-0.54-0.93-0.81-1.96-0.81-3H17.08z"/><path fill="#2e7d32" d="M30.46,6.8L24,18L17.53,6.8c0.78-0.45,1.66-0.73,2.6-0.79L27.46,6C28.54,6,29.57,6.28,30.46,6.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,424 @@
|
||||
import { convertMultiOptionsToStringArray, getCredentialData, getCredentialParam, refreshOAuth2Token } from '../../../src/utils'
|
||||
import { createGoogleSheetsTools } from './core'
|
||||
import type { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
|
||||
class GoogleSheets_Tools implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
description: string
|
||||
baseClasses: string[]
|
||||
credential: INodeParams
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Google Sheets'
|
||||
this.name = 'googleSheetsTool'
|
||||
this.version = 1.0
|
||||
this.type = 'GoogleSheets'
|
||||
this.icon = 'google-sheets.svg'
|
||||
this.category = 'Tools'
|
||||
this.description = 'Perform Google Sheets operations such as managing spreadsheets, reading and writing values'
|
||||
this.baseClasses = ['Tool']
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['googleSheetsOAuth2']
|
||||
}
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Type',
|
||||
name: 'sheetsType',
|
||||
type: 'options',
|
||||
description: 'Type of Google Sheets operation',
|
||||
options: [
|
||||
{
|
||||
label: 'Spreadsheet',
|
||||
name: 'spreadsheet'
|
||||
},
|
||||
{
|
||||
label: 'Values',
|
||||
name: 'values'
|
||||
}
|
||||
]
|
||||
},
|
||||
// Spreadsheet Actions
|
||||
{
|
||||
label: 'Spreadsheet Actions',
|
||||
name: 'spreadsheetActions',
|
||||
type: 'multiOptions',
|
||||
description: 'Actions to perform on spreadsheets',
|
||||
options: [
|
||||
{
|
||||
label: 'Create Spreadsheet',
|
||||
name: 'createSpreadsheet'
|
||||
},
|
||||
{
|
||||
label: 'Get Spreadsheet',
|
||||
name: 'getSpreadsheet'
|
||||
},
|
||||
{
|
||||
label: 'Update Spreadsheet',
|
||||
name: 'updateSpreadsheet'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
sheetsType: ['spreadsheet']
|
||||
}
|
||||
},
|
||||
// Values Actions
|
||||
{
|
||||
label: 'Values Actions',
|
||||
name: 'valuesActions',
|
||||
type: 'multiOptions',
|
||||
description: 'Actions to perform on sheet values',
|
||||
options: [
|
||||
{
|
||||
label: 'Get Values',
|
||||
name: 'getValues'
|
||||
},
|
||||
{
|
||||
label: 'Update Values',
|
||||
name: 'updateValues'
|
||||
},
|
||||
{
|
||||
label: 'Append Values',
|
||||
name: 'appendValues'
|
||||
},
|
||||
{
|
||||
label: 'Clear Values',
|
||||
name: 'clearValues'
|
||||
},
|
||||
{
|
||||
label: 'Batch Get Values',
|
||||
name: 'batchGetValues'
|
||||
},
|
||||
{
|
||||
label: 'Batch Update Values',
|
||||
name: 'batchUpdateValues'
|
||||
},
|
||||
{
|
||||
label: 'Batch Clear Values',
|
||||
name: 'batchClearValues'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
sheetsType: ['values']
|
||||
}
|
||||
},
|
||||
// Spreadsheet Parameters
|
||||
{
|
||||
label: 'Spreadsheet ID',
|
||||
name: 'spreadsheetId',
|
||||
type: 'string',
|
||||
description: 'The ID of the spreadsheet',
|
||||
show: {
|
||||
sheetsType: ['spreadsheet', 'values']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Title',
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
description: 'The title of the spreadsheet',
|
||||
show: {
|
||||
spreadsheetActions: ['createSpreadsheet', 'updateSpreadsheet']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Sheet Count',
|
||||
name: 'sheetCount',
|
||||
type: 'number',
|
||||
description: 'Number of sheets to create',
|
||||
default: 1,
|
||||
show: {
|
||||
spreadsheetActions: ['createSpreadsheet']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Values Parameters
|
||||
{
|
||||
label: 'Range',
|
||||
name: 'range',
|
||||
type: 'string',
|
||||
description: 'The range to read/write (e.g., A1:B2, Sheet1!A1:C10)',
|
||||
show: {
|
||||
valuesActions: ['getValues', 'updateValues', 'clearValues']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Ranges',
|
||||
name: 'ranges',
|
||||
type: 'string',
|
||||
description: 'Comma-separated list of ranges for batch operations',
|
||||
show: {
|
||||
valuesActions: ['batchGetValues', 'batchClearValues']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Values',
|
||||
name: 'values',
|
||||
type: 'string',
|
||||
description: 'JSON array of values to write (e.g., [["A1", "B1"], ["A2", "B2"]])',
|
||||
show: {
|
||||
valuesActions: ['updateValues', 'appendValues', 'batchUpdateValues']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Value Input Option',
|
||||
name: 'valueInputOption',
|
||||
type: 'options',
|
||||
description: 'How input data should be interpreted',
|
||||
options: [
|
||||
{
|
||||
label: 'Raw',
|
||||
name: 'RAW'
|
||||
},
|
||||
{
|
||||
label: 'User Entered',
|
||||
name: 'USER_ENTERED'
|
||||
}
|
||||
],
|
||||
default: 'USER_ENTERED',
|
||||
show: {
|
||||
valuesActions: ['updateValues', 'appendValues', 'batchUpdateValues']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Value Render Option',
|
||||
name: 'valueRenderOption',
|
||||
type: 'options',
|
||||
description: 'How values should be represented in the output',
|
||||
options: [
|
||||
{
|
||||
label: 'Formatted Value',
|
||||
name: 'FORMATTED_VALUE'
|
||||
},
|
||||
{
|
||||
label: 'Unformatted Value',
|
||||
name: 'UNFORMATTED_VALUE'
|
||||
},
|
||||
{
|
||||
label: 'Formula',
|
||||
name: 'FORMULA'
|
||||
}
|
||||
],
|
||||
default: 'FORMATTED_VALUE',
|
||||
show: {
|
||||
valuesActions: ['getValues', 'batchGetValues']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Date Time Render Option',
|
||||
name: 'dateTimeRenderOption',
|
||||
type: 'options',
|
||||
description: 'How dates, times, and durations should be represented',
|
||||
options: [
|
||||
{
|
||||
label: 'Serial Number',
|
||||
name: 'SERIAL_NUMBER'
|
||||
},
|
||||
{
|
||||
label: 'Formatted String',
|
||||
name: 'FORMATTED_STRING'
|
||||
}
|
||||
],
|
||||
default: 'FORMATTED_STRING',
|
||||
show: {
|
||||
valuesActions: ['getValues', 'batchGetValues']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Insert Data Option',
|
||||
name: 'insertDataOption',
|
||||
type: 'options',
|
||||
description: 'How data should be inserted',
|
||||
options: [
|
||||
{
|
||||
label: 'Overwrite',
|
||||
name: 'OVERWRITE'
|
||||
},
|
||||
{
|
||||
label: 'Insert Rows',
|
||||
name: 'INSERT_ROWS'
|
||||
}
|
||||
],
|
||||
default: 'OVERWRITE',
|
||||
show: {
|
||||
valuesActions: ['appendValues']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Include Grid Data',
|
||||
name: 'includeGridData',
|
||||
type: 'boolean',
|
||||
description: 'True if grid data should be returned',
|
||||
default: false,
|
||||
show: {
|
||||
spreadsheetActions: ['getSpreadsheet']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Major Dimension',
|
||||
name: 'majorDimension',
|
||||
type: 'options',
|
||||
description: 'The major dimension that results should use',
|
||||
options: [
|
||||
{
|
||||
label: 'Rows',
|
||||
name: 'ROWS'
|
||||
},
|
||||
{
|
||||
label: 'Columns',
|
||||
name: 'COLUMNS'
|
||||
}
|
||||
],
|
||||
default: 'ROWS',
|
||||
show: {
|
||||
valuesActions: ['getValues', 'updateValues', 'appendValues', 'batchGetValues', 'batchUpdateValues']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
const sheetsType = nodeData.inputs?.sheetsType as string
|
||||
|
||||
let credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
credentialData = await refreshOAuth2Token(nodeData.credential ?? '', credentialData, options)
|
||||
const accessToken = getCredentialParam('access_token', credentialData, nodeData)
|
||||
|
||||
if (!accessToken) {
|
||||
throw new Error('No access token found in credential')
|
||||
}
|
||||
|
||||
// Get all actions based on type
|
||||
let actions: string[] = []
|
||||
|
||||
if (sheetsType === 'spreadsheet') {
|
||||
actions = convertMultiOptionsToStringArray(nodeData.inputs?.spreadsheetActions)
|
||||
} else if (sheetsType === 'values') {
|
||||
actions = convertMultiOptionsToStringArray(nodeData.inputs?.valuesActions)
|
||||
}
|
||||
|
||||
// Create default params object based on inputs
|
||||
const defaultParams: any = {}
|
||||
|
||||
// Spreadsheet-specific default params
|
||||
if (sheetsType === 'spreadsheet') {
|
||||
actions.forEach((action) => {
|
||||
const params: any = {}
|
||||
|
||||
// Common spreadsheet parameters
|
||||
if (nodeData.inputs?.spreadsheetId) params.spreadsheetId = nodeData.inputs.spreadsheetId
|
||||
|
||||
if (action === 'createSpreadsheet') {
|
||||
if (nodeData.inputs?.title) params.title = nodeData.inputs.title
|
||||
if (nodeData.inputs?.sheetCount) params.sheetCount = nodeData.inputs.sheetCount
|
||||
}
|
||||
|
||||
if (action === 'getSpreadsheet') {
|
||||
if (nodeData.inputs?.ranges) params.ranges = nodeData.inputs.ranges
|
||||
if (nodeData.inputs?.includeGridData !== undefined) params.includeGridData = nodeData.inputs.includeGridData
|
||||
}
|
||||
|
||||
if (action === 'updateSpreadsheet') {
|
||||
if (nodeData.inputs?.title) params.title = nodeData.inputs.title
|
||||
}
|
||||
|
||||
defaultParams[action] = params
|
||||
})
|
||||
}
|
||||
|
||||
// Values-specific default params
|
||||
if (sheetsType === 'values') {
|
||||
actions.forEach((action) => {
|
||||
const params: any = {}
|
||||
|
||||
// Common values parameters
|
||||
if (nodeData.inputs?.spreadsheetId) params.spreadsheetId = nodeData.inputs.spreadsheetId
|
||||
|
||||
if (action === 'getValues') {
|
||||
if (nodeData.inputs?.range) params.range = nodeData.inputs.range
|
||||
if (nodeData.inputs?.valueRenderOption) params.valueRenderOption = nodeData.inputs.valueRenderOption
|
||||
if (nodeData.inputs?.dateTimeRenderOption) params.dateTimeRenderOption = nodeData.inputs.dateTimeRenderOption
|
||||
if (nodeData.inputs?.majorDimension) params.majorDimension = nodeData.inputs.majorDimension
|
||||
}
|
||||
|
||||
if (action === 'updateValues') {
|
||||
if (nodeData.inputs?.range) params.range = nodeData.inputs.range
|
||||
if (nodeData.inputs?.values) params.values = nodeData.inputs.values
|
||||
if (nodeData.inputs?.valueInputOption) params.valueInputOption = nodeData.inputs.valueInputOption
|
||||
if (nodeData.inputs?.majorDimension) params.majorDimension = nodeData.inputs.majorDimension
|
||||
}
|
||||
|
||||
if (action === 'appendValues') {
|
||||
if (nodeData.inputs?.range) params.range = nodeData.inputs.range
|
||||
if (nodeData.inputs?.values) params.values = nodeData.inputs.values
|
||||
if (nodeData.inputs?.valueInputOption) params.valueInputOption = nodeData.inputs.valueInputOption
|
||||
if (nodeData.inputs?.insertDataOption) params.insertDataOption = nodeData.inputs.insertDataOption
|
||||
if (nodeData.inputs?.majorDimension) params.majorDimension = nodeData.inputs.majorDimension
|
||||
}
|
||||
|
||||
if (action === 'clearValues') {
|
||||
if (nodeData.inputs?.range) params.range = nodeData.inputs.range
|
||||
}
|
||||
|
||||
if (action === 'batchGetValues') {
|
||||
if (nodeData.inputs?.ranges) params.ranges = nodeData.inputs.ranges
|
||||
if (nodeData.inputs?.valueRenderOption) params.valueRenderOption = nodeData.inputs.valueRenderOption
|
||||
if (nodeData.inputs?.dateTimeRenderOption) params.dateTimeRenderOption = nodeData.inputs.dateTimeRenderOption
|
||||
if (nodeData.inputs?.majorDimension) params.majorDimension = nodeData.inputs.majorDimension
|
||||
}
|
||||
|
||||
if (action === 'batchUpdateValues') {
|
||||
if (nodeData.inputs?.values) params.values = nodeData.inputs.values
|
||||
if (nodeData.inputs?.valueInputOption) params.valueInputOption = nodeData.inputs.valueInputOption
|
||||
}
|
||||
|
||||
if (action === 'batchClearValues') {
|
||||
if (nodeData.inputs?.ranges) params.ranges = nodeData.inputs.ranges
|
||||
}
|
||||
|
||||
defaultParams[action] = params
|
||||
})
|
||||
}
|
||||
|
||||
const tools = createGoogleSheetsTools({
|
||||
accessToken,
|
||||
actions,
|
||||
defaultParams
|
||||
})
|
||||
|
||||
return tools
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: GoogleSheets_Tools }
|
||||
@@ -0,0 +1,631 @@
|
||||
import { z } from 'zod'
|
||||
import fetch from 'node-fetch'
|
||||
import { DynamicStructuredTool } from '../OpenAPIToolkit/core'
|
||||
import { TOOL_ARGS_PREFIX } from '../../../src/agents'
|
||||
|
||||
export const desc = `Use this when you want to access Google Sheets API for managing spreadsheets and values`
|
||||
|
||||
export interface Headers {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
export interface Body {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface RequestParameters {
|
||||
headers?: Headers
|
||||
body?: Body
|
||||
url?: string
|
||||
description?: string
|
||||
name?: string
|
||||
actions?: string[]
|
||||
accessToken?: string
|
||||
defaultParams?: any
|
||||
}
|
||||
|
||||
// Define schemas for different Google Sheets operations
|
||||
|
||||
// Spreadsheet Schemas
|
||||
const CreateSpreadsheetSchema = z.object({
|
||||
title: z.string().describe('The title of the spreadsheet'),
|
||||
sheetCount: z.number().optional().default(1).describe('Number of sheets to create'),
|
||||
locale: z.string().optional().describe('The locale of the spreadsheet (e.g., en_US)'),
|
||||
timeZone: z.string().optional().describe('The time zone of the spreadsheet (e.g., America/New_York)')
|
||||
})
|
||||
|
||||
const GetSpreadsheetSchema = z.object({
|
||||
spreadsheetId: z.string().describe('The ID of the spreadsheet to retrieve'),
|
||||
ranges: z.string().optional().describe('Comma-separated list of ranges to retrieve'),
|
||||
includeGridData: z.boolean().optional().default(false).describe('True if grid data should be returned')
|
||||
})
|
||||
|
||||
const UpdateSpreadsheetSchema = z.object({
|
||||
spreadsheetId: z.string().describe('The ID of the spreadsheet to update'),
|
||||
title: z.string().optional().describe('New title for the spreadsheet'),
|
||||
locale: z.string().optional().describe('New locale for the spreadsheet'),
|
||||
timeZone: z.string().optional().describe('New time zone for the spreadsheet')
|
||||
})
|
||||
|
||||
// Values Schemas
|
||||
const GetValuesSchema = z.object({
|
||||
spreadsheetId: z.string().describe('The ID of the spreadsheet'),
|
||||
range: z.string().describe('The A1 notation of the range to retrieve values from'),
|
||||
valueRenderOption: z
|
||||
.enum(['FORMATTED_VALUE', 'UNFORMATTED_VALUE', 'FORMULA'])
|
||||
.optional()
|
||||
.default('FORMATTED_VALUE')
|
||||
.describe('How values should be represented'),
|
||||
dateTimeRenderOption: z
|
||||
.enum(['SERIAL_NUMBER', 'FORMATTED_STRING'])
|
||||
.optional()
|
||||
.default('FORMATTED_STRING')
|
||||
.describe('How dates should be represented'),
|
||||
majorDimension: z.enum(['ROWS', 'COLUMNS']).optional().default('ROWS').describe('The major dimension that results should use')
|
||||
})
|
||||
|
||||
const UpdateValuesSchema = z.object({
|
||||
spreadsheetId: z.string().describe('The ID of the spreadsheet'),
|
||||
range: z.string().describe('The A1 notation of the range to update'),
|
||||
values: z.string().describe('JSON array of values to write (e.g., [["A1", "B1"], ["A2", "B2"]])'),
|
||||
valueInputOption: z.enum(['RAW', 'USER_ENTERED']).optional().default('USER_ENTERED').describe('How input data should be interpreted'),
|
||||
majorDimension: z.enum(['ROWS', 'COLUMNS']).optional().default('ROWS').describe('The major dimension of the values')
|
||||
})
|
||||
|
||||
const AppendValuesSchema = z.object({
|
||||
spreadsheetId: z.string().describe('The ID of the spreadsheet'),
|
||||
range: z.string().describe('The A1 notation of the range to append to'),
|
||||
values: z.string().describe('JSON array of values to append'),
|
||||
valueInputOption: z.enum(['RAW', 'USER_ENTERED']).optional().default('USER_ENTERED').describe('How input data should be interpreted'),
|
||||
insertDataOption: z.enum(['OVERWRITE', 'INSERT_ROWS']).optional().default('OVERWRITE').describe('How data should be inserted'),
|
||||
majorDimension: z.enum(['ROWS', 'COLUMNS']).optional().default('ROWS').describe('The major dimension of the values')
|
||||
})
|
||||
|
||||
const ClearValuesSchema = z.object({
|
||||
spreadsheetId: z.string().describe('The ID of the spreadsheet'),
|
||||
range: z.string().describe('The A1 notation of the range to clear')
|
||||
})
|
||||
|
||||
const BatchGetValuesSchema = z.object({
|
||||
spreadsheetId: z.string().describe('The ID of the spreadsheet'),
|
||||
ranges: z.string().describe('Comma-separated list of ranges to retrieve'),
|
||||
valueRenderOption: z
|
||||
.enum(['FORMATTED_VALUE', 'UNFORMATTED_VALUE', 'FORMULA'])
|
||||
.optional()
|
||||
.default('FORMATTED_VALUE')
|
||||
.describe('How values should be represented'),
|
||||
dateTimeRenderOption: z
|
||||
.enum(['SERIAL_NUMBER', 'FORMATTED_STRING'])
|
||||
.optional()
|
||||
.default('FORMATTED_STRING')
|
||||
.describe('How dates should be represented'),
|
||||
majorDimension: z.enum(['ROWS', 'COLUMNS']).optional().default('ROWS').describe('The major dimension that results should use')
|
||||
})
|
||||
|
||||
const BatchUpdateValuesSchema = z.object({
|
||||
spreadsheetId: z.string().describe('The ID of the spreadsheet'),
|
||||
valueInputOption: z.enum(['RAW', 'USER_ENTERED']).optional().default('USER_ENTERED').describe('How input data should be interpreted'),
|
||||
values: z
|
||||
.string()
|
||||
.describe('JSON array of value ranges to update (e.g., [{"range": "A1:B2", "values": [["A1", "B1"], ["A2", "B2"]]}])'),
|
||||
includeValuesInResponse: z.boolean().optional().default(false).describe('Whether to return the updated values in the response')
|
||||
})
|
||||
|
||||
const BatchClearValuesSchema = z.object({
|
||||
spreadsheetId: z.string().describe('The ID of the spreadsheet'),
|
||||
ranges: z.string().describe('Comma-separated list of ranges to clear')
|
||||
})
|
||||
|
||||
class BaseGoogleSheetsTool extends DynamicStructuredTool {
|
||||
protected accessToken: string = ''
|
||||
|
||||
constructor(args: any) {
|
||||
super(args)
|
||||
this.accessToken = args.accessToken ?? ''
|
||||
}
|
||||
|
||||
async makeGoogleSheetsRequest({
|
||||
endpoint,
|
||||
method = 'GET',
|
||||
body,
|
||||
params
|
||||
}: {
|
||||
endpoint: string
|
||||
method?: string
|
||||
body?: any
|
||||
params?: any
|
||||
}): Promise<string> {
|
||||
const url = `https://sheets.googleapis.com/v4/${endpoint}`
|
||||
|
||||
const headers = {
|
||||
Authorization: `Bearer ${this.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
...this.headers
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers,
|
||||
body: body ? JSON.stringify(body) : undefined
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Google Sheets API Error ${response.status}: ${response.statusText} - ${errorText}`)
|
||||
}
|
||||
|
||||
const data = await response.text()
|
||||
return data + TOOL_ARGS_PREFIX + JSON.stringify(params)
|
||||
}
|
||||
}
|
||||
|
||||
// Spreadsheet Tools
|
||||
class CreateSpreadsheetTool extends BaseGoogleSheetsTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'create_spreadsheet',
|
||||
description: 'Create a new Google Spreadsheet',
|
||||
schema: CreateSpreadsheetSchema,
|
||||
baseUrl: '',
|
||||
method: 'POST',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
const body: any = {
|
||||
properties: {
|
||||
title: params.title
|
||||
}
|
||||
}
|
||||
|
||||
if (params.locale) body.properties.locale = params.locale
|
||||
if (params.timeZone) body.properties.timeZone = params.timeZone
|
||||
|
||||
// Add sheets if specified
|
||||
if (params.sheetCount && params.sheetCount > 1) {
|
||||
body.sheets = []
|
||||
for (let i = 0; i < params.sheetCount; i++) {
|
||||
body.sheets.push({
|
||||
properties: {
|
||||
title: i === 0 ? 'Sheet1' : `Sheet${i + 1}`
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return await this.makeGoogleSheetsRequest({
|
||||
endpoint: 'spreadsheets',
|
||||
method: 'POST',
|
||||
body,
|
||||
params
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class GetSpreadsheetTool extends BaseGoogleSheetsTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'get_spreadsheet',
|
||||
description: 'Get a Google Spreadsheet by ID',
|
||||
schema: GetSpreadsheetSchema,
|
||||
baseUrl: '',
|
||||
method: 'GET',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.ranges) {
|
||||
params.ranges.split(',').forEach((range: string) => {
|
||||
queryParams.append('ranges', range.trim())
|
||||
})
|
||||
}
|
||||
if (params.includeGridData) queryParams.append('includeGridData', 'true')
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
const endpoint = `spreadsheets/${params.spreadsheetId}${queryString ? `?${queryString}` : ''}`
|
||||
|
||||
return await this.makeGoogleSheetsRequest({
|
||||
endpoint,
|
||||
method: 'GET',
|
||||
params
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class UpdateSpreadsheetTool extends BaseGoogleSheetsTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'update_spreadsheet',
|
||||
description: 'Update a Google Spreadsheet properties',
|
||||
schema: UpdateSpreadsheetSchema,
|
||||
baseUrl: '',
|
||||
method: 'POST',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
const requests = []
|
||||
if (params.title || params.locale || params.timeZone) {
|
||||
const updateProperties: any = {}
|
||||
if (params.title) updateProperties.title = params.title
|
||||
if (params.locale) updateProperties.locale = params.locale
|
||||
if (params.timeZone) updateProperties.timeZone = params.timeZone
|
||||
|
||||
requests.push({
|
||||
updateSpreadsheetProperties: {
|
||||
properties: updateProperties,
|
||||
fields: Object.keys(updateProperties).join(',')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const body = { requests }
|
||||
|
||||
return await this.makeGoogleSheetsRequest({
|
||||
endpoint: `spreadsheets/${params.spreadsheetId}:batchUpdate`,
|
||||
method: 'POST',
|
||||
body,
|
||||
params
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Values Tools
|
||||
class GetValuesTool extends BaseGoogleSheetsTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'get_values',
|
||||
description: 'Get values from a Google Spreadsheet range',
|
||||
schema: GetValuesSchema,
|
||||
baseUrl: '',
|
||||
method: 'GET',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.valueRenderOption) queryParams.append('valueRenderOption', params.valueRenderOption)
|
||||
if (params.dateTimeRenderOption) queryParams.append('dateTimeRenderOption', params.dateTimeRenderOption)
|
||||
if (params.majorDimension) queryParams.append('majorDimension', params.majorDimension)
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
const encodedRange = encodeURIComponent(params.range)
|
||||
const endpoint = `spreadsheets/${params.spreadsheetId}/values/${encodedRange}${queryString ? `?${queryString}` : ''}`
|
||||
|
||||
return await this.makeGoogleSheetsRequest({
|
||||
endpoint,
|
||||
method: 'GET',
|
||||
params
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class UpdateValuesTool extends BaseGoogleSheetsTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'update_values',
|
||||
description: 'Update values in a Google Spreadsheet range',
|
||||
schema: UpdateValuesSchema,
|
||||
baseUrl: '',
|
||||
method: 'PUT',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
let values
|
||||
try {
|
||||
values = JSON.parse(params.values)
|
||||
} catch (error) {
|
||||
throw new Error('Values must be a valid JSON array')
|
||||
}
|
||||
|
||||
const body = {
|
||||
values,
|
||||
majorDimension: params.majorDimension || 'ROWS'
|
||||
}
|
||||
|
||||
const queryParams = new URLSearchParams()
|
||||
queryParams.append('valueInputOption', params.valueInputOption || 'USER_ENTERED')
|
||||
|
||||
const encodedRange = encodeURIComponent(params.range)
|
||||
const endpoint = `spreadsheets/${params.spreadsheetId}/values/${encodedRange}?${queryParams.toString()}`
|
||||
|
||||
return await this.makeGoogleSheetsRequest({
|
||||
endpoint,
|
||||
method: 'PUT',
|
||||
body,
|
||||
params
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class AppendValuesTool extends BaseGoogleSheetsTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'append_values',
|
||||
description: 'Append values to a Google Spreadsheet range',
|
||||
schema: AppendValuesSchema,
|
||||
baseUrl: '',
|
||||
method: 'POST',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
let values
|
||||
try {
|
||||
values = JSON.parse(params.values)
|
||||
} catch (error) {
|
||||
throw new Error('Values must be a valid JSON array')
|
||||
}
|
||||
|
||||
const body = {
|
||||
values,
|
||||
majorDimension: params.majorDimension || 'ROWS'
|
||||
}
|
||||
|
||||
const queryParams = new URLSearchParams()
|
||||
queryParams.append('valueInputOption', params.valueInputOption || 'USER_ENTERED')
|
||||
queryParams.append('insertDataOption', params.insertDataOption || 'OVERWRITE')
|
||||
|
||||
const encodedRange = encodeURIComponent(params.range)
|
||||
const endpoint = `spreadsheets/${params.spreadsheetId}/values/${encodedRange}:append?${queryParams.toString()}`
|
||||
|
||||
return await this.makeGoogleSheetsRequest({
|
||||
endpoint,
|
||||
method: 'POST',
|
||||
body,
|
||||
params
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class ClearValuesTool extends BaseGoogleSheetsTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'clear_values',
|
||||
description: 'Clear values from a Google Spreadsheet range',
|
||||
schema: ClearValuesSchema,
|
||||
baseUrl: '',
|
||||
method: 'POST',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
const encodedRange = encodeURIComponent(params.range)
|
||||
const endpoint = `spreadsheets/${params.spreadsheetId}/values/${encodedRange}:clear`
|
||||
|
||||
return await this.makeGoogleSheetsRequest({
|
||||
endpoint,
|
||||
method: 'POST',
|
||||
body: {},
|
||||
params
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class BatchGetValuesTool extends BaseGoogleSheetsTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'batch_get_values',
|
||||
description: 'Get values from multiple Google Spreadsheet ranges',
|
||||
schema: BatchGetValuesSchema,
|
||||
baseUrl: '',
|
||||
method: 'GET',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
// Add ranges
|
||||
params.ranges.split(',').forEach((range: string) => {
|
||||
queryParams.append('ranges', range.trim())
|
||||
})
|
||||
|
||||
if (params.valueRenderOption) queryParams.append('valueRenderOption', params.valueRenderOption)
|
||||
if (params.dateTimeRenderOption) queryParams.append('dateTimeRenderOption', params.dateTimeRenderOption)
|
||||
if (params.majorDimension) queryParams.append('majorDimension', params.majorDimension)
|
||||
|
||||
const endpoint = `spreadsheets/${params.spreadsheetId}/values:batchGet?${queryParams.toString()}`
|
||||
|
||||
return await this.makeGoogleSheetsRequest({
|
||||
endpoint,
|
||||
method: 'GET',
|
||||
params
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class BatchUpdateValuesTool extends BaseGoogleSheetsTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'batch_update_values',
|
||||
description: 'Update values in multiple Google Spreadsheet ranges',
|
||||
schema: BatchUpdateValuesSchema,
|
||||
baseUrl: '',
|
||||
method: 'POST',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
let valueRanges
|
||||
try {
|
||||
valueRanges = JSON.parse(params.values)
|
||||
} catch (error) {
|
||||
throw new Error('Values must be a valid JSON array of value ranges')
|
||||
}
|
||||
|
||||
const body = {
|
||||
valueInputOption: params.valueInputOption || 'USER_ENTERED',
|
||||
data: valueRanges,
|
||||
includeValuesInResponse: params.includeValuesInResponse || false
|
||||
}
|
||||
|
||||
const endpoint = `spreadsheets/${params.spreadsheetId}/values:batchUpdate`
|
||||
|
||||
return await this.makeGoogleSheetsRequest({
|
||||
endpoint,
|
||||
method: 'POST',
|
||||
body,
|
||||
params
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class BatchClearValuesTool extends BaseGoogleSheetsTool {
|
||||
defaultParams: any
|
||||
|
||||
constructor(args: any) {
|
||||
const toolInput = {
|
||||
name: 'batch_clear_values',
|
||||
description: 'Clear values from multiple Google Spreadsheet ranges',
|
||||
schema: BatchClearValuesSchema,
|
||||
baseUrl: '',
|
||||
method: 'POST',
|
||||
headers: {}
|
||||
}
|
||||
super({
|
||||
...toolInput,
|
||||
accessToken: args.accessToken
|
||||
})
|
||||
this.defaultParams = args.defaultParams || {}
|
||||
}
|
||||
|
||||
async _call(arg: any): Promise<string> {
|
||||
const params = { ...arg, ...this.defaultParams }
|
||||
|
||||
const ranges = params.ranges.split(',').map((range: string) => range.trim())
|
||||
const body = { ranges }
|
||||
|
||||
const endpoint = `spreadsheets/${params.spreadsheetId}/values:batchClear`
|
||||
|
||||
return await this.makeGoogleSheetsRequest({
|
||||
endpoint,
|
||||
method: 'POST',
|
||||
body,
|
||||
params
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const createGoogleSheetsTools = (args?: RequestParameters): DynamicStructuredTool[] => {
|
||||
const { actions = [], accessToken, defaultParams } = args || {}
|
||||
const tools: DynamicStructuredTool[] = []
|
||||
|
||||
// Define all available tools
|
||||
const toolClasses = {
|
||||
// Spreadsheet tools
|
||||
createSpreadsheet: CreateSpreadsheetTool,
|
||||
getSpreadsheet: GetSpreadsheetTool,
|
||||
updateSpreadsheet: UpdateSpreadsheetTool,
|
||||
// Values tools
|
||||
getValues: GetValuesTool,
|
||||
updateValues: UpdateValuesTool,
|
||||
appendValues: AppendValuesTool,
|
||||
clearValues: ClearValuesTool,
|
||||
batchGetValues: BatchGetValuesTool,
|
||||
batchUpdateValues: BatchUpdateValuesTool,
|
||||
batchClearValues: BatchClearValuesTool
|
||||
}
|
||||
|
||||
// Create tools based on requested actions
|
||||
actions.forEach((action) => {
|
||||
const ToolClass = toolClasses[action as keyof typeof toolClasses]
|
||||
if (ToolClass) {
|
||||
tools.push(new ToolClass({ accessToken, defaultParams }))
|
||||
}
|
||||
})
|
||||
|
||||
return tools
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><path fill="#43a047" d="M37,45H11c-1.657,0-3-1.343-3-3V6c0-1.657,1.343-3,3-3h19l10,10v29C40,43.657,38.657,45,37,45z"/><path fill="#c8e6c9" d="M40 13L30 13 30 3z"/><path fill="#2e7d32" d="M30 13L40 23 40 13z"/><path fill="#e8f5e9" d="M31,23H17h-2v2v2v2v2v2v2v2h18v-2v-2v-2v-2v-2v-2v-2H31z M17,25h4v2h-4V25z M17,29h4v2h-4V29z M17,33h4v2h-4V33z M31,35h-8v-2h8V35z M31,31h-8v-2h8V31z M31,27h-8v-2h8V27z"/></svg>
|
||||
|
After Width: | Height: | Size: 495 B |
@@ -0,0 +1,504 @@
|
||||
import { convertMultiOptionsToStringArray, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { createJiraTools } from './core'
|
||||
import type { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
|
||||
class Jira_Tools implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
description: string
|
||||
baseClasses: string[]
|
||||
credential: INodeParams
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Jira'
|
||||
this.name = 'jiraTool'
|
||||
this.version = 1.0
|
||||
this.type = 'Jira'
|
||||
this.icon = 'jira.svg'
|
||||
this.category = 'Tools'
|
||||
this.description = 'Perform Jira operations for issues, comments, and users'
|
||||
this.baseClasses = [this.type, 'Tool']
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['jiraApi']
|
||||
}
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Host',
|
||||
name: 'jiraHost',
|
||||
type: 'string',
|
||||
placeholder: 'https://example.atlassian.net'
|
||||
},
|
||||
{
|
||||
label: 'Type',
|
||||
name: 'jiraType',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
label: 'Issues',
|
||||
name: 'issues'
|
||||
},
|
||||
{
|
||||
label: 'Issue Comments',
|
||||
name: 'comments'
|
||||
},
|
||||
{
|
||||
label: 'Users',
|
||||
name: 'users'
|
||||
}
|
||||
]
|
||||
},
|
||||
// Issue Actions
|
||||
{
|
||||
label: 'Issue Actions',
|
||||
name: 'issueActions',
|
||||
type: 'multiOptions',
|
||||
options: [
|
||||
{
|
||||
label: 'List Issues',
|
||||
name: 'listIssues'
|
||||
},
|
||||
{
|
||||
label: 'Create Issue',
|
||||
name: 'createIssue'
|
||||
},
|
||||
{
|
||||
label: 'Get Issue',
|
||||
name: 'getIssue'
|
||||
},
|
||||
{
|
||||
label: 'Update Issue',
|
||||
name: 'updateIssue'
|
||||
},
|
||||
{
|
||||
label: 'Delete Issue',
|
||||
name: 'deleteIssue'
|
||||
},
|
||||
{
|
||||
label: 'Assign Issue',
|
||||
name: 'assignIssue'
|
||||
},
|
||||
{
|
||||
label: 'Transition Issue',
|
||||
name: 'transitionIssue'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
jiraType: ['issues']
|
||||
}
|
||||
},
|
||||
// Comment Actions
|
||||
{
|
||||
label: 'Comment Actions',
|
||||
name: 'commentActions',
|
||||
type: 'multiOptions',
|
||||
options: [
|
||||
{
|
||||
label: 'List Comments',
|
||||
name: 'listComments'
|
||||
},
|
||||
{
|
||||
label: 'Create Comment',
|
||||
name: 'createComment'
|
||||
},
|
||||
{
|
||||
label: 'Get Comment',
|
||||
name: 'getComment'
|
||||
},
|
||||
{
|
||||
label: 'Update Comment',
|
||||
name: 'updateComment'
|
||||
},
|
||||
{
|
||||
label: 'Delete Comment',
|
||||
name: 'deleteComment'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
jiraType: ['comments']
|
||||
}
|
||||
},
|
||||
// User Actions
|
||||
{
|
||||
label: 'User Actions',
|
||||
name: 'userActions',
|
||||
type: 'multiOptions',
|
||||
options: [
|
||||
{
|
||||
label: 'Search Users',
|
||||
name: 'searchUsers'
|
||||
},
|
||||
{
|
||||
label: 'Get User',
|
||||
name: 'getUser'
|
||||
},
|
||||
{
|
||||
label: 'Create User',
|
||||
name: 'createUser'
|
||||
},
|
||||
{
|
||||
label: 'Update User',
|
||||
name: 'updateUser'
|
||||
},
|
||||
{
|
||||
label: 'Delete User',
|
||||
name: 'deleteUser'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
jiraType: ['users']
|
||||
}
|
||||
},
|
||||
// ISSUE PARAMETERS
|
||||
{
|
||||
label: 'Project Key',
|
||||
name: 'projectKey',
|
||||
type: 'string',
|
||||
placeholder: 'PROJ',
|
||||
description: 'Project key for the issue',
|
||||
show: {
|
||||
issueActions: ['listIssues', 'createIssue']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Issue Type',
|
||||
name: 'issueType',
|
||||
type: 'string',
|
||||
placeholder: 'Bug, Task, Story',
|
||||
description: 'Type of issue to create',
|
||||
show: {
|
||||
issueActions: ['createIssue']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Summary',
|
||||
name: 'issueSummary',
|
||||
type: 'string',
|
||||
description: 'Issue summary/title',
|
||||
show: {
|
||||
issueActions: ['createIssue', 'updateIssue']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Description',
|
||||
name: 'issueDescription',
|
||||
type: 'string',
|
||||
description: 'Issue description',
|
||||
show: {
|
||||
issueActions: ['createIssue', 'updateIssue']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Priority',
|
||||
name: 'issuePriority',
|
||||
type: 'string',
|
||||
placeholder: 'Highest, High, Medium, Low, Lowest',
|
||||
description: 'Issue priority',
|
||||
show: {
|
||||
issueActions: ['createIssue', 'updateIssue']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Issue Key',
|
||||
name: 'issueKey',
|
||||
type: 'string',
|
||||
placeholder: 'PROJ-123',
|
||||
description: 'Issue key (e.g., PROJ-123)',
|
||||
show: {
|
||||
issueActions: ['getIssue', 'updateIssue', 'deleteIssue', 'assignIssue', 'transitionIssue']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Assignee Account ID',
|
||||
name: 'assigneeAccountId',
|
||||
type: 'string',
|
||||
description: 'Account ID of the user to assign',
|
||||
show: {
|
||||
issueActions: ['assignIssue', 'createIssue', 'updateIssue']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Transition ID',
|
||||
name: 'transitionId',
|
||||
type: 'string',
|
||||
description: 'ID of the transition to execute',
|
||||
show: {
|
||||
issueActions: ['transitionIssue']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'JQL Query',
|
||||
name: 'jqlQuery',
|
||||
type: 'string',
|
||||
placeholder: 'project = PROJ AND status = "To Do"',
|
||||
description: 'JQL query for filtering issues',
|
||||
show: {
|
||||
issueActions: ['listIssues']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Max Results',
|
||||
name: 'issueMaxResults',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'Maximum number of issues to return',
|
||||
show: {
|
||||
issueActions: ['listIssues']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// COMMENT PARAMETERS
|
||||
{
|
||||
label: 'Issue Key (for Comments)',
|
||||
name: 'commentIssueKey',
|
||||
type: 'string',
|
||||
placeholder: 'PROJ-123',
|
||||
description: 'Issue key for comment operations',
|
||||
show: {
|
||||
commentActions: ['listComments', 'createComment']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Comment Text',
|
||||
name: 'commentText',
|
||||
type: 'string',
|
||||
description: 'Comment content',
|
||||
show: {
|
||||
commentActions: ['createComment', 'updateComment']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Comment ID',
|
||||
name: 'commentId',
|
||||
type: 'string',
|
||||
description: 'ID of the comment',
|
||||
show: {
|
||||
commentActions: ['getComment', 'updateComment', 'deleteComment']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// USER PARAMETERS
|
||||
{
|
||||
label: 'Search Query',
|
||||
name: 'userQuery',
|
||||
type: 'string',
|
||||
placeholder: 'john.doe',
|
||||
description: 'Query string for user search',
|
||||
show: {
|
||||
userActions: ['searchUsers']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Account ID',
|
||||
name: 'userAccountId',
|
||||
type: 'string',
|
||||
description: 'User account ID',
|
||||
show: {
|
||||
userActions: ['getUser', 'updateUser', 'deleteUser']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Email Address',
|
||||
name: 'userEmail',
|
||||
type: 'string',
|
||||
placeholder: 'user@example.com',
|
||||
description: 'User email address',
|
||||
show: {
|
||||
userActions: ['createUser', 'updateUser']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Display Name',
|
||||
name: 'userDisplayName',
|
||||
type: 'string',
|
||||
description: 'User display name',
|
||||
show: {
|
||||
userActions: ['createUser', 'updateUser']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'User Max Results',
|
||||
name: 'userMaxResults',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'Maximum number of users to return',
|
||||
show: {
|
||||
userActions: ['searchUsers']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
let credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const username = getCredentialParam('username', credentialData, nodeData)
|
||||
const accessToken = getCredentialParam('accessToken', credentialData, nodeData)
|
||||
const jiraHost = nodeData.inputs?.jiraHost as string
|
||||
|
||||
if (!username) {
|
||||
throw new Error('No username found in credential')
|
||||
}
|
||||
|
||||
if (!accessToken) {
|
||||
throw new Error('No access token found in credential')
|
||||
}
|
||||
|
||||
if (!jiraHost) {
|
||||
throw new Error('No Jira host provided')
|
||||
}
|
||||
|
||||
// Get all actions based on type
|
||||
const jiraType = nodeData.inputs?.jiraType as string
|
||||
let actions: string[] = []
|
||||
|
||||
if (jiraType === 'issues') {
|
||||
actions = convertMultiOptionsToStringArray(nodeData.inputs?.issueActions)
|
||||
} else if (jiraType === 'comments') {
|
||||
actions = convertMultiOptionsToStringArray(nodeData.inputs?.commentActions)
|
||||
} else if (jiraType === 'users') {
|
||||
actions = convertMultiOptionsToStringArray(nodeData.inputs?.userActions)
|
||||
}
|
||||
|
||||
// Prepare default parameters for each action
|
||||
const defaultParams: ICommonObject = {}
|
||||
|
||||
// Issue parameters
|
||||
const projectKey = nodeData.inputs?.projectKey
|
||||
const issueType = nodeData.inputs?.issueType
|
||||
const issueSummary = nodeData.inputs?.issueSummary
|
||||
const issueDescription = nodeData.inputs?.issueDescription
|
||||
const issuePriority = nodeData.inputs?.issuePriority
|
||||
const issueKey = nodeData.inputs?.issueKey
|
||||
const assigneeAccountId = nodeData.inputs?.assigneeAccountId
|
||||
const transitionId = nodeData.inputs?.transitionId
|
||||
const jqlQuery = nodeData.inputs?.jqlQuery
|
||||
const issueMaxResults = nodeData.inputs?.issueMaxResults
|
||||
|
||||
// Comment parameters
|
||||
const commentIssueKey = nodeData.inputs?.commentIssueKey
|
||||
const commentText = nodeData.inputs?.commentText
|
||||
const commentId = nodeData.inputs?.commentId
|
||||
|
||||
// User parameters
|
||||
const userQuery = nodeData.inputs?.userQuery
|
||||
const userAccountId = nodeData.inputs?.userAccountId
|
||||
const userEmail = nodeData.inputs?.userEmail
|
||||
const userDisplayName = nodeData.inputs?.userDisplayName
|
||||
const userMaxResults = nodeData.inputs?.userMaxResults
|
||||
|
||||
// Set default parameters based on actions
|
||||
actions.forEach((action) => {
|
||||
const params: ICommonObject = {}
|
||||
|
||||
// Issue action parameters
|
||||
if (action === 'listIssues') {
|
||||
if (projectKey) params.projectKey = projectKey
|
||||
if (jqlQuery) params.jql = jqlQuery
|
||||
if (issueMaxResults) params.maxResults = issueMaxResults
|
||||
}
|
||||
if (action === 'createIssue') {
|
||||
if (projectKey) params.projectKey = projectKey
|
||||
if (issueType) params.issueType = issueType
|
||||
if (issueSummary) params.summary = issueSummary
|
||||
if (issueDescription) params.description = issueDescription
|
||||
if (issuePriority) params.priority = issuePriority
|
||||
if (assigneeAccountId) params.assigneeAccountId = assigneeAccountId
|
||||
}
|
||||
if (['getIssue', 'updateIssue', 'deleteIssue', 'assignIssue', 'transitionIssue'].includes(action)) {
|
||||
if (issueKey) params.issueKey = issueKey
|
||||
}
|
||||
if (action === 'updateIssue') {
|
||||
if (issueSummary) params.summary = issueSummary
|
||||
if (issueDescription) params.description = issueDescription
|
||||
if (issuePriority) params.priority = issuePriority
|
||||
if (assigneeAccountId) params.assigneeAccountId = assigneeAccountId
|
||||
}
|
||||
if (action === 'assignIssue') {
|
||||
if (assigneeAccountId) params.assigneeAccountId = assigneeAccountId
|
||||
}
|
||||
if (action === 'transitionIssue') {
|
||||
if (transitionId) params.transitionId = transitionId
|
||||
}
|
||||
|
||||
// Comment action parameters
|
||||
if (['listComments', 'createComment'].includes(action) && commentIssueKey) {
|
||||
params.issueKey = commentIssueKey
|
||||
}
|
||||
if (['createComment', 'updateComment'].includes(action) && commentText) {
|
||||
params.text = commentText
|
||||
}
|
||||
if (['getComment', 'updateComment', 'deleteComment'].includes(action) && commentId) {
|
||||
params.commentId = commentId
|
||||
}
|
||||
|
||||
// User action parameters
|
||||
if (action === 'searchUsers') {
|
||||
if (userQuery) params.query = userQuery
|
||||
if (userMaxResults) params.maxResults = userMaxResults
|
||||
}
|
||||
if (['getUser', 'updateUser', 'deleteUser'].includes(action) && userAccountId) {
|
||||
params.accountId = userAccountId
|
||||
}
|
||||
if (['createUser', 'updateUser'].includes(action)) {
|
||||
if (userEmail) params.emailAddress = userEmail
|
||||
if (userDisplayName) params.displayName = userDisplayName
|
||||
}
|
||||
|
||||
defaultParams[action] = params
|
||||
})
|
||||
|
||||
// Create and return tools based on selected actions
|
||||
const tools = createJiraTools({
|
||||
actions,
|
||||
username,
|
||||
accessToken,
|
||||
jiraHost,
|
||||
defaultParams
|
||||
})
|
||||
|
||||
return tools
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: Jira_Tools }
|
||||
@@ -0,0 +1 @@
|
||||
<svg height="2500" preserveAspectRatio="xMidYMid" width="2500" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 -30.632388516510233 255.324 285.95638851651023"><linearGradient id="a"><stop offset=".18" stop-color="#0052cc"/><stop offset="1" stop-color="#2684ff"/></linearGradient><linearGradient id="b" x1="98.031%" x2="58.888%" xlink:href="#a" y1=".161%" y2="40.766%"/><linearGradient id="c" x1="100.665%" x2="55.402%" xlink:href="#a" y1=".455%" y2="44.727%"/><path d="M244.658 0H121.707a55.502 55.502 0 0 0 55.502 55.502h22.649V77.37c.02 30.625 24.841 55.447 55.466 55.467V10.666C255.324 4.777 250.55 0 244.658 0z" fill="#2684ff"/><path d="M183.822 61.262H60.872c.019 30.625 24.84 55.447 55.466 55.467h22.649v21.938c.039 30.625 24.877 55.43 55.502 55.43V71.93c0-5.891-4.776-10.667-10.667-10.667z" fill="url(#b)"/><path d="M122.951 122.489H0c0 30.653 24.85 55.502 55.502 55.502h22.72v21.867c.02 30.597 24.798 55.408 55.396 55.466V133.156c0-5.891-4.776-10.667-10.667-10.667z" fill="url(#c)"/></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,971 @@
|
||||
import { convertMultiOptionsToStringArray, getCredentialData, getCredentialParam, refreshOAuth2Token } from '../../../src/utils'
|
||||
import { createOutlookTools } from './core'
|
||||
import type { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
|
||||
class MicrosoftOutlook_Tools implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
description: string
|
||||
baseClasses: string[]
|
||||
credential: INodeParams
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Microsoft Outlook'
|
||||
this.name = 'microsoftOutlook'
|
||||
this.version = 1.0
|
||||
this.type = 'MicrosoftOutlook'
|
||||
this.icon = 'outlook.svg'
|
||||
this.category = 'Tools'
|
||||
this.description = 'Perform Microsoft Outlook operations for calendars, events, and messages'
|
||||
this.baseClasses = [this.type, 'Tool']
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['microsoftOutlookOAuth2']
|
||||
}
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Type',
|
||||
name: 'outlookType',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
label: 'Calendar',
|
||||
name: 'calendar'
|
||||
},
|
||||
{
|
||||
label: 'Message',
|
||||
name: 'message'
|
||||
}
|
||||
]
|
||||
},
|
||||
// Calendar Actions
|
||||
{
|
||||
label: 'Calendar Actions',
|
||||
name: 'calendarActions',
|
||||
type: 'multiOptions',
|
||||
options: [
|
||||
{
|
||||
label: 'List Calendars',
|
||||
name: 'listCalendars'
|
||||
},
|
||||
{
|
||||
label: 'Get Calendar',
|
||||
name: 'getCalendar'
|
||||
},
|
||||
{
|
||||
label: 'Create Calendar',
|
||||
name: 'createCalendar'
|
||||
},
|
||||
{
|
||||
label: 'Update Calendar',
|
||||
name: 'updateCalendar'
|
||||
},
|
||||
{
|
||||
label: 'Delete Calendar',
|
||||
name: 'deleteCalendar'
|
||||
},
|
||||
{
|
||||
label: 'List Events',
|
||||
name: 'listEvents'
|
||||
},
|
||||
{
|
||||
label: 'Get Event',
|
||||
name: 'getEvent'
|
||||
},
|
||||
{
|
||||
label: 'Create Event',
|
||||
name: 'createEvent'
|
||||
},
|
||||
{
|
||||
label: 'Update Event',
|
||||
name: 'updateEvent'
|
||||
},
|
||||
{
|
||||
label: 'Delete Event',
|
||||
name: 'deleteEvent'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
outlookType: ['calendar']
|
||||
}
|
||||
},
|
||||
// Message Actions
|
||||
{
|
||||
label: 'Message Actions',
|
||||
name: 'messageActions',
|
||||
type: 'multiOptions',
|
||||
options: [
|
||||
{
|
||||
label: 'List Messages',
|
||||
name: 'listMessages'
|
||||
},
|
||||
{
|
||||
label: 'Get Message',
|
||||
name: 'getMessage'
|
||||
},
|
||||
{
|
||||
label: 'Create Draft Message',
|
||||
name: 'createDraftMessage'
|
||||
},
|
||||
{
|
||||
label: 'Send Message',
|
||||
name: 'sendMessage'
|
||||
},
|
||||
{
|
||||
label: 'Update Message',
|
||||
name: 'updateMessage'
|
||||
},
|
||||
{
|
||||
label: 'Delete Message',
|
||||
name: 'deleteMessage'
|
||||
},
|
||||
{
|
||||
label: 'Copy Message',
|
||||
name: 'copyMessage'
|
||||
},
|
||||
{
|
||||
label: 'Move Message',
|
||||
name: 'moveMessage'
|
||||
},
|
||||
{
|
||||
label: 'Reply to Message',
|
||||
name: 'replyMessage'
|
||||
},
|
||||
{
|
||||
label: 'Forward Message',
|
||||
name: 'forwardMessage'
|
||||
}
|
||||
],
|
||||
show: {
|
||||
outlookType: ['message']
|
||||
}
|
||||
},
|
||||
// CALENDAR PARAMETERS
|
||||
// List Calendars Parameters
|
||||
{
|
||||
label: 'Max Results [List Calendars]',
|
||||
name: 'maxResultsListCalendars',
|
||||
type: 'number',
|
||||
description: 'Maximum number of calendars to return',
|
||||
default: 50,
|
||||
show: {
|
||||
outlookType: ['calendar'],
|
||||
calendarActions: ['listCalendars']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Get Calendar Parameters
|
||||
{
|
||||
label: 'Calendar ID [Get Calendar]',
|
||||
name: 'calendarIdGetCalendar',
|
||||
type: 'string',
|
||||
description: 'ID of the calendar to retrieve',
|
||||
show: {
|
||||
outlookType: ['calendar'],
|
||||
calendarActions: ['getCalendar']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Create Calendar Parameters
|
||||
{
|
||||
label: 'Calendar Name [Create Calendar]',
|
||||
name: 'calendarNameCreateCalendar',
|
||||
type: 'string',
|
||||
description: 'Name of the calendar',
|
||||
placeholder: 'My New Calendar',
|
||||
show: {
|
||||
outlookType: ['calendar'],
|
||||
calendarActions: ['createCalendar']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Update Calendar Parameters
|
||||
{
|
||||
label: 'Calendar ID [Update Calendar]',
|
||||
name: 'calendarIdUpdateCalendar',
|
||||
type: 'string',
|
||||
description: 'ID of the calendar to update',
|
||||
show: {
|
||||
outlookType: ['calendar'],
|
||||
calendarActions: ['updateCalendar']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Calendar Name [Update Calendar]',
|
||||
name: 'calendarNameUpdateCalendar',
|
||||
type: 'string',
|
||||
description: 'New name of the calendar',
|
||||
show: {
|
||||
outlookType: ['calendar'],
|
||||
calendarActions: ['updateCalendar']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Delete Calendar Parameters
|
||||
{
|
||||
label: 'Calendar ID [Delete Calendar]',
|
||||
name: 'calendarIdDeleteCalendar',
|
||||
type: 'string',
|
||||
description: 'ID of the calendar to delete',
|
||||
show: {
|
||||
outlookType: ['calendar'],
|
||||
calendarActions: ['deleteCalendar']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// List Events Parameters
|
||||
{
|
||||
label: 'Calendar ID [List Events]',
|
||||
name: 'calendarIdListEvents',
|
||||
type: 'string',
|
||||
description: 'ID of the calendar (leave empty for primary calendar)',
|
||||
show: {
|
||||
outlookType: ['calendar'],
|
||||
calendarActions: ['listEvents']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Max Results [List Events]',
|
||||
name: 'maxResultsListEvents',
|
||||
type: 'number',
|
||||
description: 'Maximum number of events to return',
|
||||
default: 50,
|
||||
show: {
|
||||
outlookType: ['calendar'],
|
||||
calendarActions: ['listEvents']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Start Date Time [List Events]',
|
||||
name: 'startDateTimeListEvents',
|
||||
type: 'string',
|
||||
description: 'Start date time filter in ISO format',
|
||||
placeholder: '2024-01-01T00:00:00Z',
|
||||
show: {
|
||||
outlookType: ['calendar'],
|
||||
calendarActions: ['listEvents']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'End Date Time [List Events]',
|
||||
name: 'endDateTimeListEvents',
|
||||
type: 'string',
|
||||
description: 'End date time filter in ISO format',
|
||||
placeholder: '2024-12-31T23:59:59Z',
|
||||
show: {
|
||||
outlookType: ['calendar'],
|
||||
calendarActions: ['listEvents']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Get Event Parameters
|
||||
{
|
||||
label: 'Event ID [Get Event]',
|
||||
name: 'eventIdGetEvent',
|
||||
type: 'string',
|
||||
description: 'ID of the event to retrieve',
|
||||
show: {
|
||||
outlookType: ['calendar'],
|
||||
calendarActions: ['getEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Create Event Parameters
|
||||
{
|
||||
label: 'Subject [Create Event]',
|
||||
name: 'subjectCreateEvent',
|
||||
type: 'string',
|
||||
description: 'Subject/title of the event',
|
||||
placeholder: 'Meeting Title',
|
||||
show: {
|
||||
outlookType: ['calendar'],
|
||||
calendarActions: ['createEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Body [Create Event]',
|
||||
name: 'bodyCreateEvent',
|
||||
type: 'string',
|
||||
description: 'Body/description of the event',
|
||||
placeholder: 'Meeting description',
|
||||
rows: 3,
|
||||
show: {
|
||||
outlookType: ['calendar'],
|
||||
calendarActions: ['createEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Start Date Time [Create Event]',
|
||||
name: 'startDateTimeCreateEvent',
|
||||
type: 'string',
|
||||
description: 'Start date and time in ISO format',
|
||||
placeholder: '2024-01-15T10:00:00',
|
||||
show: {
|
||||
outlookType: ['calendar'],
|
||||
calendarActions: ['createEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'End Date Time [Create Event]',
|
||||
name: 'endDateTimeCreateEvent',
|
||||
type: 'string',
|
||||
description: 'End date and time in ISO format',
|
||||
placeholder: '2024-01-15T11:00:00',
|
||||
show: {
|
||||
outlookType: ['calendar'],
|
||||
calendarActions: ['createEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Time Zone [Create Event]',
|
||||
name: 'timeZoneCreateEvent',
|
||||
type: 'string',
|
||||
description: 'Time zone for the event',
|
||||
placeholder: 'UTC',
|
||||
default: 'UTC',
|
||||
show: {
|
||||
outlookType: ['calendar'],
|
||||
calendarActions: ['createEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Location [Create Event]',
|
||||
name: 'locationCreateEvent',
|
||||
type: 'string',
|
||||
description: 'Location of the event',
|
||||
placeholder: 'Conference Room A',
|
||||
show: {
|
||||
outlookType: ['calendar'],
|
||||
calendarActions: ['createEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Attendees [Create Event]',
|
||||
name: 'attendeesCreateEvent',
|
||||
type: 'string',
|
||||
description: 'Comma-separated list of attendee email addresses',
|
||||
placeholder: 'user1@example.com,user2@example.com',
|
||||
show: {
|
||||
outlookType: ['calendar'],
|
||||
calendarActions: ['createEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Update Event Parameters
|
||||
{
|
||||
label: 'Event ID [Update Event]',
|
||||
name: 'eventIdUpdateEvent',
|
||||
type: 'string',
|
||||
description: 'ID of the event to update',
|
||||
show: {
|
||||
outlookType: ['calendar'],
|
||||
calendarActions: ['updateEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Subject [Update Event]',
|
||||
name: 'subjectUpdateEvent',
|
||||
type: 'string',
|
||||
description: 'New subject/title of the event',
|
||||
show: {
|
||||
outlookType: ['calendar'],
|
||||
calendarActions: ['updateEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Delete Event Parameters
|
||||
{
|
||||
label: 'Event ID [Delete Event]',
|
||||
name: 'eventIdDeleteEvent',
|
||||
type: 'string',
|
||||
description: 'ID of the event to delete',
|
||||
show: {
|
||||
outlookType: ['calendar'],
|
||||
calendarActions: ['deleteEvent']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// MESSAGE PARAMETERS
|
||||
// List Messages Parameters
|
||||
{
|
||||
label: 'Max Results [List Messages]',
|
||||
name: 'maxResultsListMessages',
|
||||
type: 'number',
|
||||
description: 'Maximum number of messages to return',
|
||||
default: 50,
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['listMessages']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Filter [List Messages]',
|
||||
name: 'filterListMessages',
|
||||
type: 'string',
|
||||
description: 'Filter query (e.g., "isRead eq false")',
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['listMessages']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Get Message Parameters
|
||||
{
|
||||
label: 'Message ID [Get Message]',
|
||||
name: 'messageIdGetMessage',
|
||||
type: 'string',
|
||||
description: 'ID of the message to retrieve',
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['getMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Create Draft Message Parameters
|
||||
{
|
||||
label: 'To [Create Draft Message]',
|
||||
name: 'toCreateDraftMessage',
|
||||
type: 'string',
|
||||
description: 'Recipient email address(es), comma-separated',
|
||||
placeholder: 'user@example.com',
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['createDraftMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Subject [Create Draft Message]',
|
||||
name: 'subjectCreateDraftMessage',
|
||||
type: 'string',
|
||||
description: 'Subject of the message',
|
||||
placeholder: 'Email Subject',
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['createDraftMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Body [Create Draft Message]',
|
||||
name: 'bodyCreateDraftMessage',
|
||||
type: 'string',
|
||||
description: 'Body content of the message',
|
||||
placeholder: 'Email body content',
|
||||
rows: 4,
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['createDraftMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'CC [Create Draft Message]',
|
||||
name: 'ccCreateDraftMessage',
|
||||
type: 'string',
|
||||
description: 'CC email address(es), comma-separated',
|
||||
placeholder: 'cc@example.com',
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['createDraftMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'BCC [Create Draft Message]',
|
||||
name: 'bccCreateDraftMessage',
|
||||
type: 'string',
|
||||
description: 'BCC email address(es), comma-separated',
|
||||
placeholder: 'bcc@example.com',
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['createDraftMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Send Message Parameters
|
||||
{
|
||||
label: 'To [Send Message]',
|
||||
name: 'toSendMessage',
|
||||
type: 'string',
|
||||
description: 'Recipient email address(es), comma-separated',
|
||||
placeholder: 'user@example.com',
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['sendMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Subject [Send Message]',
|
||||
name: 'subjectSendMessage',
|
||||
type: 'string',
|
||||
description: 'Subject of the message',
|
||||
placeholder: 'Email Subject',
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['sendMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Body [Send Message]',
|
||||
name: 'bodySendMessage',
|
||||
type: 'string',
|
||||
description: 'Body content of the message',
|
||||
placeholder: 'Email body content',
|
||||
rows: 4,
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['sendMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Update Message Parameters
|
||||
{
|
||||
label: 'Message ID [Update Message]',
|
||||
name: 'messageIdUpdateMessage',
|
||||
type: 'string',
|
||||
description: 'ID of the message to update',
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['updateMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Is Read [Update Message]',
|
||||
name: 'isReadUpdateMessage',
|
||||
type: 'boolean',
|
||||
description: 'Mark message as read/unread',
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['updateMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Delete Message Parameters
|
||||
{
|
||||
label: 'Message ID [Delete Message]',
|
||||
name: 'messageIdDeleteMessage',
|
||||
type: 'string',
|
||||
description: 'ID of the message to delete',
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['deleteMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Copy Message Parameters
|
||||
{
|
||||
label: 'Message ID [Copy Message]',
|
||||
name: 'messageIdCopyMessage',
|
||||
type: 'string',
|
||||
description: 'ID of the message to copy',
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['copyMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Destination Folder ID [Copy Message]',
|
||||
name: 'destinationFolderIdCopyMessage',
|
||||
type: 'string',
|
||||
description: 'ID of the destination folder',
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['copyMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Move Message Parameters
|
||||
{
|
||||
label: 'Message ID [Move Message]',
|
||||
name: 'messageIdMoveMessage',
|
||||
type: 'string',
|
||||
description: 'ID of the message to move',
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['moveMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Destination Folder ID [Move Message]',
|
||||
name: 'destinationFolderIdMoveMessage',
|
||||
type: 'string',
|
||||
description: 'ID of the destination folder',
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['moveMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Reply Message Parameters
|
||||
{
|
||||
label: 'Message ID [Reply Message]',
|
||||
name: 'messageIdReplyMessage',
|
||||
type: 'string',
|
||||
description: 'ID of the message to reply to',
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['replyMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Reply Body [Reply Message]',
|
||||
name: 'replyBodyReplyMessage',
|
||||
type: 'string',
|
||||
description: 'Reply message body',
|
||||
rows: 4,
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['replyMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
// Forward Message Parameters
|
||||
{
|
||||
label: 'Message ID [Forward Message]',
|
||||
name: 'messageIdForwardMessage',
|
||||
type: 'string',
|
||||
description: 'ID of the message to forward',
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['forwardMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Forward To [Forward Message]',
|
||||
name: 'forwardToForwardMessage',
|
||||
type: 'string',
|
||||
description: 'Email address(es) to forward to, comma-separated',
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['forwardMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Forward Comment [Forward Message]',
|
||||
name: 'forwardCommentForwardMessage',
|
||||
type: 'string',
|
||||
description: 'Additional comment to include with forward',
|
||||
rows: 2,
|
||||
show: {
|
||||
outlookType: ['message'],
|
||||
messageActions: ['forwardMessage']
|
||||
},
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
const outlookType = nodeData.inputs?.outlookType as string
|
||||
const calendarActions = nodeData.inputs?.calendarActions as string
|
||||
const messageActions = nodeData.inputs?.messageActions as string
|
||||
|
||||
let credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
credentialData = await refreshOAuth2Token(nodeData.credential ?? '', credentialData, options)
|
||||
const accessToken = getCredentialParam('access_token', credentialData, nodeData)
|
||||
|
||||
if (!accessToken) {
|
||||
throw new Error('No access token found in credential')
|
||||
}
|
||||
|
||||
let actions: string[] = []
|
||||
if (outlookType === 'calendar') {
|
||||
actions = convertMultiOptionsToStringArray(calendarActions)
|
||||
} else if (outlookType === 'message') {
|
||||
actions = convertMultiOptionsToStringArray(messageActions)
|
||||
}
|
||||
|
||||
// Prepare default parameters for each action based on type
|
||||
const defaultParams: ICommonObject = {}
|
||||
|
||||
if (outlookType === 'calendar') {
|
||||
// Map calendar actions to their parameters
|
||||
actions.forEach((action) => {
|
||||
defaultParams[action] = {}
|
||||
|
||||
switch (action) {
|
||||
case 'listCalendars':
|
||||
if (nodeData.inputs?.maxResultsListCalendars) {
|
||||
defaultParams[action].maxResults = nodeData.inputs.maxResultsListCalendars
|
||||
}
|
||||
break
|
||||
|
||||
case 'getCalendar':
|
||||
if (nodeData.inputs?.calendarIdGetCalendar) {
|
||||
defaultParams[action].calendarId = nodeData.inputs.calendarIdGetCalendar
|
||||
}
|
||||
break
|
||||
|
||||
case 'createCalendar':
|
||||
if (nodeData.inputs?.calendarNameCreateCalendar) {
|
||||
defaultParams[action].calendarName = nodeData.inputs.calendarNameCreateCalendar
|
||||
}
|
||||
break
|
||||
|
||||
case 'updateCalendar':
|
||||
if (nodeData.inputs?.calendarIdUpdateCalendar) {
|
||||
defaultParams[action].calendarId = nodeData.inputs.calendarIdUpdateCalendar
|
||||
}
|
||||
if (nodeData.inputs?.calendarNameUpdateCalendar) {
|
||||
defaultParams[action].calendarName = nodeData.inputs.calendarNameUpdateCalendar
|
||||
}
|
||||
break
|
||||
|
||||
case 'deleteCalendar':
|
||||
if (nodeData.inputs?.calendarIdDeleteCalendar) {
|
||||
defaultParams[action].calendarId = nodeData.inputs.calendarIdDeleteCalendar
|
||||
}
|
||||
break
|
||||
|
||||
case 'listEvents':
|
||||
if (nodeData.inputs?.calendarIdListEvents) {
|
||||
defaultParams[action].calendarId = nodeData.inputs.calendarIdListEvents
|
||||
}
|
||||
if (nodeData.inputs?.maxResultsListEvents) {
|
||||
defaultParams[action].maxResults = nodeData.inputs.maxResultsListEvents
|
||||
}
|
||||
if (nodeData.inputs?.startDateTimeListEvents) {
|
||||
defaultParams[action].startDateTime = nodeData.inputs.startDateTimeListEvents
|
||||
}
|
||||
if (nodeData.inputs?.endDateTimeListEvents) {
|
||||
defaultParams[action].endDateTime = nodeData.inputs.endDateTimeListEvents
|
||||
}
|
||||
break
|
||||
|
||||
case 'getEvent':
|
||||
if (nodeData.inputs?.eventIdGetEvent) {
|
||||
defaultParams[action].eventId = nodeData.inputs.eventIdGetEvent
|
||||
}
|
||||
break
|
||||
|
||||
case 'createEvent':
|
||||
if (nodeData.inputs?.subjectCreateEvent) {
|
||||
defaultParams[action].subject = nodeData.inputs.subjectCreateEvent
|
||||
}
|
||||
if (nodeData.inputs?.bodyCreateEvent) {
|
||||
defaultParams[action].body = nodeData.inputs.bodyCreateEvent
|
||||
}
|
||||
if (nodeData.inputs?.startDateTimeCreateEvent) {
|
||||
defaultParams[action].startDateTime = nodeData.inputs.startDateTimeCreateEvent
|
||||
}
|
||||
if (nodeData.inputs?.endDateTimeCreateEvent) {
|
||||
defaultParams[action].endDateTime = nodeData.inputs.endDateTimeCreateEvent
|
||||
}
|
||||
if (nodeData.inputs?.timeZoneCreateEvent) {
|
||||
defaultParams[action].timeZone = nodeData.inputs.timeZoneCreateEvent
|
||||
}
|
||||
if (nodeData.inputs?.locationCreateEvent) {
|
||||
defaultParams[action].location = nodeData.inputs.locationCreateEvent
|
||||
}
|
||||
if (nodeData.inputs?.attendeesCreateEvent) {
|
||||
defaultParams[action].attendees = nodeData.inputs.attendeesCreateEvent
|
||||
}
|
||||
break
|
||||
|
||||
case 'updateEvent':
|
||||
if (nodeData.inputs?.eventIdUpdateEvent) {
|
||||
defaultParams[action].eventId = nodeData.inputs.eventIdUpdateEvent
|
||||
}
|
||||
if (nodeData.inputs?.subjectUpdateEvent) {
|
||||
defaultParams[action].subject = nodeData.inputs.subjectUpdateEvent
|
||||
}
|
||||
break
|
||||
|
||||
case 'deleteEvent':
|
||||
if (nodeData.inputs?.eventIdDeleteEvent) {
|
||||
defaultParams[action].eventId = nodeData.inputs.eventIdDeleteEvent
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
} else if (outlookType === 'message') {
|
||||
// Map message actions to their parameters
|
||||
actions.forEach((action) => {
|
||||
defaultParams[action] = {}
|
||||
|
||||
switch (action) {
|
||||
case 'listMessages':
|
||||
if (nodeData.inputs?.maxResultsListMessages) {
|
||||
defaultParams[action].maxResults = nodeData.inputs.maxResultsListMessages
|
||||
}
|
||||
if (nodeData.inputs?.filterListMessages) {
|
||||
defaultParams[action].filter = nodeData.inputs.filterListMessages
|
||||
}
|
||||
break
|
||||
|
||||
case 'getMessage':
|
||||
if (nodeData.inputs?.messageIdGetMessage) {
|
||||
defaultParams[action].messageId = nodeData.inputs.messageIdGetMessage
|
||||
}
|
||||
break
|
||||
|
||||
case 'createDraftMessage':
|
||||
if (nodeData.inputs?.toCreateDraftMessage) {
|
||||
defaultParams[action].to = nodeData.inputs.toCreateDraftMessage
|
||||
}
|
||||
if (nodeData.inputs?.subjectCreateDraftMessage) {
|
||||
defaultParams[action].subject = nodeData.inputs.subjectCreateDraftMessage
|
||||
}
|
||||
if (nodeData.inputs?.bodyCreateDraftMessage) {
|
||||
defaultParams[action].body = nodeData.inputs.bodyCreateDraftMessage
|
||||
}
|
||||
if (nodeData.inputs?.ccCreateDraftMessage) {
|
||||
defaultParams[action].cc = nodeData.inputs.ccCreateDraftMessage
|
||||
}
|
||||
if (nodeData.inputs?.bccCreateDraftMessage) {
|
||||
defaultParams[action].bcc = nodeData.inputs.bccCreateDraftMessage
|
||||
}
|
||||
break
|
||||
|
||||
case 'sendMessage':
|
||||
if (nodeData.inputs?.toSendMessage) {
|
||||
defaultParams[action].to = nodeData.inputs.toSendMessage
|
||||
}
|
||||
if (nodeData.inputs?.subjectSendMessage) {
|
||||
defaultParams[action].subject = nodeData.inputs.subjectSendMessage
|
||||
}
|
||||
if (nodeData.inputs?.bodySendMessage) {
|
||||
defaultParams[action].body = nodeData.inputs.bodySendMessage
|
||||
}
|
||||
break
|
||||
|
||||
case 'updateMessage':
|
||||
if (nodeData.inputs?.messageIdUpdateMessage) {
|
||||
defaultParams[action].messageId = nodeData.inputs.messageIdUpdateMessage
|
||||
}
|
||||
if (nodeData.inputs?.isReadUpdateMessage !== undefined) {
|
||||
defaultParams[action].isRead = nodeData.inputs.isReadUpdateMessage
|
||||
}
|
||||
break
|
||||
|
||||
case 'deleteMessage':
|
||||
if (nodeData.inputs?.messageIdDeleteMessage) {
|
||||
defaultParams[action].messageId = nodeData.inputs.messageIdDeleteMessage
|
||||
}
|
||||
break
|
||||
|
||||
case 'copyMessage':
|
||||
if (nodeData.inputs?.messageIdCopyMessage) {
|
||||
defaultParams[action].messageId = nodeData.inputs.messageIdCopyMessage
|
||||
}
|
||||
if (nodeData.inputs?.destinationFolderIdCopyMessage) {
|
||||
defaultParams[action].destinationFolderId = nodeData.inputs.destinationFolderIdCopyMessage
|
||||
}
|
||||
break
|
||||
|
||||
case 'moveMessage':
|
||||
if (nodeData.inputs?.messageIdMoveMessage) {
|
||||
defaultParams[action].messageId = nodeData.inputs.messageIdMoveMessage
|
||||
}
|
||||
if (nodeData.inputs?.destinationFolderIdMoveMessage) {
|
||||
defaultParams[action].destinationFolderId = nodeData.inputs.destinationFolderIdMoveMessage
|
||||
}
|
||||
break
|
||||
|
||||
case 'replyMessage':
|
||||
if (nodeData.inputs?.messageIdReplyMessage) {
|
||||
defaultParams[action].messageId = nodeData.inputs.messageIdReplyMessage
|
||||
}
|
||||
if (nodeData.inputs?.replyBodyReplyMessage) {
|
||||
defaultParams[action].replyBody = nodeData.inputs.replyBodyReplyMessage
|
||||
}
|
||||
break
|
||||
|
||||
case 'forwardMessage':
|
||||
if (nodeData.inputs?.messageIdForwardMessage) {
|
||||
defaultParams[action].messageId = nodeData.inputs.messageIdForwardMessage
|
||||
}
|
||||
if (nodeData.inputs?.forwardToForwardMessage) {
|
||||
defaultParams[action].forwardTo = nodeData.inputs.forwardToForwardMessage
|
||||
}
|
||||
if (nodeData.inputs?.forwardCommentForwardMessage) {
|
||||
defaultParams[action].forwardComment = nodeData.inputs.forwardCommentForwardMessage
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const outlookTools = createOutlookTools({
|
||||
accessToken,
|
||||
actions,
|
||||
defaultParams
|
||||
})
|
||||
|
||||
return outlookTools
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: MicrosoftOutlook_Tools }
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><path fill="#03A9F4" d="M21,31c0,1.104,0.896,2,2,2h17c1.104,0,2-0.896,2-2V16c0-1.104-0.896-2-2-2H23c-1.104,0-2,0.896-2,2V31z"/><path fill="#B3E5FC" d="M42,16.975V16c0-0.428-0.137-0.823-0.367-1.148l-11.264,6.932l-7.542-4.656L22.125,19l8.459,5L42,16.975z"/><path fill="#0277BD" d="M27 41.46L6 37.46 6 9.46 27 5.46z"/><path fill="#FFF" d="M21.216,18.311c-1.098-1.275-2.546-1.913-4.328-1.913c-1.892,0-3.408,0.669-4.554,2.003c-1.144,1.337-1.719,3.088-1.719,5.246c0,2.045,0.564,3.714,1.69,4.986c1.126,1.273,2.592,1.91,4.378,1.91c1.84,0,3.331-0.652,4.474-1.975c1.143-1.313,1.712-3.043,1.712-5.199C22.869,21.281,22.318,19.595,21.216,18.311z M19.049,26.735c-0.568,0.769-1.339,1.152-2.313,1.152c-0.939,0-1.699-0.394-2.285-1.187c-0.581-0.785-0.87-1.861-0.87-3.211c0-1.336,0.289-2.414,0.87-3.225c0.586-0.81,1.368-1.211,2.355-1.211c0.962,0,1.718,0.393,2.267,1.178c0.555,0.795,0.833,1.895,0.833,3.31C19.907,24.906,19.618,25.968,19.049,26.735z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><path fill="#5c6bc0" d="M41.5 13A3.5 3.5 0 1 0 41.5 20 3.5 3.5 0 1 0 41.5 13zM4 40l23 4V4L4 8V40z"/><path fill="#fff" d="M21 16.27L21 19 17.01 19.18 16.99 31.04 14.01 30.95 14.01 19.29 10 19.45 10 16.94z"/><path fill="#5c6bc0" d="M36 14c0 2.21-1.79 4-4 4-1.2 0-2.27-.53-3-1.36v-5.28c.73-.83 1.8-1.36 3-1.36C34.21 10 36 11.79 36 14zM38 23v11c0 0 1.567 0 3.5 0 1.762 0 3.205-1.306 3.45-3H45v-8H38zM29 20v17c0 0 1.567 0 3.5 0 1.762 0 3.205-1.306 3.45-3H36V20H29z"/></svg>
|
||||
|
After Width: | Height: | Size: 556 B |