11 Commits

6 changed files with 2801 additions and 217 deletions

22
README.md Normal file
View File

@@ -0,0 +1,22 @@
[![Release](https://img.shields.io/npm/v/remotestorage-module-chat-messages.svg?style=flat)](https://github.com/67P/remotestorage-module-chat-messages/releases)
# remoteStorage Module: Chat Messages
Stores chat messages in daily archive documents.
Please feel free to open GitHub issues for questions, feature requests,
protocol proposals, and whatever else you like.
## Protocols
### Currently supported
* IRC
* XMPP
### Planned
* Mattermost
* Matrix
* Slack
* ...

2
dist/build.js vendored

File diff suppressed because one or more lines are too long

360
index.js
View File

@@ -1,7 +1,18 @@
var RemoteStorage = require('remotestoragejs');
function pad (num) {
num = String(num);
if (num.length === 1) { num = "0" + num; }
return num;
};
RemoteStorage.defineModule("chat-messages", function (privateClient, publicClient) {
function parseDate (date) {
return {
year: date.getUTCFullYear(),
month: pad( date.getUTCMonth() + 1 ),
day: pad( date.getUTCDate() )
};
};
const ChatMessages = function (privateClient, publicClient) {
/**
* Schema: chat-messages/daily
*
@@ -141,7 +152,7 @@ RemoteStorage.defineModule("chat-messages", function (privateClient, publicClien
* Example for IRC:
*
* (start code)
* var archive = new chatMessages.DailyArchive({
* const archive = new chatMessages.DailyArchive({
* server: {
* type: 'irc',
* name: 'freenode',
@@ -156,7 +167,7 @@ RemoteStorage.defineModule("chat-messages", function (privateClient, publicClien
* Example for XMPP:
*
* (start code)
* var archive = new chatMessages.DailyArchive({
* const archive = new chatMessages.DailyArchive({
* server: {
* type: 'xmpp',
* name: '5apps',
@@ -168,125 +179,125 @@ RemoteStorage.defineModule("chat-messages", function (privateClient, publicClien
* });
* (end code)
*/
var DailyArchive = function DailyArchive(options) {
//
// Defaults
//
options.isPublic = options.isPublic || false;
class DailyArchive {
constructor (options) {
//
// Defaults
//
options.isPublic = options.isPublic || false;
//
// Validate options
//
if (typeof options !== "object") {
throw "options must be an object";
}
if (typeof options.server !== "object" ||
typeof options.server.type !== "string" ||
typeof options.server.name !== "string") {
throw "server must be an object containing at least server \"type\" and \"name\"";
}
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";
//
// Validate options
//
if (typeof options !== "object") {
throw "options must be an object";
}
if (typeof options.server !== "object" ||
typeof options.server.type !== "string" ||
typeof options.server.name !== "string") {
throw "server must be an object containing at least server \"type\" and \"name\"";
}
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";
}
/**
* Property: server
*
* Contains information about the chat server/network
*
* Properties:
* type - Type of server/protocol (e.g. "irc", "xmpp", "campfire", "slack")
* name - Shortname/id/alias of network/server (e.g. "freenode", "mycompanyname")
* ircURI - (optional) IRC URI of network (e.g. "irc://irc.freenode.net/")
* xmppMUC - (optional) XMPP MUC service host (e.g. "conference.jabber.org")
*/
this.server = options.server;
/**
* Property: channelName
*
* Name of the IRC channel (e.g. "#kosmos")
*/
this.channelName = options.channelName;
/**
* Property: date
*
* Date of the archive's content
*/
this.date = options.date;
/**
* Property: isPublic
*
* `true` for public archives, `false` for private ones
*/
this.isPublic = options.isPublic;
/**
* Property: parsedDate
*
* Object containing padded year, month and day of date
*/
this.parsedDate = parseDate(this.date);
/**
* Property: dateId
*
* Date string in the form of YYYY/MM/DD
*/
this.dateId = this.parsedDate.year+'/'+this.parsedDate.month+'/'+this.parsedDate.day;
/**
* Property: path
*
* Document path of the archive file
*/
switch (this.server.type) {
case 'irc':
if (this.channelName.match(/^#/)) {
// normal chatroom
const channelName = this.channelName.replace(/^#/,'');
this.path = `${this.server.name}/channels/${channelName}/${this.dateId}`;
} else {
// user direct message
this.path = `${this.server.name}/users/${this.channelName}/${this.dateId}`;
}
break;
default:
this.path = `${this.server.name}/${this.channelName}/${this.dateId}`;
break;
}
/**
* Property: client
*
* Public or private BaseClient, depending on isPublic
*/
this.client = this.isPublic ? publicClient : privateClient;
/**
* Property: previous
*
* Date of previous log file as YYYY/MM/DD
*/
this.previous = options.previous;
/**
* Property: next
*
* Date of next log file as YYYY/MM/DD
*/
this.next = options.next;
}
/**
* Property: server
*
* Contains information about the chat server/network
*
* Properties:
* type - Type of server/protocol (e.g. "irc", "xmpp", "campfire", "slack")
* name - Shortname/id/alias of network/server (e.g. "freenode", "mycompanyname")
* ircURI - (optional) IRC URI of network (e.g. "irc://irc.freenode.net/")
* xmppMUC - (optional) XMPP MUC service host (e.g. "conference.jabber.org")
*/
this.server = options.server;
/**
* Property: channelName
*
* Name of the IRC channel (e.g. "#kosmos")
*/
this.channelName = options.channelName;
/**
* Property: date
*
* Date of the archive's content
*/
this.date = options.date;
/**
* Property: isPublic
*
* `true` for public archives, `false` for private ones
*/
this.isPublic = options.isPublic;
/**
* Property: parsedDate
*
* Object containing padded year, month and day of date
*/
this.parsedDate = parseDate(this.date);
/**
* Property: dateId
*
* Date string in the form of YYYY/MM/DD
*/
this.dateId = this.parsedDate.year+'/'+this.parsedDate.month+'/'+this.parsedDate.day;
/**
* Property: path
*
* Document path of the archive file
*/
switch (this.server.type) {
case 'irc':
if (this.channelName.match(/^#/)) {
// normal chatroom
var channelName = this.channelName.replace(/^#/,'');
this.path = `${this.server.name}/channels/${channelName}/${this.dateId}`;
} else {
// user direct message
this.path = `${this.server.name}/users/${this.channelName}/${this.dateId}`;
}
break;
default:
this.path = `${this.server.name}/${this.channelName}/${this.dateId}`;
break;
}
/**
* Property: client
*
* Public or private BaseClient, depending on isPublic
*/
this.client = this.isPublic ? publicClient : privateClient;
/**
* Property: previous
*
* Date of previous log file as YYYY/MM/DD
*/
this.previous = options.previous;
/**
* Property: next
*
* Date of next log file as YYYY/MM/DD
*/
this.next = options.next;
};
DailyArchive.prototype = {
/*
* Method: addMessage
*
@@ -296,7 +307,7 @@ RemoteStorage.defineModule("chat-messages", function (privateClient, publicClien
* text - The message itself
* type - Type of message (one of text, join, leave, action)
*/
addMessage: function addMessage(message) {
addMessage (message) {
if (this.isPublic && !this.channelName.match(/^#/)) {
return Promise.resolve(false);
}
@@ -310,7 +321,7 @@ RemoteStorage.defineModule("chat-messages", function (privateClient, publicClien
return this._createDocument(message);
}
});
},
}
/*
* Method: addMessages
@@ -323,7 +334,7 @@ RemoteStorage.defineModule("chat-messages", function (privateClient, publicClien
* overwrite - If true, creates a new archive file and overwrites the
* old one. Defaults to false.
*/
addMessages: function addMessage(messages, overwrite) {
addMessages (messages, overwrite) {
if (this.isPublic && !this.channelName.match(/^#/)) {
return Promise.resolve(false);
}
@@ -345,24 +356,24 @@ RemoteStorage.defineModule("chat-messages", function (privateClient, publicClien
}
});
}
},
}
/*
* Method: remove
*
* Deletes the entire archive document from storage
*/
remove: function() {
remove () {
return this.client.remove(this.path);
},
}
/*
* Method: _updateDocument
*
* Updates and writes an existing archive document
*/
_updateDocument: function(archive, messages) {
RemoteStorage.log('[chat-messages] Updating archive document', archive);
_updateDocument (archive, messages) {
console.debug('[chat-messages] Updating archive document', archive);
if (Array.isArray(messages)) {
messages.forEach(function(message) {
@@ -373,16 +384,16 @@ RemoteStorage.defineModule("chat-messages", function (privateClient, publicClien
}
return this._sync(archive);
},
}
/*
* Method: _createDocument
*
* Creates and writes a new archive document
*/
_createDocument: function(messages) {
RemoteStorage.log('[chat-messages] Creating new archive document');
let archive = this._buildArchiveObject();
_createDocument (messages) {
console.debug('[chat-messages] Creating new archive document');
const archive = this._buildArchiveObject();
if (Array.isArray(messages)) {
messages.forEach((message) => {
@@ -407,17 +418,17 @@ RemoteStorage.defineModule("chat-messages", function (privateClient, publicClien
return this._sync(archive);
});
}
},
}
/*
* Method: _buildArchiveObject
*
* Builds the object to be stored in remote storage
*/
_buildArchiveObject: function() {
let roomName = this.channelName.replace(/#/,'');
_buildArchiveObject () {
const roomName = this.channelName.replace(/#/,'');
let archive = {
const archive = {
"@id": "chat-messages/"+this.server.name+"/channels/"+roomName+"/",
"@type": "ChatChannel",
"name": this.channelName,
@@ -442,82 +453,82 @@ RemoteStorage.defineModule("chat-messages", function (privateClient, publicClien
}
return archive;
},
}
/*
* Method: _updatePreviousArchive
*
* Finds the previous archive document and updates its today.next value
*/
_updatePreviousArchive: function() {
_updatePreviousArchive () {
return this._findPreviousArchive().then((archive) => {
if (typeof archive === 'object' && archive.today) {
archive.today.next = this.dateId;
let path = this.path.substring(0, this.path.length-this.dateId.length)+archive.today['@id'];
const path = this.path.substring(0, this.path.length-this.dateId.length)+archive.today['@id'];
return this.client.storeObject('daily-archive', path, archive).then(() => {
RemoteStorage.log('[chat-messages] Previous archive written to remote storage', path, archive);
console.debug('[chat-messages] Previous archive written to remote storage', path, archive);
return archive;
});
} else {
RemoteStorage.log('[chat-messages] Previous archive not found');
console.debug('[chat-messages] Previous archive not found');
return false;
}
});
},
}
/*
* Method: _findPreviousArchive
*
* Returns the previous archive document
*/
_findPreviousArchive: function() {
_findPreviousArchive () {
const monthPath = this.path.substring(0, this.path.length-2);
const yearPath = this.path.substring(0, this.path.length-5);
const basePath = this.path.substring(0, this.path.length-10);
return this.client.getListing(monthPath).then((listing) => {
let days = Object.keys(listing).map((i) => parseInt(i)).map((i) => {
const days = Object.keys(listing).map((i) => parseInt(i)).map((i) => {
return (i < parseInt(this.parsedDate.day)) ? i : null;
}).filter(function(i){ return i != null; });
if (days.length > 0) {
let day = pad(Math.max(...days).toString());
const day = pad(Math.max(...days).toString());
return this.client.getObject(monthPath+day);
}
// Find last day in previous month
return this.client.getListing(yearPath).then((listing) => {
let months = Object.keys(listing).map((i) => parseInt(i.substr(0,2))).map((i) => {
const months = Object.keys(listing).map((i) => parseInt(i.substr(0,2))).map((i) => {
return (i < parseInt(this.parsedDate.month)) ? i : null;
}).filter(function(i){ return i != null; });
if (months.length > 0) {
let month = pad(Math.max(...months).toString());
const month = pad(Math.max(...months).toString());
return this.client.getListing(yearPath+month+'/').then((listing) => {
let days = Object.keys(listing).map((i) => parseInt(i));
let day = pad(Math.max(...days).toString());
const days = Object.keys(listing).map((i) => parseInt(i));
const day = pad(Math.max(...days).toString());
return this.client.getObject(yearPath+month+'/'+day);
});
} else {
// Find last month and day in previous year
return this.client.getListing(basePath).then((listing) => {
let years = Object.keys(listing).map((i) => parseInt(i.substr(0,4))).map((i) => {
const years = Object.keys(listing).map((i) => parseInt(i.substr(0,4))).map((i) => {
return (i < parseInt(this.parsedDate.year)) ? i : null;
}).filter(function(i){ return i != null; });
if (years.length > 0) {
let year = Math.max(...years).toString();
const year = Math.max(...years).toString();
return this.client.getListing(basePath+year+'/').then((listing) => {
let months = Object.keys(listing).map((i) => parseInt(i.substr(0,2)));
let month = pad(Math.max(...months).toString());
const months = Object.keys(listing).map((i) => parseInt(i.substr(0,2)));
const month = pad(Math.max(...months).toString());
return this.client.getListing(basePath+year+'/'+month+'/').then((listing) => {
let days = Object.keys(listing).map((i) => parseInt(i));
let day = pad(Math.max(...days).toString());
const days = Object.keys(listing).map((i) => parseInt(i));
const day = pad(Math.max(...days).toString());
return this.client.getObject(basePath+year+'/'+month+'/'+day);
});
});
@@ -528,46 +539,33 @@ RemoteStorage.defineModule("chat-messages", function (privateClient, publicClien
}
});
});
},
}
/*
* Method: _sync
*
* Write archive document
*/
_sync: function(obj) {
RemoteStorage.log('[chat-messages] Writing archive object', obj);
_sync (obj) {
console.debug('[chat-messages] Writing archive object', obj);
return this.client.storeObject('daily-archive', this.path, obj).then(function(){
RemoteStorage.log('[chat-messages] Archive written to remote storage');
console.debug('[chat-messages] Archive written to remote storage');
return true;
},function(error){
console.log('[chat-messages] Error trying to store object', error);
console.warn('[chat-messages] Error trying to store object', error);
return error;
});
}
};
var pad = function(num) {
num = String(num);
if (num.length === 1) { num = "0" + num; }
return num;
return {
exports: {
DailyArchive,
privateClient,
publicClient
}
};
};
var parseDate = function(date) {
return {
year: date.getUTCFullYear(),
month: pad( date.getUTCMonth() + 1 ),
day: pad( date.getUTCDate() )
};
};
var exports = {
DailyArchive: DailyArchive,
privateClient: privateClient,
publicClient: publicClient
};
// Return public functions
return { exports: exports };
});
export default { name: 'chat-messages', builder: ChatMessages };

2565
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +1,26 @@
{
"name": "remotestorage-module-chat-messages",
"version": "0.7.1",
"version": "1.0.1",
"description": "Stores chat messages in daily archive files",
"main": "./dist/build.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "NODE_ENV=production webpack",
"dev": "webpack -w",
"build": "NODE_ENV=production webpack"
"start": "npm run dev",
"test": "echo \"Error: no test specified\" && exit 1",
"version": "npm run build && git add dist/"
},
"author": "Kosmos Developers <mail@kosmos.org> (https://kosmos.org)",
"contributors": [
"Sebastian Kippe <sebastian@kip.pe>"
],
"author": "Kosmos Contributors <mail@kosmos.org> (https://kosmos.org)",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/67P/remotestorage-module-chat-messages.git"
},
"devDependencies": {
"babel-core": "^6.18.2",
"babel-loader": "^6.2.7",
"babel-preset-es2015": "^6.18.0",
"webpack": "^1.13.2"
"@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"
}
}

View File

@@ -1,32 +1,31 @@
var webpack = require('webpack');
var isProd = (process.env.NODE_ENV === 'production');
// minimize only in production
var plugins = isProd ? [new webpack.optimize.UglifyJsPlugin({minimize: true })] : []
/* global __dirname */
const isProd = (process.env.NODE_ENV === 'production');
const path = require('path');
module.exports = {
entry: './index.js',
// source map not in production
devtool: !isProd && 'source-map',
entry: ['./index.js'],
output: {
filename: __dirname + '/dist/build.js',
libraryTarget: 'umd'
},
externals: {
"remotestoragejs": {
root: "RemoteStorage", // <script src='remotestorage.js'> will resolve in this.RemoteStorage
commonjs2: "remotestoragejs", // require('remotestoragejs')
commonjs: "remotestoragejs", // require('remotestoragejs')
amd: "remotestoragejs" // define(['remotestoragejs'], ...)
}
path: path.resolve(__dirname, 'dist'),
filename: 'build.js',
library: 'ChatMessages',
libraryTarget: 'umd',
libraryExport: 'default'
},
mode: isProd ? 'production' : 'development',
devtool: isProd ? 'source-map' : 'eval-source-map',
module: {
loaders: [
{ test: /\.js$/, exclude: '/node_modules|dist/', loader: 'babel?presets[]=es2015' },
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
]
},
resolve: {
extensions: ['', '.js']
},
plugins: plugins
// plugins: plugins
};