From a0bec9a12c29fc19ac087b37c31b24fddc180f39 Mon Sep 17 00:00:00 2001 From: Sebastian Kippe Date: Tue, 4 Jan 2022 13:13:21 -0600 Subject: [PATCH] WIP Introduce Channel class This is a breaking change, introducing a new Channel class to hold the channel meta information, and functions to open either the last DailyArchive, or one for a specified date. --- src/chat-messages.js | 214 ++++++++++++++++++++++++++++++------------- 1 file changed, 152 insertions(+), 62 deletions(-) diff --git a/src/chat-messages.js b/src/chat-messages.js index 8f73967..ac166a9 100644 --- a/src/chat-messages.js +++ b/src/chat-messages.js @@ -199,55 +199,45 @@ const ChatMessages = function (privateClient, publicClient) { publicClient.declareType("daily-archive-meta", "https://kosmos.org/ns/v2/chat-channel-meta", archiveMetaSchema); /** - * A daily archive stores chat messages by calendar day. + * A chat channel * * @class */ - class DailyArchive { + class Channel { /** * @param {object} options * @param {object} options.service * @param {string} options.service.protocol - Type of chat service/protocol (e.g. "IRC", "XMPP", "Campfire", "Slack") * @param {string} options.service.domain - Domain of the chat service (e.g. "irc.libera.chat", "kosmos.chat") - * @param {string} options.channelName - Name of room/channel (e.g. "#kosmos") - * @param {string} [options.channelType] - Type of channel ("room" or "person") - * @param {date} options.date - Date of archive day - * @param {boolean} [options.isPublic] - Store logs in public folder (defaults to false) - * @param {string} [options.previous] - Date of previous log file as `YYYY/MM/DD`. Looked up automatically when not given - * @param {string} [options.next] - Date of next log file as `YYYY/MM/DD`. looked up automatically when not given + * @param {string} options.name - Name of room/channel (e.g. "#kosmos") + * @param {string} [options.type] - Type of channel ("room" or "person") * * @example - * // IRC archive: - * const archive = new chatMessages.DailyArchive({ + * // IRC channel: + * const channel = new remoteStorage.chatMessages.Channel({ * service: { * protocol: 'IRC', * domain: 'irc.libera.chat', * }, - * channelName: '#kosmos-dev', - * channelType: 'room', - * date: new Date(), - * isPublic: true + * name: '#kosmos-dev', + * type: 'room' * }); * - * // XMPP archive: - * const archive = new chatMessages.DailyArchive({ + * // XMPP channel: + * const channel = new remoteStorage.chatMessages.Channel({ * service: { * protocol: 'XMPP', * domain: 'kosmos.chat', * }, - * channelName: 'kosmos-dev', - * channelType: 'room', - * date: new Date(), - * isPublic: false + * name: 'kosmos-dev', + * type: 'room' * }); - * */ constructor (options) { // // Defaults // - options.isPublic = options.isPublic || false; - options.channelType = options.channelType || "room"; + options.type = options.type || "room"; // // Validate options @@ -258,16 +248,10 @@ const ChatMessages = function (privateClient, publicClient) { if (typeof options.service !== "object" || typeof options.service.protocol !== "string" || typeof options.service.domain !== "string") { - throw "service must be an object containing at least service \"protocol\" and \"domain\""; + throw "options.service must be an object containing at least service \"protocol\" and \"domain\""; } - if (typeof options.channelName !== "string") { - throw "channelName must be a string"; - } - if (!(options.date instanceof Date)) { - throw "date must be a date object"; - } - if (typeof options.isPublic !== "boolean") { - throw "isPublic must be a boolean value"; + if (typeof options.name !== "string") { + throw "options.name must be a string"; } /** @@ -278,14 +262,130 @@ const ChatMessages = function (privateClient, publicClient) { this.service = options.service; /** - * @property {string} channelName - Name of channel (e.g. "#kosmos") + * @property {string} name - Name of channel (e.g. "#kosmos") */ - this.channelName = options.channelName; + this.name = options.name; /** - * @property {string} channelType - Type of channel ("room" or "person") + * @property {string} type - Type of channel ("room" or "person") */ - this.channelType = options.channelType; + this.type = options.type; + + /** + * @property {string} path - Base directory path of the channel archives + */ + if (this.type === "room") { + // Normal chatroom + const name = this.name.replace(/#/,''); + this.path = `${this.service.domain}/channels/${name}`; + } else { + // User direct messages + this.path = `${this.service.domain}/users/${this.name}`; + } + + /** + * @property {string} metaPath - Path of the channel's metadata document + */ + this.metaPath = `${this.path}/meta`; + } + + /* + * Returns the last stored DailyArchive, or a new one for the current day. + * + * @param {object} options + * @param {boolean} [options.public] - Public or private archive. Defaults to 'false'. + * + * @returns {Promise} + */ + async openLastDailyArchive (options={}) { + const client = options.public ? publicClient : privateClient; + const meta = await client.getObject(this.metaPath); + + if (meta && meta.last) { + return new DailyArchive({ + channel: this, + date: new Date(meta.last.replace(/\//g,'-')), + isPublic: options.public + }); + } else { + return new DailyArchive({ + channel: this, + date: new Date(), + isPublic: options.public + }); + } + } + + /* + * Returns a DailyArchive for the given date + * + * @param {object} options + * @param {date} options.date - Date of archive day + * @param {boolean} [options.public] - Public or private archive. Defaults to 'false'. + * + * @returns {Promise} + */ + openDailyArchive (options={}) { + if (!(options.date instanceof Date)) { + throw 'options.date must be a date object'; + } + + return new DailyArchive({ + channel: this, + date: options.date, + isPublic: options.public + }); + } + } + + /** + * A daily archive stores chat messages by calendar day. + * + * @class + */ + class DailyArchive { + /** + * @param {object} options + * @param {string} options.channel - A Channel instance + * @param {date} options.date - Date of archive day + * @param {boolean} [options.isPublic] - Store logs in public folder (defaults to false) + * @param {string} [options.previous] - Date of previous log file as `YYYY/MM/DD`. Looked up automatically when not given + * @param {string} [options.next] - Date of next log file as `YYYY/MM/DD`. Looked up automatically when not given + * + * @example + * const channel = new remoteStorage.chatMessages.Channel({ ...options }) + * const archive = new chatMessages.DailyArchive({ + * channel: channel, + * date: new Date(), + * isPublic: true + * }); + */ + constructor (options) { + // + // Defaults + // + options.isPublic = options.isPublic || false; + + // + // Validate options + // + if (typeof options !== "object") { + throw "options must be an object"; + } + if (!(options.channel instanceof Channel)) { + throw "options.channel must be a Channel object"; + } + if (!(options.date instanceof Date)) { + throw "options.date must be a date object"; + } + if (typeof options.isPublic !== "boolean") { + throw "options.isPublic must be a boolean value"; + } + + /** + * @property {Channel} channel - A Channel instance + */ + this.channel = options.channel; /** * @property {string} date - Gregorian calendar date of the archive's content @@ -310,27 +410,15 @@ const ChatMessages = function (privateClient, publicClient) { */ this.dateId = this.parsedDate.year+'/'+this.parsedDate.month+'/'+this.parsedDate.day; - /** - * @property {string} channelPath - Base directory path of the channel archives - */ - if (this.channelType === "room") { - // Normal chatroom - const channelName = this.channelName.replace(/#/,''); - this.channelPath = `${this.service.domain}/channels/${channelName}`; - } else { - // User direct messages - this.channelPath = `${this.service.domain}/users/${this.channelName}`; - } - /** * @property {string} path - Path of the archive document */ - this.path = `${this.channelPath}/${this.dateId}`; + this.path = `${this.channel.path}/${this.dateId}`; /** * @property {string} metaPath - Path of the channel's metadata document */ - this.metaPath = `${this.channelPath}/meta`; + this.metaPath = `${this.channel.path}/meta`; /** * @property {object} client - Public or private remoteStorgage.js BaseClient @@ -359,7 +447,7 @@ const ChatMessages = function (privateClient, publicClient) { * @returns {Promise} */ addMessage (message) { - if (this.isPublic && !this.channelName.match(/^#/)) { + if (this.isPublic && !this.channel.name.match(/^#/)) { return Promise.resolve(false); } @@ -384,7 +472,7 @@ const ChatMessages = function (privateClient, publicClient) { * @returns {Promise} */ addMessages (messages, overwrite) { - if (this.isPublic && !this.channelName.match(/^#/)) { + if (this.isPublic && !this.channel.name.match(/^#/)) { return Promise.resolve(false); } @@ -489,14 +577,14 @@ const ChatMessages = function (privateClient, publicClient) { * @private */ _buildArchiveObject () { - const roomName = this.channelName.replace(/#/,''); + const roomName = this.channel.name.replace(/#/,''); const archive = { - "@id": "chat-messages/"+this.service.domain+"/channels/"+roomName+"/", + "@id": "chat-messages/"+this.channel.service.domain+"/channels/"+roomName+"/", "@type": "ChatChannel", - "service": this.service, - "name": this.channelName, - "type": this.channelType, + "service": this.channel.service, + "name": this.channel.name, + "type": this.channel.type, "today": { "@id": this.dateId, "@type": "ChatLog", @@ -505,10 +593,10 @@ const ChatMessages = function (privateClient, publicClient) { } }; - switch (this.service.protocol) { + switch (this.channel.service.protocol) { case 'IRC': - if (!this.channelName.match(/^#/)) { - archive["@id"] = "chat-messages/"+this.service.domain+"/users/"+this.channelName+"/"; + if (!this.channel.name.match(/^#/)) { + archive["@id"] = "chat-messages/"+this.channel.service.domain+"/users/"+this.channel.name+"/"; } break; case 'XMPP': @@ -630,10 +718,10 @@ const ChatMessages = function (privateClient, publicClient) { // When creating a new meta doc, we need to find the oldest archive, // because older versions of the module did not write a meta doc. const first = await this._findFirstArchive(); - const roomName = this.channelName.replace(/#/,''); + const roomName = this.channel.name.replace(/#/,''); const meta = { - '@id': `chat-messages/${this.service.domain}/channels/${roomName}/meta`, + '@id': `chat-messages/${this.channel.service.domain}/channels/${roomName}/meta`, '@type': 'ChatChannelMeta', first: first, last: this.dateId // TODO might have to search for last? @@ -677,6 +765,8 @@ const ChatMessages = function (privateClient, publicClient) { console.warn('[chat-messages] Error trying to store object', error); return error; }); + + // TODO await this.client.startSync(); } };