UNPKG

botbuilder

Version:

Bot Builder is a framework for building rich bots on virtually any platform.

257 lines • 10.1 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FileTranscriptStore = void 0; /** * @module botbuilder */ /** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ const path_1 = require("path"); const fs_extra_1 = require("fs-extra"); const filenamify = require("filenamify"); /** * @private * The number of .net ticks at the unix epoch. */ const epochTicks = 621355968000000000; /** * @private * There are 10000 .net ticks per millisecond. */ const ticksPerMillisecond = 10000; /** * @private * @param timestamp A date used to calculate future ticks. */ function getTicks(timestamp) { const ticks = epochTicks + timestamp.getTime() * ticksPerMillisecond; return ticks.toString(16); } /** * @private * @param ticks A string containing ticks. */ function readDate(ticks) { const t = Math.round((parseInt(ticks, 16) - epochTicks) / ticksPerMillisecond); return new Date(t); } /** * @private * @param date A date used to create a filter. * @param fileName The filename containing the timestamp string */ function withDateFilter(date, fileName) { if (!date) { return true; } const ticks = fileName.split('-')[0]; return readDate(ticks) >= date; } /** * @private * @param expression A function that will be used to test items. */ function includeWhen(expression) { let shouldInclude = false; return (item) => { return shouldInclude || (shouldInclude = expression(item)); }; } /** * @private * @param json A JSON string to be parsed into an activity. */ function parseActivity(json) { const activity = JSON.parse(json); activity.timestamp = new Date(activity.timestamp); return activity; } /** * The file transcript store stores transcripts in file system with each activity as a file. * * @remarks * This class provides an interface to log all incoming and outgoing activities to the filesystem. * It implements the features necessary to work alongside the TranscriptLoggerMiddleware plugin. * When used in concert, your bot will automatically log all conversations. * * Below is the boilerplate code needed to use this in your app: * ```javascript * const { FileTranscriptStore, TranscriptLoggerMiddleware } = require('botbuilder'); * * adapter.use(new TranscriptLoggerMiddleware(new FileTranscriptStore(__dirname + '/transcripts/'))); * ``` */ class FileTranscriptStore { /** * Creates an instance of FileTranscriptStore. * * @param folder Root folder where transcript will be stored. */ constructor(folder) { if (!folder) { throw new Error('Missing folder.'); } this.rootFolder = folder; } /** * Log an activity to the transcript. * * @param activity Activity being logged. * @returns {Promise<void>} a promise representing the asynchronous operation. */ logActivity(activity) { return __awaiter(this, void 0, void 0, function* () { if (!activity) { throw new Error('activity cannot be null for logActivity()'); } const conversationFolder = this.getTranscriptFolder(activity.channelId, activity.conversation.id); const activityFileName = this.getActivityFilename(activity); return this.saveActivity(activity, conversationFolder, activityFileName); }); } /** * Get all activities associated with a conversation id (aka get the transcript). * * @param channelId Channel Id. * @param conversationId Conversation Id. * @param continuationToken (Optional) Continuation token to page through results. * @param startDate (Optional) Earliest time to include. * @returns {Promise<PagedResult<Activity>>} PagedResult of activities. */ getTranscriptActivities(channelId, conversationId, continuationToken, startDate) { return __awaiter(this, void 0, void 0, function* () { if (!channelId) { throw new Error('Missing channelId'); } if (!conversationId) { throw new Error('Missing conversationId'); } const pagedResult = { items: [], continuationToken: undefined }; const transcriptFolder = this.getTranscriptFolder(channelId, conversationId); const exists = yield fs_extra_1.pathExists(transcriptFolder); if (!exists) { return pagedResult; } const transcriptFolderContents = yield fs_extra_1.readdir(transcriptFolder); const include = includeWhen((fileName) => !continuationToken || path_1.parse(fileName).name === continuationToken); const items = transcriptFolderContents.filter((transcript) => transcript.endsWith('.json') && withDateFilter(startDate, transcript) && include(transcript)); pagedResult.items = yield Promise.all(items .slice(0, FileTranscriptStore.PageSize) .sort() .map((activityFilename) => __awaiter(this, void 0, void 0, function* () { const json = yield fs_extra_1.readFile(path_1.join(transcriptFolder, activityFilename), 'utf8'); return parseActivity(json); }))); const { length } = pagedResult.items; if (length === FileTranscriptStore.PageSize && items[length]) { pagedResult.continuationToken = path_1.parse(items[length]).name; } return pagedResult; }); } /** * List all the logged conversations for a given channelId. * * @param channelId Channel Id. * @param continuationToken (Optional) Continuation token to page through results. * @returns {Promise<PagedResult<TranscriptInfo>>} PagedResult of transcripts. */ listTranscripts(channelId, continuationToken) { return __awaiter(this, void 0, void 0, function* () { if (!channelId) { throw new Error('Missing channelId'); } const pagedResult = { items: [], continuationToken: undefined }; const channelFolder = this.getChannelFolder(channelId); const exists = yield fs_extra_1.pathExists(channelFolder); if (!exists) { return pagedResult; } const channels = yield fs_extra_1.readdir(channelFolder); const items = channels.filter(includeWhen((di) => !continuationToken || di === continuationToken)); pagedResult.items = items .slice(0, FileTranscriptStore.PageSize) .map((i) => ({ channelId: channelId, id: i, created: null })); const { length } = pagedResult.items; if (length === FileTranscriptStore.PageSize && items[length]) { pagedResult.continuationToken = items[length]; } return pagedResult; }); } /** * Delete a conversation and all of it's activities. * * @param channelId Channel Id where conversation took place. * @param conversationId Id of the conversation to delete. * @returns {Promise<void>} A promise representing the asynchronous operation. */ deleteTranscript(channelId, conversationId) { return __awaiter(this, void 0, void 0, function* () { if (!channelId) { throw new Error('Missing channelId'); } if (!conversationId) { throw new Error('Missing conversationId'); } const transcriptFolder = this.getTranscriptFolder(channelId, conversationId); return fs_extra_1.remove(transcriptFolder); }); } /** * Saves the [Activity](xref:botframework-schema.Activity) as a JSON file. * * @param activity The [Activity](xref:botframework-schema.Activity) to transcript. * @param transcriptPath The path where the transcript will be saved. * @param activityFilename The name for the file. * @returns {Promise<void>} A promise representing the asynchronous operation. */ saveActivity(activity, transcriptPath, activityFilename) { return __awaiter(this, void 0, void 0, function* () { const json = JSON.stringify(activity, null, '\t'); const exists = yield fs_extra_1.pathExists(transcriptPath); if (!exists) { yield fs_extra_1.mkdirp(transcriptPath); } return fs_extra_1.writeFile(path_1.join(transcriptPath, activityFilename), json, 'utf8'); }); } /** * @private */ getActivityFilename(activity) { return `${getTicks(activity.timestamp)}-${this.sanitizeKey(activity.id)}.json`; } /** * @private */ getChannelFolder(channelId) { return path_1.join(this.rootFolder, this.sanitizeKey(channelId)); } /** * @private */ getTranscriptFolder(channelId, conversationId) { return path_1.join(this.rootFolder, this.sanitizeKey(channelId), this.sanitizeKey(conversationId)); } /** * @private */ sanitizeKey(key) { return filenamify(key); } } exports.FileTranscriptStore = FileTranscriptStore; FileTranscriptStore.PageSize = 20; //# sourceMappingURL=fileTranscriptStore.js.map