Compare commits
2 Commits
master
...
feature/ch
Author | SHA1 | Date | |
---|---|---|---|
dcf117e1dd | |||
a0bec9a12c |
23
.drone.yml
23
.drone.yml
@ -1,23 +0,0 @@
|
||||
---
|
||||
kind: pipeline
|
||||
name: node 14
|
||||
|
||||
steps:
|
||||
- name: test
|
||||
image: node:14
|
||||
commands:
|
||||
- npm install
|
||||
- npm run build
|
||||
- npm test
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: node 16
|
||||
|
||||
steps:
|
||||
- name: test
|
||||
image: node:16
|
||||
commands:
|
||||
- npm install
|
||||
- npm run build
|
||||
- npm test
|
3
dist/build.js
vendored
3
dist/build.js
vendored
File diff suppressed because one or more lines are too long
1
dist/build.js.LICENSE.txt
vendored
1
dist/build.js.LICENSE.txt
vendored
@ -1 +0,0 @@
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
2
dist/build.js.map
vendored
2
dist/build.js.map
vendored
File diff suppressed because one or more lines are too long
7040
package-lock.json
generated
7040
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "remotestorage-module-chat-messages",
|
||||
"version": "2.1.1",
|
||||
"version": "2.1.0",
|
||||
"description": "Stores chat messages in daily archive files",
|
||||
"main": "./dist/build.js",
|
||||
"scripts": {
|
||||
"build": "NODE_ENV=production webpack",
|
||||
"dev": "webpack -w",
|
||||
"start": "npm run dev",
|
||||
"test": "mocha tests/",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"version": "npm run build && git add dist/"
|
||||
},
|
||||
"author": "Kosmos Contributors <mail@kosmos.org> (https://kosmos.org)",
|
||||
@ -18,14 +18,11 @@
|
||||
"url": "https://gitea.kosmos.org/kosmos/rs-module-chat-messages.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.10",
|
||||
"@babel/preset-env": "^7.18.10",
|
||||
"babel-loader": "^8.2.5",
|
||||
"chai": "^4.3.6",
|
||||
"mocha": "^10.0.0",
|
||||
"regenerator-runtime": "^0.13.9",
|
||||
"sinon": "^14.0.0",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0"
|
||||
"@babel/core": "^7.14.8",
|
||||
"@babel/preset-env": "^7.14.9",
|
||||
"babel-loader": "^8.2.2",
|
||||
"webpack": "^5.48.0",
|
||||
"webpack-cli": "^4.8.0",
|
||||
"regenerator-runtime": "^0.13.9"
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
@ -354,12 +442,14 @@ const ChatMessages = function (privateClient, publicClient) {
|
||||
* @param {string} message.from - The sender of the message
|
||||
* @param {string} message.text - The message itself
|
||||
* @param {string} message.type - Type of message (one of text, join, leave, action)
|
||||
* @param {string} [message.id] - Unique ID of message. TODO implement
|
||||
* @param {string} [message.id] - Unique ID of message.
|
||||
* @param {string} [message.sid] - Unique stanza ID of message (XMPP only)
|
||||
* @param {boolean} [message.edited] - If the message has been edited
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
addMessage (message) {
|
||||
if (this.isPublic && !this.channelName.match(/^#/)) {
|
||||
if (this.isPublic && !this.channel.name.match(/^#/)) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
@ -384,7 +474,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 +579,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 +595,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':
|
||||
@ -616,7 +706,7 @@ const ChatMessages = function (privateClient, publicClient) {
|
||||
}
|
||||
|
||||
// Only update document if current date is newer than known "last"
|
||||
if (Date.parse(meta.last.replace(/\//g,'-')) < Date.parse(this.date)) {
|
||||
if (Date.parse(meta.last.replace('/','-')) > Date.parse(this.date)) {
|
||||
console.debug('[chat-messages]', 'Updating meta document for channel');
|
||||
meta.last = this.dateId;
|
||||
await this.client.storeObject('daily-archive-meta', this.metaPath, meta);
|
||||
@ -630,10 +720,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?
|
||||
@ -667,16 +757,18 @@ const ChatMessages = function (privateClient, publicClient) {
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
async _sync (archive) {
|
||||
console.debug(`[chat-messages] Writing archive object with ${archive.today.messages.length} messages`);
|
||||
async _sync (obj) {
|
||||
console.debug(`[chat-messages] Writing archive object with ${obj.today.messages.length} messages`);
|
||||
|
||||
return this.client.storeObject('daily-archive', this.path, archive).then(function(){
|
||||
return this.client.storeObject('daily-archive', this.path, obj).then(function(){
|
||||
console.debug('[chat-messages] Archive written to remote storage');
|
||||
return true;
|
||||
},function(error){
|
||||
console.warn('[chat-messages] Error trying to store object', error);
|
||||
return error;
|
||||
});
|
||||
|
||||
// TODO await this.client.startSync();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,114 +0,0 @@
|
||||
const expect = require('chai').expect;
|
||||
const sandbox = require("sinon").createSandbox();
|
||||
const ChatMessages = require('../dist/build');
|
||||
|
||||
const rsClient = {
|
||||
declareType: function() {},
|
||||
getObject: function() {},
|
||||
getListing: function() {},
|
||||
storeObject: function() {},
|
||||
remove: function() {}
|
||||
}
|
||||
|
||||
describe('ChatMessages', function () {
|
||||
|
||||
describe('constructor', function () {
|
||||
let chatMessages;
|
||||
|
||||
before(function() {
|
||||
chatMessages = new ChatMessages.builder(rsClient, rsClient);
|
||||
});
|
||||
|
||||
it('behaves like a remoteStorage module', function () {
|
||||
expect(chatMessages).to.be.an('object');
|
||||
expect(chatMessages.exports).to.be.an('object');
|
||||
});
|
||||
|
||||
it('exports the desired functionality', function () {
|
||||
expect(chatMessages.exports.DailyArchive).to.be.a('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('DailyArchive', function () {
|
||||
let archive;
|
||||
|
||||
before(function() {
|
||||
chatMessages = (new ChatMessages.builder(rsClient, rsClient)).exports;
|
||||
|
||||
archive = new chatMessages.DailyArchive({
|
||||
service: { protocol: 'IRC', domain: 'irc.libera.chat' },
|
||||
channelName: '#kosmos',
|
||||
date: new Date('2022-08-11')
|
||||
});
|
||||
});
|
||||
|
||||
describe('constructor', function () {
|
||||
it('creates an archive instance with the desired properties', function () {
|
||||
expect(archive).to.be.an('object');
|
||||
expect(archive.service.protocol).to.eq('IRC');
|
||||
expect(archive.service.domain).to.eq('irc.libera.chat');
|
||||
expect(archive.channelName).to.eq('#kosmos');
|
||||
expect(archive.channelType).to.eq('room');
|
||||
expect(archive.date).to.be.a('date');
|
||||
expect(archive.parsedDate.year).to.eq(2022);
|
||||
expect(archive.parsedDate.month).to.eq('08');
|
||||
expect(archive.parsedDate.day).to.eq('11');
|
||||
expect(archive.dateId).to.eq('2022/08/11');
|
||||
expect(archive.isPublic).to.eq(false);
|
||||
expect(archive.channelPath).to.eq('irc.libera.chat/channels/kosmos');
|
||||
expect(archive.path).to.eq('irc.libera.chat/channels/kosmos/2022/08/11');
|
||||
expect(archive.metaPath).to.eq('irc.libera.chat/channels/kosmos/meta');
|
||||
expect(archive.client).to.eq(rsClient);
|
||||
expect(archive.previous).to.be.an('undefined');
|
||||
expect(archive.next).to.be.an('undefined');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_updateArchiveMetaDocument', function () {
|
||||
describe('meta up to date', function () {
|
||||
before(function() {
|
||||
sandbox.stub(archive.client, 'getObject').withArgs(archive.metaPath)
|
||||
.returns({
|
||||
'@id': `chat-messages/irc.libera.chat/channels/kosmos/meta`,
|
||||
'@type': 'ChatChannelMeta',
|
||||
first: '2021/01/01', last: '2022/08/11'
|
||||
})
|
||||
sandbox.stub(archive.client, 'storeObject');
|
||||
});
|
||||
|
||||
it('does not store a new archive', async function () {
|
||||
await archive._updateArchiveMetaDocument();
|
||||
sandbox.assert.notCalled(archive.client.storeObject);
|
||||
});
|
||||
|
||||
after(function() { sandbox.restore() });
|
||||
});
|
||||
|
||||
describe('meta needs updating', function () {
|
||||
before(function() {
|
||||
sandbox.stub(archive.client, 'getObject').withArgs(archive.metaPath)
|
||||
.returns({
|
||||
'@id': archive.metaPath,
|
||||
'@type': 'ChatChannelMeta',
|
||||
first: '2021/01/01', last: '2022/08/10'
|
||||
})
|
||||
sandbox.stub(archive.client, 'storeObject');
|
||||
});
|
||||
|
||||
it('stores a new archive', async function () {
|
||||
await archive._updateArchiveMetaDocument();
|
||||
sandbox.assert.calledWithMatch(
|
||||
archive.client.storeObject,
|
||||
'daily-archive-meta', archive.metaPath, {
|
||||
'@id': archive.metaPath, '@type': 'ChatChannelMeta',
|
||||
first: '2021/01/01', last: '2022/08/11'
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
after(function() { sandbox.restore() });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user