/*****************************************************************************\ | █████ █████ ██ █ █████ █████ ████ ██ ████ █████ █████ ███ ® | | ██ █ ███ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ | | ██ ███ ████ █ ██ █ ████ █████ ██████ ██ ████ █ █ █ ██ | | ██ ██ █ █ ██ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ | | █████ █████ █ ███ █████ ██ ██ ██ ██ █████ ████ █████ █ ███ | | | | General Bots Copyright (c) pragmatismo.com.br. All rights reserved. | | Licensed under the AGPL-3.0. | | | | According to our dual licensing model, this program can be used either | | under the terms of the GNU Affero General Public License, version 3, | | or under a proprietary license. | | | | The texts of the GNU Affero General Public License with an additional | | permission and of our proprietary license can be found at and | | in the LICENSE file you have received along with this program. | | | | This program is distributed in the hope that it will be useful, | | but WITHOUT ANY WARRANTY, without even the implied warranty of | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | | GNU Affero General Public License for more details. | | | | "General Bots" is a registered trademark of pragmatismo.com.br. | | The licensing of the program under the AGPLv3 does not imply a | | trademark license. Therefore any rights, title and interest in | | our trademarks remain entirely with us. | | | \*****************************************************************************/ "use strict" import { GBMinInstance, GBLog } from "botlib"; import { CollectionUtil } from 'pragmatismo-io-framework'; import MicrosoftGraph from "@microsoft/microsoft-graph-client"; import sgMail from '@sendgrid/mail'; import { default as PasswordGenerator } from 'strict-password-generator'; import { promises as fs } from 'fs'; import { existsSync } from 'fs'; import { GBConfigService } from "../../core.gbapp/services/GBConfigService.js"; import path from "path"; import { Client } from "minio"; export class GBOService { public isValidCardNumber(ccNumber) { } public isValidSecurityCode(ccNumber, cvcNumber) { } public isValidExpireDate(month, year) { } public async sendEmail(token: string, to: string, from: string, subject: string, text: string, html: string) { return new Promise((resolve, reject) => { sgMail.setApiKey(token); const msg = { to: to, from: from, subject: subject, text: text, html: html }; sgMail.send(msg, false, (err, res) => { if (err) { reject(err) } else { resolve(res); } }); }); } public async listTemplates(min: GBMinInstance) { if (GBConfigService.get('GB_MODE') === 'legacy') { let templateLibraryId = process.env.SAAS_TEMPLATE_LIBRARY; let siteId = process.env.STORAGE_SITE_ID; let token = await (min.adminService as any).acquireElevatedToken(min.instance.instanceId, true); let client = MicrosoftGraph.Client.init({ authProvider: done => { done(null, token); } }); const packagePath = `/`; let res = await client.api( `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${templateLibraryId}/drive/root/children`) .get(); return res.value; } else { const templatesDir = path.join(process.env.PWD, 'templates'); const gbaiDirectories = []; // Read all entries in the templates directory const entries = await fs.readdir(templatesDir, { withFileTypes: true }); for (const entry of entries) { // Check if it's a directory and ends with .gbai if (entry.isDirectory() && entry.name.endsWith('.gbai')) { gbaiDirectories.push({ name: entry.name }); } } return gbaiDirectories; } } public async copyTemplates(min: GBMinInstance, gbaiDest: any, templateName: string, kind: string, botName: string): Promise { const storageMode = process.env.GB_MODE; if (storageMode === 'legacy') { // Microsoft Graph (SharePoint) Implementation const token = await (min.adminService as any).acquireElevatedToken(min.instance.instanceId, true); const siteId = process.env.STORAGE_SITE_ID; const templateLibraryId = process.env.SAAS_TEMPLATE_LIBRARY; const libraryId = process.env.STORAGE_LIBRARY; const client = MicrosoftGraph.Client.init({ authProvider: (done) => done(null, token) }); const packageName = `${templateName.split('.')[0]}.${kind}`; const destinationName = `${botName}.${kind}`; try { // Try direct copy first const src = await client.api( `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${templateLibraryId}/drive/root:/${templateName}/${packageName}` ).get(); await client.api( `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${templateLibraryId}/drive/items/${src.id}/copy` ).post({ parentReference: { driveId: gbaiDest.parentReference.driveId, id: gbaiDest.id }, name: destinationName }); } catch (error) { if (error.code === "nameAlreadyExists") { // Handle existing destination by copying contents individually const srcItems = await client.api( `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${templateLibraryId}/drive/root:/${templateName}/${packageName}:/children` ).get(); const dstPath = `${botName}.gbai/${destinationName}`; const dst = await client.api( `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${libraryId}/drive/root:/${dstPath}` ).get(); await CollectionUtil.asyncForEach(srcItems.value, async (item) => { await client.api( `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${templateLibraryId}/drive/items/${item.id}/copy` ).post({ parentReference: { driveId: dst.parentReference.driveId, id: dst.id } }); }); } else { GBLog.error(`Failed to copy templates: ${error.message}`); throw error; } } } else if (storageMode === 'gbcluster') { // MinIO Implementation const minioClient = new Client({ endPoint: process.env.DRIVE_SERVER, port: parseInt(process.env.DRIVE_PORT), useSSL: process.env.DRIVE_USE_SSL === 'true', accessKey: process.env.DRIVE_ACCESSKEY, secretKey: process.env.DRIVE_SECRET, }); const bucketName = `${process.env.DRIVE_ORG_PREFIX}${botName}.gbai`.toLowerCase(); const packageName = `${templateName.split('.')[0]}.${kind}`; const localTemplatePath = path.join(process.env.PWD, 'templates', templateName, packageName); const minioDestinationPath = `${botName}.${kind}`; const uploadDirectory = async (localPath: string, minioPath: string = '') => { // Ensure the bucket exists in local file system if (existsSync(localPath)) { const entries = await fs.readdir(localPath, { withFileTypes: true }); for (const entry of entries) { const fullLocalPath = path.join(localPath, entry.name); const objectName = path.posix.join(minioPath, entry.name); if (entry.isDirectory()) { await uploadDirectory(fullLocalPath, objectName); } else { const fileContent = await fs.readFile(fullLocalPath); await minioClient.putObject(bucketName, objectName, fileContent); GBLog.info(`Uploaded ${objectName} to MinIO bucket ${bucketName}`); } } } else { GBLog.verbose(`Package ${localPath} does not exist on templates.`); } }; await uploadDirectory(localTemplatePath, minioDestinationPath); } } public async createExcelFile(min: GBMinInstance, destinationFolder: any, name: string) { let token = await (min.adminService.acquireElevatedToken as any)(min.instance.instanceId, true); let siteId = process.env.STORAGE_SITE_ID; let templateLibraryId = process.env.SAAS_TEMPLATE_LIBRARY; let client = MicrosoftGraph.Client.init({ authProvider: done => { done(null, token); } }); const body = { "parentReference": { driveId: destinationFolder.parentReference.driveId, id: destinationFolder.id }, "name": name } try { const src = await client.api( `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${templateLibraryId}/drive/root:/System.gbdata/blank.xlsx`) .get(); return await client.api( `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${templateLibraryId}/drive/items/${src.id}/copy`) .post(body); } catch (error) { GBLog.error(error); throw error; } } public async shareFolder(token: string, driveId: string, itemId: string, email: string) { return new Promise((resolve, reject) => { let client = MicrosoftGraph.Client.init({ authProvider: done => { done(null, token); } }); const body = { "recipients": [ { "email": email } ], "message": "General Bots Online - Packages folder", "requireSignIn": true, "sendInvitation": true, "roles": ["write"] }; client.api(`https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/invite`) .post(body, (err, res) => { if (err) { GBLog.error('Sharing: ' + err); reject(err) } else { resolve(res); } }); }); } public kmpSearch(pattern, text) { pattern = pattern.toLowerCase(); text = text.toLowerCase(); if (pattern.length == 0) return 0; // Immediate match // Compute longest suffix-prefix table var lsp = [0]; // Base case for (var i = 1; i < pattern.length; i++) { var j = lsp[i - 1]; // Start by assuming we're extending the previous LSP while (j > 0 && pattern.charAt(i) != pattern.charAt(j)) j = lsp[j - 1]; if (pattern.charAt(i) == pattern.charAt(j)) j++; lsp.push(j); } // Walk through text string var j = 0; // Number of chars matched in pattern for (var i = 0; i < text.length; i++) { while (j > 0 && text.charAt(i) != pattern.charAt(j)) j = lsp[j - 1]; // Fall back in the pattern if (text.charAt(i) == pattern.charAt(j)) { j++; // Next char matched, increment position if (j == pattern.length) return i - (j - 1); } } return -1; // Not found } public getAddressFromZipCode() { // https://maps.googleapis.com/maps/api/geocode/json?address=94040 } /** * Retrives token and initialize drive client API. */ public static async internalGetDriveClient(min: GBMinInstance) { let token = await (min.adminService as any).acquireElevatedToken(0, true); let siteId = process.env.STORAGE_SITE_ID; let libraryId = process.env.STORAGE_LIBRARY; let client = MicrosoftGraph.Client.init({ authProvider: done => { done(null, token); } }); const baseUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${libraryId}`; return [baseUrl, client]; } /** * Retrives a document from the drive, given a path and filename. */ private async internalGetDocument(client: any, baseUrl: any, path: string, file: string) { let res = await client .api(`${baseUrl}/drive/root:${path}:/children`) .get(); let documents = res.value.filter(m => { return m.name.toLowerCase() === file.toLowerCase(); }); if (!documents || documents.length === 0) { throw `File '${file}' specified on GBasic command not found. Check the .gbdata or the .gbdialog associated.`; } return documents[0]; } public async createRootFolder(token: string, name: string, siteId: string, libraryId: string) { const storageMode = process.env.GB_MODE; if (storageMode === 'gbcluster') { // Minio implementation const minioClient = new Client({ endPoint: process.env.DRIVE_SERVER, port: parseInt(process.env.DRIVE_PORT), useSSL: process.env.DRIVE_USE_SSL === 'true', accessKey: process.env.DRIVE_ACCESSKEY, secretKey: process.env.DRIVE_SECRET, }); // Ensure bucket exists name = `${process.env.DRIVE_ORG_PREFIX}${name}`.toLowerCase(); const bucketExists = await minioClient.bucketExists(name); if (!bucketExists) { await minioClient.makeBucket(name); } return { name: name, folder: {} }; // Return similar structure to MS Graph } else { // Original MS Graph implementation return new Promise((resolve, reject) => { let client = MicrosoftGraph.Client.init({ authProvider: done => { done(null, token); } }); const body = { "name": name, "folder": {}, "@microsoft.graph.conflictBehavior": "rename" } client.api(`https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${libraryId}/drive/root/children`) .post(body, (err, res) => { if (err) { reject(err) } else { resolve(res); } }); }); } } }