12 Commits

Author SHA1 Message Date
189cd1f86d 2.1.0 2021-11-13 21:59:53 +01:00
8db448c4a0 Make log less verbose 2021-11-13 21:59:34 +01:00
84613ed3a3 Merge pull request 'Add/store channel archive metadata document' (#8) from feature/7-archive_metadata into master
Reviewed-on: #8
2021-11-13 20:56:18 +00:00
f83ef92adc Remove superfluous sort function
#8 (comment)

Co-authored-by: Garret Alfert <alfert@wevelop.de>
2021-11-13 21:53:56 +01:00
54352e09e8 Store meta document with first/last archive ID 2021-11-08 12:55:42 +01:00
cd93cd4167 Add archive metadata schema, RS type 2021-11-04 13:18:55 +01:00
9f477ba14b Update badge link 2021-09-13 14:31:04 +02:00
328be7ed75 2.0.0 2021-09-13 14:08:10 +02:00
cf8c43bede Merge pull request 'Add proper README' (#6) from docs/readme into master
Reviewed-on: #6
2021-09-13 12:06:56 +00:00
188b45e778 Add proper README
* Document usage
* Update communication options for support/issues
2021-09-13 14:03:41 +02:00
d331af7256 Merge pull request 'Various pre-release fixes' (#5) from dev/prerelease_fixes into master
Reviewed-on: #5
2021-09-13 11:43:27 +00:00
9ba618f38e Update repo URL, add website 2021-09-13 13:40:24 +02:00
6 changed files with 188 additions and 39 deletions

View File

@@ -1,22 +1,40 @@
[![Release](https://img.shields.io/npm/v/remotestorage-module-chat-messages.svg?style=flat)](https://github.com/67P/remotestorage-module-chat-messages/releases)
[![Release](https://img.shields.io/npm/v/remotestorage-module-chat-messages.svg?style=flat)](https://www.npmjs.com/package/remotestorage-module-chat-messages)
# 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.
## Usage
## Protocols
Open a daily archive and write messages to it:
### Currently supported
```js
const RemoteStorage = require("remotestoragejs");
const ChatMessages = require("remotestorage-module-chat-messages");
const remoteStorage = new RemoteStorage({ modules: [ ChatMessages ] });
* IRC
* XMPP
const archive = new remoteStorage.chatMessages.DailyArchive({
service: {
protocol: 'IRC',
domain: 'irc.libera.chat'
},
channelName: '#kosmos',
date: new Date(),
isPublic: true // Channel logs will be written to public folder
});
### Planned
const messages = [
{ "date": "2015-06-05T17:35:28.454Z", "user": "jimmy", "text": "knock knock" },
{ "date": "2015-06-05T17:36:05.123Z", "user": "walter", "text": "who's there?" }
];
* Mattermost
* Matrix
* Slack
* ...
archive.addMessages(messages);
```
See the inline source code documentation (JSDoc) for usage details and function
arguments. For a real-world integration example, see
[hubot-remotestorage-logger](https://github.com/67P/hubot-remotestorage-logger/).
## Support, bugs, feedback, questions
Come and chat with us: https://wiki.kosmos.org/Main_Page#Chat

2
dist/build.js vendored

File diff suppressed because one or more lines are too long

2
dist/build.js.map vendored

File diff suppressed because one or more lines are too long

8
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "remotestorage-module-chat-messages",
"version": "1.0.1",
"version": "2.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -2215,6 +2215,12 @@
"regenerate": "^1.4.0"
}
},
"regenerator-runtime": {
"version": "0.13.9",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==",
"dev": true
},
"resolve": {
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "remotestorage-module-chat-messages",
"version": "1.0.1",
"version": "2.1.0",
"description": "Stores chat messages in daily archive files",
"main": "./dist/build.js",
"scripts": {
@@ -12,15 +12,17 @@
},
"author": "Kosmos Contributors <mail@kosmos.org> (https://kosmos.org)",
"license": "MIT",
"homepage": "https://gitea.kosmos.org/kosmos/rs-module-chat-messages",
"repository": {
"type": "git",
"url": "https://github.com/67P/remotestorage-module-chat-messages.git"
"url": "https://gitea.kosmos.org/kosmos/rs-module-chat-messages.git"
},
"devDependencies": {
"@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"
"webpack-cli": "^4.8.0",
"regenerator-runtime": "^0.13.9"
}
}

View File

@@ -1,3 +1,5 @@
import 'regenerator-runtime/runtime';
function pad (num) {
num = String(num);
if (num.length === 1) { num = "0" + num; }
@@ -12,9 +14,17 @@ function parseDate (date) {
};
};
function lowestNumberInListing(items) {
const sortedNumbers = Object.keys(items)
.map(i => parseInt(i))
.filter(i => !Number.isNaN(i))
.sort();
return sortedNumbers[0];
}
const ChatMessages = function (privateClient, publicClient) {
/**
* Schema: chat-messages/daily
* Schema: chat-messages/daily-archive
*
* Represents one calendar day of chat messages
*
@@ -48,8 +58,7 @@ const ChatMessages = function (privateClient, publicClient) {
"properties": {
"@context": {
"type": "string",
"default": "https://kosmos.org/ns/v2",
"enum": ["https://kosmos.org/ns/v2"]
"default": "https://kosmos.org/ns/v2/chat-channel"
},
"@id": {
"type": "string",
@@ -141,8 +150,53 @@ const ChatMessages = function (privateClient, publicClient) {
"required": []
};
privateClient.declareType("daily-archive", "https://kosmos.org/ns/v2", archiveSchema);
publicClient.declareType("daily-archive", "https://kosmos.org/ns/v2", archiveSchema);
privateClient.declareType("daily-archive", "https://kosmos.org/ns/v2/chat-channel", archiveSchema);
publicClient.declareType("daily-archive", "https://kosmos.org/ns/v2/chat-channel", archiveSchema);
/**
* Schema: chat-messages/daily-archive-meta
*
* Stores meta information about the daily archives
*
* @example
* {
* "@context": "https://kosmos.org/ns/v2",
* "@id": "chat-messages/irc.libera.chat/channels/kosmos/meta",
* "@type": "ChatChannelMeta",
* "first": "2009/01/03",
* "last": "2021/11/05"
* }
* }
*/
const archiveMetaSchema = {
"type": "object",
"properties": {
"@context": {
"type": "string",
"default": "https://kosmos.org/ns/v2/chat-channel-meta"
},
"@id": {
"type": "string",
},
"@type": {
"type": "string",
"default": "ChatChannelMeta",
"enum": ["ChatChannelMeta"]
},
"first": {
"type": "string",
"pattern": "^[0-9]{4}\/[0-9]{2}\/[0-9]{2}$"
},
"last": {
"type": "string",
"pattern": "^[0-9]{4}\/[0-9]{2}\/[0-9]{2}$"
}
},
"required": ["@id", "first", "last"]
};
privateClient.declareType("daily-archive-meta", "https://kosmos.org/ns/v2/chat-channel-meta", archiveMetaSchema);
publicClient.declareType("daily-archive-meta", "https://kosmos.org/ns/v2/chat-channel-meta", archiveMetaSchema);
/**
* A daily archive stores chat messages by calendar day.
@@ -257,17 +311,27 @@ const ChatMessages = function (privateClient, publicClient) {
this.dateId = this.parsedDate.year+'/'+this.parsedDate.month+'/'+this.parsedDate.day;
/**
* @property {string} path - Document path of the archive file
* @property {string} channelPath - Base directory path of the channel archives
*/
if (this.channelType === "room") {
// Normal chatroom
const channelName = this.channelName.replace(/#/,'');
this.path = `${this.service.domain}/channels/${channelName}/${this.dateId}`;
this.channelPath = `${this.service.domain}/channels/${channelName}`;
} else {
// User direct message
this.path = `${this.service.domain}/users/${this.channelName}/${this.dateId}`;
// 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}`;
/**
* @property {string} metaPath - Path of the channel's metadata document
*/
this.metaPath = `${this.channelPath}/meta`;
/**
* @property {object} client - Public or private remoteStorgage.js BaseClient
*/
@@ -349,6 +413,8 @@ const ChatMessages = function (privateClient, publicClient) {
* @returns {Promise}
*/
remove () {
// TODO when removing, if previous is set, but not next, it means the
// removed file is the last archive. Thus, set "last" to previous file.
return this.client.remove(this.path);
}
@@ -359,7 +425,7 @@ const ChatMessages = function (privateClient, publicClient) {
*
* @private
*/
_updateDocument (archive, messages) {
async _updateDocument (archive, messages) {
console.debug('[chat-messages] Updating archive document');
if (Array.isArray(messages)) {
@@ -380,12 +446,12 @@ const ChatMessages = function (privateClient, publicClient) {
*
* @private
*/
_createDocument (messages) {
async _createDocument (messages) {
console.debug('[chat-messages] Creating new archive document');
const archive = this._buildArchiveObject();
if (Array.isArray(messages)) {
messages.forEach((message) => {
messages.forEach(message => {
archive.today.messages.push(message);
});
} else {
@@ -397,16 +463,22 @@ const ChatMessages = function (privateClient, publicClient) {
// That includes setting 'next' in the previous log file
if (this.previous) { archive.today.previous = this.previous; }
if (this.next) { archive.today.next = this.next; }
return this._sync(archive);
} else {
// Find and update previous archive, set 'previous' on this one
return this._updatePreviousArchive().then((previous) => {
if (typeof previous === 'object') {
archive.today.previous = previous.today['@id'];
}
return this._sync(archive);
});
const previous = await this._updatePreviousArchive();
if (typeof previous === 'object') {
archive.today.previous = previous.today['@id'];
}
}
await this._sync(archive);
// TODO only write meta doc if argument is set on addMessages. This way
// we can avoid race conditions when syncing remote chat messages all at
// once for multiple days
await this._updateArchiveMetaDocument();
return;
}
/*
@@ -461,7 +533,7 @@ const ChatMessages = function (privateClient, publicClient) {
const path = this.path.substring(0, this.path.length-this.dateId.length)+archive.today['@id'];
return this.client.storeObject('daily-archive', path, archive).then(() => {
console.debug('[chat-messages] Previous archive written to remote storage', path, archive);
console.debug('[chat-messages] Previous archive written to remote storage at', path);
return archive;
});
} else {
@@ -537,6 +609,57 @@ const ChatMessages = function (privateClient, publicClient) {
});
}
async _updateArchiveMetaDocument () {
const meta = await this.client.getObject(this.metaPath);
if (typeof meta !== 'object') {
return this._createArchiveMetaDocument();
}
// Only update document if current date is newer than known "last"
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);
}
return;
}
async _createArchiveMetaDocument () {
console.debug('[chat-messages]', 'Creating new meta document for channel');
// 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 meta = {
'@id': `chat-messages/${this.service.domain}/channels/${roomName}/meta`,
'@type': 'ChatChannelMeta',
first: first,
last: this.dateId // TODO might have to search for last?
};
return this.client.storeObject('daily-archive-meta', this.metaPath, meta)
.then(() => console.debug('[chat-messages]', 'Meta document written to remote storage'))
.catch(e => {
console.log('[chat-messages]', `Failed to store ${this.metaPath}`);
console.error(e);
});
}
async _findFirstArchive () {
console.debug('[chat-messages]', 'Finding first archive for channel');
const years = await this.client.getListing(`${this.channelPath}/`);
const year = lowestNumberInListing(years);
const months = await this.client.getListing(`${this.channelPath}/${year}/`);
const month = lowestNumberInListing(months);
const days = await this.client.getListing(`${this.channelPath}/${year}/${pad(month)}/`);
const day = lowestNumberInListing(days);
const firstId = `${year}/${pad(month)}/${pad(day)}`;
console.debug('[chat-messages]', 'First is', firstId);
return firstId;
}
/*
* Write archive document
*
@@ -544,7 +667,7 @@ const ChatMessages = function (privateClient, publicClient) {
*
* @private
*/
_sync (obj) {
async _sync (obj) {
console.debug(`[chat-messages] Writing archive object with ${obj.today.messages.length} messages`);
return this.client.storeObject('daily-archive', this.path, obj).then(function(){