n8n-nodes-smartgent
Version:
SmartGent custom nodes for n8n - AI-powered automation and intelligent workflow integrations including LiteLLM chat completions, SharePoint file monitoring, and enterprise search
529 lines • 24.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SmartgentSharePoint = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const sharepoint_1 = require("../../utils/sharepoint");
class SmartgentSharePoint {
constructor() {
this.description = {
displayName: 'Smartgent SharePoint',
name: 'smartgentSharePoint',
icon: 'file:sharepoint.svg',
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"]}}',
description: 'Get documents from SharePoint document libraries using Microsoft Graph API',
defaults: {
name: 'Smartgent SharePoint',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'smartgentMicrosoftSharePointApi',
required: true,
},
],
properties: [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
options: [
{
name: 'List Documents',
value: 'listDocuments',
description: 'Get all documents from a document library',
action: 'List all documents in a library',
},
{
name: 'Get Document Details',
value: 'getDocument',
description: 'Get details of a specific document',
action: 'Get document details',
},
{
name: 'Download Document',
value: 'downloadDocument',
description: 'Download a document content',
action: 'Download document content',
},
{
name: 'Move File',
value: 'moveFile',
description: 'Move a file to a different folder',
action: 'Move file to folder',
},
],
default: 'listDocuments',
},
{
displayName: 'Document Library Path',
name: 'libraryPath',
type: 'string',
default: '/',
required: true,
description: 'Path to the document library. Use "/" for root/main Documents library, or specify a subfolder path.',
placeholder: '/',
displayOptions: {
show: {
operation: ['listDocuments'],
},
},
},
{
displayName: 'Document ID',
name: 'documentId',
type: 'string',
default: '',
required: true,
description: 'The ID of the document to retrieve',
displayOptions: {
show: {
operation: ['getDocument', 'downloadDocument'],
},
},
},
{
displayName: 'Source Document ID',
name: 'sourceDocumentId',
type: 'string',
default: '',
required: true,
description: 'The ID of the document to move',
displayOptions: {
show: {
operation: ['moveFile'],
},
},
},
{
displayName: 'Destination Folder Path',
name: 'destinationFolderPath',
type: 'string',
default: '',
required: true,
description: 'The path of the destination folder (e.g., "/NewFolder" or "ParentFolder/SubFolder")',
placeholder: '/NewFolder',
displayOptions: {
show: {
operation: ['moveFile'],
},
},
},
{
displayName: 'Additional Options',
name: 'additionalOptions',
type: 'collection',
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'Filter',
name: 'filter',
type: 'string',
default: '',
description: 'OData filter expression to filter documents',
placeholder: "name eq 'document.pdf'",
},
{
displayName: 'Select Fields',
name: 'select',
type: 'string',
default: '',
description: 'Comma-separated list of fields to include in response',
placeholder: 'id,name,lastModifiedDateTime,size',
},
{
displayName: 'Top',
name: 'top',
type: 'number',
default: 100,
description: 'Maximum number of items to return',
typeOptions: {
minValue: 1,
maxValue: 1000,
},
},
],
},
],
};
}
async execute() {
const items = this.getInputData();
const returnData = [];
for (let i = 0; i < items.length; i++) {
try {
const operation = this.getNodeParameter('operation', i);
const credentials = await this.getCredentials('smartgentMicrosoftSharePointApi');
const tokenResponse = await SmartgentSharePoint.getAccessToken(credentials, this);
const accessToken = tokenResponse.access_token;
const siteId = await SmartgentSharePoint.getSiteId(credentials.siteUrl, accessToken, this);
let executionData;
switch (operation) {
case 'listDocuments':
const libraryPath = this.getNodeParameter('libraryPath', i);
const additionalOptions = this.getNodeParameter('additionalOptions', i);
const driveId = await SmartgentSharePoint.getDriveId(siteId, accessToken, this);
const documents = await SmartgentSharePoint.listDocuments(driveId, libraryPath, accessToken, additionalOptions, this);
executionData = {
json: {
operation: 'listDocuments',
siteId,
driveId,
libraryPath,
documents,
totalCount: documents.length,
timestamp: new Date().toISOString(),
},
pairedItem: { item: i },
};
break;
case 'getDocument':
const documentId = this.getNodeParameter('documentId', i);
const driveIdForGet = await SmartgentSharePoint.getDriveId(siteId, accessToken, this);
const documentDetails = await SmartgentSharePoint.getDocument(driveIdForGet, documentId, accessToken, this);
executionData = {
json: {
operation: 'getDocument',
siteId,
driveId: driveIdForGet,
documentId,
document: documentDetails,
timestamp: new Date().toISOString(),
},
pairedItem: { item: i },
};
break;
case 'downloadDocument':
const downloadDocumentId = this.getNodeParameter('documentId', i);
const driveIdForDownload = await SmartgentSharePoint.getDriveId(siteId, accessToken, this);
const downloadContent = await SmartgentSharePoint.downloadDocument(driveIdForDownload, downloadDocumentId, accessToken, this);
const downloadDocumentDetails = await SmartgentSharePoint.getDocument(driveIdForDownload, downloadDocumentId, accessToken, this);
executionData = {
json: {
operation: 'downloadDocument',
siteId,
driveId: driveIdForDownload,
documentId: downloadDocumentId,
fileName: downloadDocumentDetails.name,
timestamp: new Date().toISOString(),
},
binary: {
data: {
data: downloadContent.toString('base64'),
mimeType: downloadDocumentDetails.mimeType || 'application/octet-stream',
fileName: downloadDocumentDetails.name,
},
},
pairedItem: { item: i },
};
break;
case 'moveFile':
const sourceDocumentId = this.getNodeParameter('sourceDocumentId', i);
const destinationFolderPath = this.getNodeParameter('destinationFolderPath', i);
const driveIdForMove = await SmartgentSharePoint.getDriveId(siteId, accessToken, this);
const destinationFolderId = await SmartgentSharePoint.getFolderId(driveIdForMove, destinationFolderPath, accessToken, this);
const moveRequest = {
driveId: driveIdForMove,
fileId: sourceDocumentId,
destinationFolderId,
token: accessToken,
};
await (0, sharepoint_1.moveFile)(moveRequest);
const movedFileDetails = await SmartgentSharePoint.getDocument(driveIdForMove, sourceDocumentId, accessToken, this);
executionData = {
json: {
operation: 'moveFile',
siteId,
driveId: driveIdForMove,
sourceDocumentId,
destinationFolderPath,
destinationFolderId,
movedFile: movedFileDetails,
success: true,
timestamp: new Date().toISOString(),
},
pairedItem: { item: i },
};
break;
default:
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation: ${operation}`);
}
returnData.push(executionData);
}
catch (error) {
if (this.continueOnFail()) {
const executionData = {
json: {
error: error.message,
operation: this.getNodeParameter('operation', i),
timestamp: new Date().toISOString(),
},
pairedItem: { item: i },
};
returnData.push(executionData);
}
else {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `SharePoint operation failed: ${error.message}`, { itemIndex: i });
}
}
}
return [returnData];
}
static async getAccessToken(credentials, context) {
var _a, _b, _c, _d;
const tokenUrl = credentials.tokenEndpoint ||
`https://login.microsoftonline.com/${credentials.tenantId}/oauth2/v2.0/token`;
const data = {
grant_type: 'client_credentials',
client_id: credentials.clientId,
client_secret: credentials.clientSecret,
scope: 'https://graph.microsoft.com/.default',
};
const formDataString = Object.keys(data)
.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`)
.join('&');
const options = {
method: 'POST',
url: tokenUrl,
headers: {
'content-type': 'application/x-www-form-urlencoded',
},
body: formDataString,
json: true,
};
try {
const response = await context.helpers.httpRequest(options);
if (!response.access_token) {
throw new n8n_workflow_1.ApplicationError('Failed to obtain access token from Microsoft. Check your tenant ID, client ID, and client secret.');
}
return response;
}
catch (error) {
if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 400) {
throw new n8n_workflow_1.ApplicationError(`Authentication failed: ${((_c = (_b = error.response) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c.error_description) || 'Invalid client credentials or tenant ID'}`);
}
else if (((_d = error.response) === null || _d === void 0 ? void 0 : _d.status) === 401) {
throw new n8n_workflow_1.ApplicationError('Authentication failed: Invalid client credentials. Please check your Client ID and Client Secret.');
}
throw new n8n_workflow_1.ApplicationError(`Authentication error: ${error.message}`);
}
}
static async getSiteId(siteUrl, token, context) {
console.log('Parsing site URL:', siteUrl);
try {
const urlObj = new URL(siteUrl);
const hostname = urlObj.hostname;
const relativePath = urlObj.pathname;
console.log('Hostname:', hostname, 'Relative path:', relativePath);
const directUrl = `https://graph.microsoft.com/v1.0/sites/${hostname}:${relativePath}`;
console.log('Trying direct site URL:', directUrl);
const directOptions = {
method: 'GET',
url: directUrl,
headers: {
Authorization: `Bearer ${token}`,
},
json: true,
};
const directResponse = await context.helpers.httpRequest(directOptions);
console.log('Direct site response:', directResponse);
if (directResponse.id) {
return directResponse.id;
}
}
catch (directError) {
console.log('Direct site access failed, trying search method:', directError.message);
}
const urlParts = siteUrl.split('sites/');
if (urlParts.length < 2) {
throw new n8n_workflow_1.ApplicationError('Invalid SharePoint site URL format');
}
const siteName = urlParts[1].split('/')[0];
console.log('Extracted site name:', siteName);
const options = {
method: 'GET',
url: `https://graph.microsoft.com/v1.0/sites?search=${siteName}`,
headers: {
Authorization: `Bearer ${token}`,
},
json: true,
};
const response = await context.helpers.httpRequest(options);
console.log('Site search response:', response);
if (!response.value || response.value.length === 0) {
throw new n8n_workflow_1.ApplicationError(`No sites found matching '${siteName}'`);
}
const site = response.value.find((x) => x.name && x.name.toLowerCase() === siteName.toLowerCase());
if (!site) {
const partialMatch = response.value.find((x) => x.name && x.name.toLowerCase().includes(siteName.toLowerCase()));
if (!partialMatch) {
throw new n8n_workflow_1.ApplicationError(`Site '${siteName}' not found. Available sites: ${response.value.map((x) => x.name).join(', ')}`);
}
return partialMatch.id;
}
return site.id;
}
static async getDriveId(siteId, token, context) {
const options = {
method: 'GET',
url: `https://graph.microsoft.com/v1.0/sites/${siteId}/drives`,
headers: {
Authorization: `Bearer ${token}`,
},
json: true,
};
const response = await context.helpers.httpRequest(options);
console.log('Drives response:', response);
if (!response.value || response.value.length === 0) {
throw new n8n_workflow_1.ApplicationError('No drives found for the site');
}
const documentsDrive = response.value.find((drive) => drive.name === 'Documents' ||
drive.webUrl.includes('Shared%20Documents') ||
drive.webUrl.includes('Shared Documents'));
if (documentsDrive) {
console.log('Found Documents drive:', documentsDrive);
return documentsDrive.id;
}
console.log('No Documents drive found, using first drive:', response.value[0]);
return response.value[0].id;
}
static async listDocuments(driveId, libraryPath, token, additionalOptions, context) {
console.log('Listing documents for drive:', driveId, 'path:', libraryPath);
let url;
const decodedPath = decodeURIComponent(libraryPath);
if (libraryPath === '/' ||
libraryPath === '' ||
libraryPath === '/root' ||
libraryPath === '/Shared Documents' ||
libraryPath === 'Shared Documents' ||
libraryPath === '/Shared%20Documents' ||
libraryPath === 'Shared%20Documents' ||
decodedPath === '/Shared Documents' ||
decodedPath === 'Shared Documents') {
url = `https://graph.microsoft.com/v1.0/drives/${driveId}/root/children`;
console.log('Using root children URL');
}
else {
const cleanPath = libraryPath.startsWith('/') ? libraryPath.substring(1) : libraryPath;
url = `https://graph.microsoft.com/v1.0/drives/${driveId}/root:/${cleanPath}:/children`;
console.log('Using path-specific URL with clean path:', cleanPath);
}
const queryParams = new URLSearchParams();
if (additionalOptions.filter) {
queryParams.append('$filter', additionalOptions.filter);
}
if (additionalOptions.select) {
queryParams.append('$select', additionalOptions.select);
}
else {
queryParams.append('$select', 'id,name,lastModifiedDateTime,size,file,folder,webUrl,@microsoft.graph.downloadUrl');
}
if (additionalOptions.top) {
queryParams.append('$top', additionalOptions.top.toString());
}
if (queryParams.toString()) {
url += `?${queryParams.toString()}`;
}
console.log('Documents list URL:', url);
const options = {
method: 'GET',
url,
headers: {
Authorization: `Bearer ${token}`,
},
json: true,
};
const response = await context.helpers.httpRequest(options);
console.log('Documents list response:', response);
const documents = response.value.filter((item) => item.file !== undefined);
return documents.map((doc) => {
var _a;
return ({
id: doc.id,
name: doc.name,
lastModifiedDateTime: doc.lastModifiedDateTime,
size: doc.size,
webUrl: doc.webUrl,
downloadUrl: doc['@microsoft.graph.downloadUrl'],
mimeType: (_a = doc.file) === null || _a === void 0 ? void 0 : _a.mimeType,
});
});
}
static async getDocument(driveId, documentId, token, context) {
var _a;
const options = {
method: 'GET',
url: `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${documentId}`,
headers: {
Authorization: `Bearer ${token}`,
},
json: true,
};
const response = await context.helpers.httpRequest(options);
return {
id: response.id,
name: response.name,
lastModifiedDateTime: response.lastModifiedDateTime,
size: response.size,
webUrl: response.webUrl,
downloadUrl: response['@microsoft.graph.downloadUrl'],
mimeType: (_a = response.file) === null || _a === void 0 ? void 0 : _a.mimeType,
createdDateTime: response.createdDateTime,
lastModifiedBy: response.lastModifiedBy,
createdBy: response.createdBy,
};
}
static async downloadDocument(driveId, documentId, token, context) {
const options = {
method: 'GET',
url: `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${documentId}/content`,
headers: {
Authorization: `Bearer ${token}`,
},
encoding: 'arraybuffer',
};
const response = await context.helpers.httpRequest(options);
return Buffer.from(response);
}
static async getFolderId(driveId, folderPath, token, context) {
var _a, _b;
let url;
if (folderPath === '/' || folderPath === '' || folderPath === '/root') {
url = `https://graph.microsoft.com/v1.0/drives/${driveId}/root`;
}
else {
const cleanPath = folderPath.startsWith('/') ? folderPath.substring(1) : folderPath;
url = `https://graph.microsoft.com/v1.0/drives/${driveId}/root:/${cleanPath}`;
}
const options = {
method: 'GET',
url,
headers: {
Authorization: `Bearer ${token}`,
},
json: true,
};
try {
const response = await context.helpers.httpRequest(options);
if (!response.id) {
throw new n8n_workflow_1.ApplicationError(`Could not find folder: ${folderPath}`);
}
return response.id;
}
catch (error) {
if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 404) {
throw new n8n_workflow_1.ApplicationError(`Destination folder not found: ${folderPath}. Please verify the path exists in SharePoint.`);
}
else if (((_b = error.response) === null || _b === void 0 ? void 0 : _b.status) === 403) {
throw new n8n_workflow_1.ApplicationError(`Access denied to destination folder: ${folderPath}. Please check your permissions.`);
}
throw new n8n_workflow_1.ApplicationError(`Failed to access destination folder ${folderPath}: ${error.message}`);
}
}
}
exports.SmartgentSharePoint = SmartgentSharePoint;
//# sourceMappingURL=SmartgentSharePoint.node.js.map