From 6f52de162687b4e6008bf759e2c7fb1b6310d305 Mon Sep 17 00:00:00 2001 From: Denperidge Date: Thu, 9 Feb 2023 11:43:56 +0100 Subject: [PATCH 01/10] Added Matrix env variable configuration Changes: - Hardcoded Matrix vars in gitter-solid & terminal-app are now fallback - Documented env var configuration options in README - Added .env.example --- .env.example | 15 +++++++++++++++ README.md | 11 ++++++++++- gitter-solid.js | 16 +++++++++------- terminal-app.js | 23 +++++++++++++++-------- 4 files changed, 49 insertions(+), 16 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..9f69bd6 --- /dev/null +++ b/.env.example @@ -0,0 +1,15 @@ +# Solid configuration +# Only needed when saving to Solid as opposed to your local filesystem +SOLID_IDP=https://storage.inrupt.com +SOLID_USERNAME=yourusername +SOLID_PASSWORD= + +# Matrix configuration +# Only needed when the Matrix API will be used +MATRIX_USER_ID=@your:matrixid.org +MATRIX_ACCESS_TOKEN=syt_YoUr_MatrIx_Access_TokeN +MATRIX_BASE_URL=http://matrix.org + +# Gitter configuration +# Only needed when the (deprecated) Gitter API will be uesd +GITTER_TOKEN= \ No newline at end of file diff --git a/README.md b/README.md index 45601e4..911491c 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,16 @@ analysis tools on top of your gitter chat data? Maybe you should be using this s ## Usage +The variables referenced in this section can be configured through any of the following: +- (When using the terminal script) providing them when prompted +- Using `export` directly in your shell +- Creating a .env file (see [.env.example](.env.example)) + ### Solid access If you will be storing your chats on the local filesystem, you don't need Solid access. -If you will be storing your chats on a Solid Pod (either local or remote), you will need to provide login credentials to give your script write access. You can export enviornoment variables (SOLID_IDP, SOLID_USERNAME, SOLID_PASSWORD) or let the script prompt you for the values if the environment variables are not found. +If you will be storing your chats on a Solid Pod (either local or remote), you will need to provide login credentials to give your script write access. You can export environment variables (SOLID_IDP, SOLID_USERNAME, SOLID_PASSWORD) or let the script prompt you for the values if the environment variables are not found. ### Gitter access @@ -36,6 +41,10 @@ node gitter-solid.js list Once you have set your shell session up with the gitter token, you can use gitter-solid repeatedly. +### Matrix Access + +To access Matrix (or Gitter using the Matrix API), you can optionally configure MATRIX_USER_ID, MATRIX_ACCESS_TOKEN & MATRIX_BASE_URL (defaults to http://matrix.org). To get an access token, you can follow the [instructions here](https://t2bot.io/docs/access_tokens/). + ## Gitter rooms In gitter, the concept of a room includes public rooms, private rooms, and private 1-1 conversations, some call *direct messaging*. diff --git a/gitter-solid.js b/gitter-solid.js index 81f3c48..40f040e 100644 --- a/gitter-solid.js +++ b/gitter-solid.js @@ -23,11 +23,13 @@ import { SolidNodeClient } from 'solid-node-client' import * as readlineSync from 'readline-sync' import * as readline from 'readline' -var matrixUserId = "@timbllee:matrix.org"; -var matrixAccessToken = "syt_dGltYmxsZWU_lCSmPVdmmykTLyUJrZws_1nKivD"; +dotenv.config() + +const matrixUserId = process.env.MATRIX_USER_ID || "@timbllee:matrix.org"; +const matrixAccessToken = process.env.MATRIX_ACCESS_TOKEN || "syt_dGltYmxsZWU_lCSmPVdmmykTLyUJrZws_1nKivD"; +const matrixBaseUrl = process.env.MATRIX_BASE_URL || "http://matrix.org"; -dotenv.config() /* SILENCE FETCH_QUEUE ERRORS see https://github.com/linkeddata/rdflib.js/issues/461 @@ -42,8 +44,8 @@ console.log = (...msgs)=>{ } } */ -const command = process.argv[2] -const targetRoomName = process.argv[3] +let command = process.argv[2] +let targetRoomName = process.argv[3] const archiveBaseURI = process.argv[4] const GITTER = false @@ -238,9 +240,9 @@ async function processRooms () { } async function initialiseMatrix() { - + console.log(matrixAccessToken) matrixClient = sdk.createClient({ - baseUrl: "http://matrix.org", + baseUrl: matrixBaseUrl, accessToken: matrixAccessToken, userId: matrixUserId, }); diff --git a/terminal-app.js b/terminal-app.js index 44402ac..97310a8 100644 --- a/terminal-app.js +++ b/terminal-app.js @@ -1,13 +1,20 @@ -var myUserId = "@timbllee:matrix.org"; -var myAccessToken = "syt_dGltYmxsZWU_lCSmPVdmmykTLyUJrZws_1nKivD"; import * as sdk from "matrix-js-sdk";// https://github.com/matrix-org/matrix-js-sdk import clc from "cli-color" import * as readline from 'readline' +import * as dotenv from 'dotenv'; + +dotenv.config(); + +const matrixUserId = process.env.MATRIX_USER_ID || "@timbllee:matrix.org"; +const matrixAccessToken = process.env.MATRIX_ACCESS_TOKEN || "syt_dGltYmxsZWU_lCSmPVdmmykTLyUJrZws_1nKivD"; +const matrixBaseUrl = process.env.MATRIX_BASE_URL || "http://matrix.org"; + + var matrixClient = sdk.createClient({ - baseUrl: "http://matrix.org", - accessToken: myAccessToken, - userId: myUserId, + baseUrl: matrixBaseUrl, + accessToken: matrixAccessToken, + userId: matrixUserId, }); // Data structures @@ -118,7 +125,7 @@ rl.on("line", function (line) { if (line.indexOf("/join ") === 0) { var roomIndex = line.split(" ")[1]; viewingRoom = roomList[roomIndex]; - if (viewingRoom.getMember(myUserId).membership === "invite") { + if (viewingRoom.getMember(matrixUserId).membership === "invite") { // join the room first console.log('@@ Room to be joined: ' + JSON.stringify(viewingRoom.roomId)) matrixClient.joinRoom(viewingRoom.roomId).then( @@ -295,7 +302,7 @@ function printMemberList(room) { "%s" + fmt(" :: ") + "%s" + fmt(" (") + "%s" + fmt(")"), membershipWithPadding, member.name, - member.userId === myUserId ? "Me" : member.userId, + member.userId === matrixUserId ? "Me" : member.userId, fmt, ); }); @@ -332,7 +339,7 @@ function printLine(event) { var name = event.sender ? event.sender.name : event.getSender(); var time = new Date(event.getTs()).toISOString().replace(/T/, " ").replace(/\..+/, ""); var separator = "<<<"; - if (event.getSender() === myUserId) { + if (event.getSender() === matrixUserId) { name = "Me"; separator = ">>>"; if (event.status === sdk.EventStatus.SENDING) { From 998cdbf6488c16fb1648cafa66a2195124732af1 Mon Sep 17 00:00:00 2001 From: Denperidge Date: Thu, 9 Feb 2023 12:31:12 +0100 Subject: [PATCH 02/10] Fixed printRoomInfo map iteration In node v18, map.keys().forEach doesn't exist. It is now a for of loop --- terminal-app.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/terminal-app.js b/terminal-app.js index 97310a8..b6cf409 100644 --- a/terminal-app.js +++ b/terminal-app.js @@ -318,20 +318,20 @@ function printRoomInfo(room) { var contentHeader = padSide + "Content" + padSide; print(eTypeHeader + sendHeader + contentHeader); print(new Array(100).join("-")); - eventMap.keys().forEach(function (eventType) { + for (let eventType of eventMap.keys()) { if (eventType === "m.room.member") { - return; + continue; } // use /members instead. var eventEventMap = eventMap.get(eventType); - eventEventMap.keys().forEach(function (stateKey) { + for (let stateKey of eventEventMap.keys()) { var typeAndKey = eventType + (stateKey.length > 0 ? "(" + stateKey + ")" : ""); var typeStr = fixWidth(typeAndKey, eTypeHeader.length); var event = eventEventMap.get(stateKey); var sendStr = fixWidth(event.getSender(), sendHeader.length); var contentStr = fixWidth(JSON.stringify(event.getContent()), contentHeader.length); print(typeStr + " | " + sendStr + " | " + contentStr); - }); - }); + }; + }; } function printLine(event) { From f4c3d3e28e6ea59dbe653d4d99bbd60749d9e846 Mon Sep 17 00:00:00 2001 From: Denperidge Date: Thu, 9 Feb 2023 13:36:06 +0100 Subject: [PATCH 03/10] Fixed rl in g-s, matrix room load in go, refactor General changes: - setRoomList moved to matrix-utils - show & short moved to utils Gitter-Solid changes: - go() now loads Matrix rooms - renamed doRoom() { function show } to doRoom() { function doRoomShow } This to fix ambiguity with show, show (now doRoomShow), and showRoom --- gitter-solid.js | 95 +++++++++++++++----------------------------- src/matrix-utils.mjs | 25 ++++++++++++ src/utils.mjs | 24 +++++++++++ terminal-app.js | 29 +++----------- 4 files changed, 88 insertions(+), 85 deletions(-) create mode 100644 src/matrix-utils.mjs create mode 100644 src/utils.mjs diff --git a/gitter-solid.js b/gitter-solid.js index 40f040e..994d7b1 100644 --- a/gitter-solid.js +++ b/gitter-solid.js @@ -23,6 +23,9 @@ import { SolidNodeClient } from 'solid-node-client' import * as readlineSync from 'readline-sync' import * as readline from 'readline' +import { show } from "./src/utils.mjs" +import { setRoomList } from "./src/matrix-utils.mjs"; + dotenv.config() const matrixUserId = process.env.MATRIX_USER_ID || "@timbllee:matrix.org"; @@ -54,13 +57,14 @@ const MATRIX = true const numMessagesToShow = 20 let matrixClient = null - -var rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - // completer: completer, -}); - +// If this is run on startup, readline-sync will not work +function initReadlineAsync() { + return readline.createInterface({ + input: process.stdin, + output: process.stdout, + // completer: completer, + }); +} // const MATRIX_APP_ORIGIN = 'timbl.com' // or makybe a solidcommmunity pod @@ -92,28 +96,6 @@ const MESSAGES_AT_A_TIME = 20 // make biggers let roomList = [] -function setRoomList() { - roomList = matrixClient.getRooms(); - console.log(' setRoomList ' + show(roomList)) - roomList.sort(function (a, b) { - // < 0 = a comes first (lower index) - we want high indexes = newer - var aMsg = a.timeline[a.timeline.length - 1]; - if (!aMsg) { - return -1; - } - var bMsg = b.timeline[b.timeline.length - 1]; - if (!bMsg) { - return 1; - } - if (aMsg.getTs() > bMsg.getTs()) { - return 1; - } else if (aMsg.getTs() < bMsg.getTs()) { - return -1; - } - return 0; - }); -} - function showRoom (room) { var msg = room.timeline[room.timeline.length - 1]; @@ -135,29 +117,6 @@ function printRoomList() { } } -function short (x) { - if (x === null) return 'null' - if (!x || typeof x !== 'object') return '*'; - if (x.length) return `[${x.length}]`; - return `{${Object.keys(x).length}}`; -} -function show (x) { - if (x === null || x === undefined) return ' - ' - const typ = typeof x - switch (typ) { - case 'null': - case 'undefined': return 'x' - case 'string': return `"${x}"` - case 'boolean': - case 'number': return x.toString() - case 'object': - if (x.length) return '[' + x.slice(0, 3).map(show).join(', ') + ']' - return '{' + Object.keys(x).slice(0,3).map(k => ` ${k}: ${short(x[k])}`).join('; ') + '}' - - default: return `Type ${typ} ??` - } -} - function showMessage (event, myUserId) { var name = event.sender ? event.sender.name : event.getSender(); var time = new Date(event.getTs()).toISOString().replace(/T/, " ").replace(/\..+/, ""); @@ -204,6 +163,7 @@ function loadRoomMessages (room) { for (var i = 0; i < mostRecentMessages.length; i++) { console.log(showMessage(mostRecentMessages[i], room.myUserId)); } + let rl = initReadlineAsync(); rl.prompt(); }, function (err) { @@ -239,7 +199,7 @@ async function processRooms () { } } -async function initialiseMatrix() { +async function initialiseMatrix(callback) { console.log(matrixAccessToken) matrixClient = sdk.createClient({ baseUrl: matrixBaseUrl, @@ -271,11 +231,17 @@ async function initialiseMatrix() { console.log('getRooms ' + JSON.stringify(roomList)) // - + + /** + * It takes a second for all rooms to load on startup. + * This promise solution is all but elegant, but it works for now at least + */ matrixClient.on("Room", function () { - setRoomList(); + roomList = setRoomList(matrixClient); console.log('on Room room list: ' + roomList.length + ' rooms') }); + console.log("Loading rooms...") + await new Promise(resolve => setTimeout(resolve, 5000)) } function oldInitialiseMatrix() { @@ -311,14 +277,14 @@ function oldInitialiseMatrix() { async function init() { if(!command) { - command = await readlineSync.question('Command (e.g. create) : '); + command = readlineSync.question('Command (e.g. create) : '); } if(!targetRoomName) { - targetRoomName = await readlineSync.question('Gitter Room (e.g. solid/chat) : '); + targetRoomName = readlineSync.question('Gitter Room (e.g. solid/chat) : '); } if (GITTER) { if (!GITTER_TOKEN) { - GITTER_TOKEN = await readlineSync.question('Gitter Token : '); + GITTER_TOKEN = readlineSync.question('Gitter Token : '); } gitter = new Gitter(GITTER_TOKEN) @@ -699,7 +665,7 @@ async function doRoom (room, config) { return earliest.id } - async function show () { + async function doRoomShow () { let name = room.oneToOne ? '@' + room.user.username : room.name console.log(` ${room.githubType}: ${name}`) } @@ -865,7 +831,7 @@ async function doRoom (room, config) { } // Body of doRoom if (command === 'show') { - await show() + await doRoomShow() } else if (command === 'details') { await details() } else if (command === 'archive') { @@ -1021,11 +987,16 @@ async function go () { // }); } - console.log('rooms ' + rooms.length) + if (MATRIX) { + rooms = setRoomList(matrixClient); + } + console.log('rooms -- ' + rooms.length) + + let rl = initReadlineAsync(); rl.setPrompt("> "); rl.on("line", function (line) {}) - matrixClient.startClient(numMessagesToShow); // messages for each room. + //matrixClient.startClient(numMessagesToShow); // messages for each room. return diff --git a/src/matrix-utils.mjs b/src/matrix-utils.mjs new file mode 100644 index 0000000..16605ee --- /dev/null +++ b/src/matrix-utils.mjs @@ -0,0 +1,25 @@ +import { show } from "./utils.mjs" + + +export function setRoomList(matrixClient) { + let roomList = matrixClient.getRooms(); + console.log(' setRoomList ' + show(roomList)) + roomList.sort(function (a, b) { + // < 0 = a comes first (lower index) - we want high indexes = newer + var aMsg = a.timeline[a.timeline.length - 1]; + if (!aMsg) { + return -1; + } + var bMsg = b.timeline[b.timeline.length - 1]; + if (!bMsg) { + return 1; + } + if (aMsg.getTs() > bMsg.getTs()) { + return 1; + } else if (aMsg.getTs() < bMsg.getTs()) { + return -1; + } + return 0; + }); + return roomList; +} \ No newline at end of file diff --git a/src/utils.mjs b/src/utils.mjs new file mode 100644 index 0000000..5a9c8c6 --- /dev/null +++ b/src/utils.mjs @@ -0,0 +1,24 @@ +// General purpose utils +export function show (x) { + if (x === null || x === undefined) return ' - ' + const typ = typeof x + switch (typ) { + case 'null': + case 'undefined': return 'x' + case 'string': return `"${x}"` + case 'boolean': + case 'number': return x.toString() + case 'object': + if (x.length) return '[' + x.slice(0, 3).map(show).join(', ') + ']' + return '{' + Object.keys(x).slice(0,3).map(k => ` ${k}: ${short(x[k])}`).join('; ') + '}' + + default: return `Type ${typ} ??` + } +} + +export function short (x) { + if (x === null) return 'null' + if (!x || typeof x !== 'object') return '*'; + if (x.length) return `[${x.length}]`; + return `{${Object.keys(x).length}}`; +} diff --git a/terminal-app.js b/terminal-app.js index b6cf409..bdcfc05 100644 --- a/terminal-app.js +++ b/terminal-app.js @@ -3,6 +3,8 @@ import clc from "cli-color" import * as readline from 'readline' import * as dotenv from 'dotenv'; +import { setRoomList } from './src/matrix-utils.mjs' + dotenv.config(); const matrixUserId = process.env.MATRIX_USER_ID || "@timbllee:matrix.org"; @@ -130,7 +132,7 @@ rl.on("line", function (line) { console.log('@@ Room to be joined: ' + JSON.stringify(viewingRoom.roomId)) matrixClient.joinRoom(viewingRoom.roomId).then( function (room) { - setRoomList(); + roomList = setRoomList(matrixClient); viewingRoom = room; printMessages(); rl.prompt(); @@ -152,7 +154,7 @@ rl.on("line", function (line) { matrixClient.on("sync", function (state, prevState, data) { switch (state) { case "PREPARED": - setRoomList(); + roomList = setRoomList(matrixClient); console.log('on sync: state: ' + state) printRoomList(); printHelp(); @@ -162,7 +164,7 @@ matrixClient.on("sync", function (state, prevState, data) { }); matrixClient.on("Room", function () { - setRoomList(); + roomList = setRoomList(matrixClient); if (!viewingRoom) { console.log('on Room print room list') @@ -182,26 +184,7 @@ matrixClient.on("Room.room.", function (event, room, toStartOfTimeline) { printLine(event); }); -function setRoomList() { - roomList = matrixClient.getRooms(); - roomList.sort(function (a, b) { - // < 0 = a comes first (lower index) - we want high indexes = newer - var aMsg = a.timeline[a.timeline.length - 1]; - if (!aMsg) { - return -1; - } - var bMsg = b.timeline[b.timeline.length - 1]; - if (!bMsg) { - return 1; - } - if (aMsg.getTs() > bMsg.getTs()) { - return 1; - } else if (aMsg.getTs() < bMsg.getTs()) { - return -1; - } - return 0; - }); -} + function printRoomList() { print(CLEAR_CONSOLE); From fa787aa17af97b68620394a647916e7f196bd4bb Mon Sep 17 00:00:00 2001 From: Denperidge Date: Fri, 10 Feb 2023 10:08:10 +0100 Subject: [PATCH 04/10] Clarifying & refactoring gitter/rdf specific code Chanes: - Disabled Gitter Room selector when using Matrix API - Refactored and split up go() - Added Matrix code to go() - Renamed functions to {gitter,rdf}Function --- gitter-solid.js | 474 ++++++++++++++++++++++++++++-------------------- 1 file changed, 274 insertions(+), 200 deletions(-) diff --git a/gitter-solid.js b/gitter-solid.js index 994d7b1..011cefe 100644 --- a/gitter-solid.js +++ b/gitter-solid.js @@ -97,7 +97,7 @@ let roomList = [] -function showRoom (room) { +function matrixShowRoom (room) { var msg = room.timeline[room.timeline.length - 1]; var dateStr = "---"; if (msg) { @@ -109,15 +109,16 @@ function showRoom (room) { return ` ${roomName} %s (${room.getJoinedMembers().length} members)${star} ${dateStr}` } -function printRoomList() { +function matrixPrintRoomList() { // console.log(CLEAR_CONSOLE); console.log("Room List:"); - for (let i = 0; i < roomList.length; i++) { - console.log(showRoom(room)) + for (let room in roomList) { + console.log(room) + console.log(matrixShowRoom(room)) } } -function showMessage (event, myUserId) { +function matrixShowMessage (event, myUserId) { var name = event.sender ? event.sender.name : event.getSender(); var time = new Date(event.getTs()).toISOString().replace(/T/, " ").replace(/\..+/, ""); var separator = "<<<"; @@ -154,17 +155,19 @@ function showMessage (event, myUserId) { return `[${time}] ${name}: ${separator}; ${body}` } -function loadRoomMessages (room) { +function matrixLoadRoomMessages (room) { console.log(`loadRoomMessages: room name ${room.name}`) // console.log(show(room)) matrixClient.scrollback(room, MESSAGES_AT_A_TIME).then( function (room) { const mostRecentMessages = room.timeline; for (var i = 0; i < mostRecentMessages.length; i++) { - console.log(showMessage(mostRecentMessages[i], room.myUserId)); + console.log(matrixShowMessage(mostRecentMessages[i], room.myUserId)); } + /* let rl = initReadlineAsync(); rl.prompt(); + */ }, function (err) { console.error("loadRoomMessages ##### Error: %s", err); @@ -172,10 +175,10 @@ function loadRoomMessages (room) { ); } -async function processRooms () { +async function matrixProcessRooms () { for (let i = 0; i < roomList.length; i++) { const room = roomList[i] - console.log(`\n Room "${i}": <${room.roomId}> ${showRoom(room)}`) + console.log(`\n Room "${i}": <${room.roomId}> ${matrixShowRoom(room)}`) console.log(` timeline(${room.timeline.length}`) // console.log(JSON.stringify(room)) @@ -195,11 +198,11 @@ async function processRooms () { const typ = typeof room[prop] console.log(` ${prop}: ${show(room[prop])}`) // ${room[prop]} } - loadRoomMessages(room) + matrixLoadRoomMessages(room) } } -async function initialiseMatrix(callback) { +async function matrixInitialise() { console.log(matrixAccessToken) matrixClient = sdk.createClient({ baseUrl: matrixBaseUrl, @@ -214,7 +217,7 @@ async function initialiseMatrix(callback) { client.once("sync", async function (state, prevState, res) { if (state === "PREPARED") { console.log("prepared"); - await processRooms() + await matrixProcessRooms() } else { console.log('Fatal Error: state not prepared: ' + state); // console.log(state); @@ -244,7 +247,7 @@ async function initialiseMatrix(callback) { await new Promise(resolve => setTimeout(resolve, 5000)) } -function oldInitialiseMatrix() { +function matrixOldInitialise() { // Connect to your Matrix endpoint: const baseUrl = 'https://matrix.org/_matrix'; const matrix = new Matrix(baseUrl); @@ -279,26 +282,32 @@ async function init() { if(!command) { command = readlineSync.question('Command (e.g. create) : '); } - if(!targetRoomName) { - targetRoomName = readlineSync.question('Gitter Room (e.g. solid/chat) : '); + + // The script currently only supports Matrix using ALL + if (MATRIX) { + await matrixInitialise() + targetRoomName = "all"; } + if (GITTER) { + // Target room name will already be defined if Matrix is enabled + if(!targetRoomName) { + targetRoomName = readlineSync.question('Gitter Room (e.g. solid/chat) : '); + } if (!GITTER_TOKEN) { GITTER_TOKEN = readlineSync.question('Gitter Token : '); } gitter = new Gitter(GITTER_TOKEN) } - if (MATRIX) { - await initialiseMatrix() - } + } -async function confirm (q) { +function confirm (q) { while (1) { - var a = (await readlineSync.question(q+' (y/n)? ')).trim().toLowerCase(); + var a = (readlineSync.question(q+' (y/n)? ')).trim().toLowerCase(); if (a === 'yes' || a === 'y') return true if (a === 'no' || a === 'n') return false console.log(' Please reply y or n') @@ -331,7 +340,7 @@ function delayMs (ms) { return new Promise(resolve => setTimeout(resolve, ms)) } -function chatDocumentFromDate (chatChannel, date) { +function rdfChatDocumentFromDate (chatChannel, date) { let isoDate = date.toISOString() // Like "2018-05-07T17:42:46.576Z" var path = isoDate.split('T')[0].replace(/-/g, '/') // Like "2018/05/07" path = chatChannel.dir().uri + path + '/chat.ttl' @@ -353,7 +362,7 @@ async function update (ddd, sts) { } */ // individualChatFolder', 'privateChatFolder', 'publicChatFolder -function archiveBaseURIFromGitterRoom (room, config) { +function gitterArchiveBaseURIFromRoom (room, config) { const folder = room.oneToOne ? config.individualChatFolder : room.public ? config.publicChatFolder : config.privateChatFolder return (folder.uri) ? folder.uri : folder // needed if config newly created @@ -363,13 +372,13 @@ function archiveBaseURIFromGitterRoom (room, config) { * * @param room {Room} - like 'solid/chat' */ -function chatChannelFromGitterRoom (room, config) { +function gitterChatChannelFromRoom (room, config) { var path let segment = room.name.split('/').map(encodeURIComponent).join('/') // Preseeve the slash begween org and room if (room.githubType === 'ORG') { segment += '/_Organization' // make all multi rooms two level names } - var archiveBaseURI = archiveBaseURIFromGitterRoom(room, config) + var archiveBaseURI = gitterArchiveBaseURIFromRoom(room, config) if (!archiveBaseURI.endsWith('/')) throw new Error('base should end with slash') if (room.oneToOne) { var username = room.user.username @@ -386,12 +395,12 @@ function chatChannelFromGitterRoom (room, config) { */ -async function putResource (doc) { +async function rdfPutResource (doc) { delete fetcher.requested[doc.uri] // invalidate read cache @@ should be done by fetcher in future return fetcher.putBack(doc, clone(normalOptions)) } -async function loadIfExists (doc) { +async function rdfLoadIfExists (doc) { try { // delete fetcher.requested[doc.uri] await fetcher.load(doc, clone(normalOptions)) @@ -417,7 +426,7 @@ function suitable (x) { // return kb.anyValue(chatDocument, POSIX('size')) !== 0 // empty file? } -async function firstMessage (chatChannel, backwards) { // backwards -> last message +async function rdfFirstMessage (chatChannel, backwards) { // backwards -> last message var folderStore = $rdf.graph() var folderFetcher = new $rdf.Fetcher(folderStore,fetcherOpts) async function earliestSubfolder (parent) { @@ -463,12 +472,12 @@ async function firstMessage (chatChannel, backwards) { // backwards -> last mess return sortMe[0][1] } -async function saveEverythingBack () { +async function rdfSaveEverythingBack () { // console.log('Saving all modified files:') for (let uri in toBePut) { if (toBePut.hasOwnProperty(uri)) { console.log('Putting ' + uri) - await putResource($rdf.sym(uri)) + await rdfPutResource($rdf.sym(uri)) delete fetcher.requested[uri] // invalidate read cache @@ should be done by fether in future } } @@ -541,14 +550,14 @@ async function authorFromGitter (fromUser, archiveBaseURI) { var newMessages = 0 var oldMessages = 0 -async function storeMessage (chatChannel, gitterMessage, archiveBaseURI) { +async function gitterStoreMessage (chatChannel, gitterMessage, archiveBaseURI) { var sent = new Date(gitterMessage.sent) // Like "2014-03-25T11:51:32.289Z" // console.log(' Message sent on date ' + sent) - var chatDocument = chatDocumentFromDate(chatChannel, sent) + var chatDocument = rdfChatDocumentFromDate(chatChannel, sent) var message = $rdf.sym(chatDocument.uri + '#' + gitterMessage.id) // like "53316dc47bfc1a000000000f" // console.log(' Solid Message ' + message) - await loadIfExists(chatDocument) + await rdfLoadIfExists(chatDocument) if (store.holds(chatChannel, ns.wf('message'), message, chatDocument)) { // console.log(` already got ${gitterMessage.sent} message ${message}`) oldMessages += 1 @@ -581,11 +590,11 @@ async function storeMessage (chatChannel, gitterMessage, archiveBaseURI) { "text":"The quick red fox", "html":"The quick red fox","sent":"2019-03-24T19:18:05.278Z","editedAt":"2019-03-24T19:18:12.757Z","fromUser":{"id":"54d26c98db8155e6700f7312","username":"timbl","displayName":"Tim Berners-Lee","url":"/timbl","avatarUrl":"https://avatars-02.gitter.im/gh/uv/4/timbl","avatarUrlSmall":"https://avatars2.githubusercontent.com/u/1254848?v=4&s=60","avatarUrlMedium":"https://avatars2.githubusercontent.com/u/1254848?v=4&s=128","v":30,"gv":"4"},"unread":true,"readBy":3,"urls":[],"mentions":[],"issues":[],"meta":[],"v":2}} */ -async function updateMessage (chatChannel, payload) { +async function rdfUpdateMessage (chatChannel, payload) { var sent = new Date(payload.sent) - var chatDocument = chatDocumentFromDate(chatChannel, sent) + var chatDocument = rdfChatDocumentFromDate(chatChannel, sent) var message = $rdf.sym(chatDocument.uri + '#' + payload.id) - await loadIfExists(chatDocument) + await rdfLoadIfExists(chatDocument) var found = store.any(message, ns.sioc('content')) if (!found) { console.error('DID NOT FIND MESSAGE TO UPDATE ' + payload.id) @@ -626,10 +635,10 @@ async function updateMessage (chatChannel, payload) { } } -async function deleteMessage (chatChannel, payload) { - var chatDocument = chatDocumentFromDate(chatChannel, new Date()) // @@ guess now +async function rdfDeleteMessage (chatChannel, payload) { + var chatDocument = rdfChatDocumentFromDate(chatChannel, new Date()) // @@ guess now var message = $rdf.sym(chatDocument.uri + '#' + payload.id) - await loadIfExists(chatDocument) + await rdfLoadIfExists(chatDocument) var found = store.any(message, ns.sioc('content')) if (!found) { console.error('DID NOT FIND MESSAGE TO UPDATE ' + payload.id) @@ -652,12 +661,22 @@ async function doRoom (room, config) { console.log(`\nDoing room ${room.id}: ${room.name}`) // console.log('@@ bare room: ' + JSON.stringify(room)) var gitterRoom - const solidChannel = chatChannelFromGitterRoom(room, config) - const archiveBaseURI = archiveBaseURIFromGitterRoom(room, config) + let solidChannel, archiveBaseURI; + + if (GITTER) { + solidChannel = gitterChatChannelFromRoom(room, config); + archiveBaseURI = gitterArchiveBaseURIFromRoom(room, config); + } + if (MATRIX) { + // TODO + } + + + console.log(' solid channel ' + solidChannel) - function findEarliestId (messages) { + function gitterFindEarliestId (messages) { var sortMe = messages.map(gitterMessage => [gitterMessage.sent, gitterMessage]) if (sortMe.length === 0) return null sortMe.sort() @@ -665,38 +684,38 @@ async function doRoom (room, config) { return earliest.id } - async function doRoomShow () { + async function gitterDoRoomShow () { let name = room.oneToOne ? '@' + room.user.username : room.name console.log(` ${room.githubType}: ${name}`) } - async function details () { + async function gitterRoomDetails () { let name = room.oneToOne ? '@' + room.user.username : room.name console.log(`${room.githubType}: ${name}`) console.log(JSON.stringify(room)) } - async function catchup () { + async function gitterCatchup () { newMessages = 0 oldMessages = 0 gitterRoom = gitterRoom || await gitter.rooms.find(room.id) var messages = await gitterRoom.chatMessages() // @@@@ ? if (messages.length !== 50) console.log(' Messages read: ' + messages.length) for (let gitterMessage of messages) { - await storeMessage(solidChannel, gitterMessage, archiveBaseURI) + await gitterStoreMessage(solidChannel, gitterMessage, archiveBaseURI) } - await saveEverythingBack() + await rdfSaveEverythingBack() if (oldMessages) { console.log('End catchup. Found message we already had.') return true } - var newId = findEarliestId(messages) + var newId = gitterFindEarliestId(messages) if (!newId) { console.log('Catchup found no gitter messages.') return true } for (let i = 0; i < 30; i++) { - newId = await extendBeforeId(newId) + newId = await gitterExtendBeforeId(newId) if (!newId) { console.log(`End catchup. No more gitter messages after ${newMessages} new messages.`) return true @@ -713,15 +732,15 @@ async function doRoom (room, config) { } async function initialize () { - const solidChannel = chatChannelFromGitterRoom(room, config) + const solidChannel = gitterChatChannelFromRoom(room, config) console.log(' solid channel ' + solidChannel) // Make the main chat channel file var newChatDoc = solidChannel.doc() - let already = await loadIfExists(newChatDoc) + let already = await rdfLoadIfExists(newChatDoc) if (!already) { store.add(solidChannel, ns.rdf('type'), ns.meeting('LongChat'), newChatDoc) store.add(solidChannel, ns.dc('title'), room.name + ' gitter chat archive', newChatDoc) - await putResource(newChatDoc) + await rdfPutResource(newChatDoc) console.log(' New chat channel created. ' + solidChannel) return false } else { @@ -730,14 +749,14 @@ async function doRoom (room, config) { } } - async function extendArchiveBack () { - let m0 = await firstMessage(solidChannel) + async function rdfExtendArchiveBack () { + let m0 = await rdfFirstMessage(solidChannel) let d0 = kb.anyValue(m0, ns.dct('created')) console.log('Before extension back, earliest message ' + d0) var newId = m0.uri.split('#')[1] // var newId = await extendBeforeId(id) for (let i = 0; i < 30; i++) { - newId = await extendBeforeId(newId) + newId = await gitterExtendBeforeId(newId) if (!newId) return null console.log(' ... pause ...') await delayMs(3000) // ms give the API a rest @@ -745,7 +764,7 @@ async function doRoom (room, config) { return newId } - async function stream (store) { + async function gitterStream (store) { gitterRoom = gitterRoom || await gitter.rooms.find(room.id) var events = gitterRoom.streaming().chatMessages() @@ -760,7 +779,7 @@ async function doRoom (room, config) { console.log('Text: ', gitterEvent.model.text) console.log('gitterEvent object: ', JSON.stringify(gitterEvent)) if (gitterEvent.operation === 'create') { - var solidMessage = await storeMessage(solidChannel, gitterEvent.model, archiveBaseURI) + var solidMessage = await gitterStoreMessage(solidChannel, gitterEvent.model, archiveBaseURI) console.log('creating solid message ' + solidMessage) var sts = store.connectedStatements(solidMessage) try { @@ -773,10 +792,10 @@ async function doRoom (room, config) { } } else if (gitterEvent.operation === 'remove') { console.log('Deleting existing message:') - await deleteMessage(solidChannel, gitterEvent.model) + await rdfDeleteMessage(solidChannel, gitterEvent.model) } else if (gitterEvent.operation === 'update') { console.log('Updating existing message:') - await updateMessage(solidChannel, gitterEvent.model) + await rdfUpdateMessage(solidChannel, gitterEvent.model) } else if (gitterEvent.operation === 'patch') { console.log('Ignoring patch') } else { @@ -788,7 +807,7 @@ async function doRoom (room, config) { /* Returns earliest id it finds so can be chained */ - async function extendBeforeId (id) { + async function gitterExtendBeforeId (id) { console.log(` Looking for messages before ${id}`) gitterRoom = gitterRoom || await gitter.rooms.find(room.id) let messages = await gitterRoom.chatMessages({limit: 100, beforeId: id}) @@ -798,10 +817,10 @@ async function doRoom (room, config) { return null } for (let gitterMessage of messages) { - await storeMessage(solidChannel, gitterMessage, archiveBaseURI) + await gitterStoreMessage(solidChannel, gitterMessage, archiveBaseURI) } - await saveEverythingBack() - let m1 = await firstMessage(solidChannel) + await rdfSaveEverythingBack() + let m1 = await rdfFirstMessage(solidChannel) let d1 = kb.anyValue(m1, ns.dct('created')) console.log('After extension back, earliest message now ' + d1) @@ -811,17 +830,17 @@ async function doRoom (room, config) { return earliest.id } - async function create() { + async function gitterCreate() { console.log('First make the solid chat object if necessary:') await initialize() console.log('Now first catchup recent messages:') - var catchupDone = await catchup() + var catchupDone = await gitterCatchup() if (catchupDone) { console.log('Initial catchup gave no messages, so no archive necessary.✅') return null } console.log('Now extend the archive back hopefully all the way -- but check:') - let pickUpFrom = await extendArchiveBack() + let pickUpFrom = await rdfExtendArchiveBack() if (pickUpFrom) { console.log('Did NOT go all the way. More archive sessions will be needed. ⚠️') } else { @@ -831,27 +850,27 @@ async function doRoom (room, config) { } // Body of doRoom if (command === 'show') { - await doRoomShow() + await gitterDoRoomShow() } else if (command === 'details') { - await details() + await gitterRoomDetails() } else if (command === 'archive') { - await extendArchiveBack() + await rdfExtendArchiveBack() } else if (command === 'catchup') { - await catchup() + await gitterCatchup() } else if (command === 'stream') { console.log('catching up to make sure we don\'t miss any when we stream') - var ok = await catchup() + var ok = await gitterCatchup() if (!ok) { console.error('catching up FAILED so NOT starting stream as we would get a gap!') throw new Error('Not caught up. Cant stream.') } console.log('Catchup done. Now set up stream.') - await stream(store) + await gitterStream(store) } else if (command === 'init') { var already = await initialize() // console.log('Solid channel already there:' + already) } else if (command === 'create') { - await create() + await gitterCreate() } } @@ -860,9 +879,9 @@ async function loadConfig () { let localPod = process.argv[4]; let remotePod = false; if(!localPod){ - remotePod = await confirm('Store on remote pod'); + remotePod = confirm('Store on remote pod'); if(!remotePod) { - localPod = await readlineSync.question('URI to local pod (e.g. file:///home/me/myPod/) : '); + localPod = readlineSync.question('URI to local pod (e.g. file:///home/me/myPod/) : '); } } if( localPod && !remotePod && !localPod.startsWith('http')){ @@ -895,16 +914,18 @@ async function loadConfig () { password: process.env.SOLID_PASSWORD } if(!creds.idp){ - const idp = await readlineSync.question('Identity Provider (e.g. https://solidcommunity.net) : ') + creds.idp = readlineSync.question('Identity Provider (e.g. https://solidcommunity.net) : ') } if(!creds.username){ - const username = await readlineSync.question('Pod username : ') + creds.username = readlineSync.question('Pod username : ') } if(!creds.password){ - const password = await readlineSync.question('Pod password : ') + creds.password = readlineSync.question('Pod password : ') } + console.log(creds) console.log(`Logging into Solid Pod <${creds.idp}>`) var session = await auth.login(creds); + console.log(session) webId = session.webId } const me = $rdf.sym(webId) @@ -916,60 +937,85 @@ async function loadConfig () { await fetcher.load(prefs) console.log('Loaded prefs ✅') - var config = kb.the(me, ns.solid('gitterConfiguationFile'), null, prefs) - if (!config) { - console.log('You don\'t have a gitter configuration. ') - config = $rdf.sym(prefs.dir().uri + 'gitterConfiguration.ttl') - if (await confirm('Make a gitter config file now in your pod at ' + config)) { - console.log(' putting ' + config) - await kb.fetcher.webOperation('PUT', config.uri, {data: '', contentType: 'text/turtle'}) - console.log(' getting ' + config) - await kb.fetcher.load(config) - await kb.updater.update([], [$rdf.st(me, ns.solid('gitterConfiguationFile'), config, prefs)]) - await kb.updater.update([], [$rdf.st(config, ns.dct('title'), 'My gitter config file', config)]) - console.log('Made new gitter config: ' + config) + if (GITTER) { + + // TODO make sure config is only needed by gitter + var config = kb.the(me, ns.solid('gitterConfiguationFile'), null, prefs) + if (!config) { + console.log('You don\'t have a gitter configuration. ') + config = $rdf.sym(prefs.dir().uri + 'gitterConfiguration.ttl') + if (await confirm('Make a gitter config file now in your pod at ' + config)) { + console.log(' putting ' + config) + await kb.fetcher.webOperation('PUT', config.uri, {data: '', contentType: 'text/turtle'}) + console.log(' getting ' + config) + await kb.fetcher.load(config) + await kb.updater.update([], [$rdf.st(me, ns.solid('gitterConfiguationFile'), config, prefs)]) + await kb.updater.update([], [$rdf.st(config, ns.dct('title'), 'My gitter config file', config)]) + console.log('Made new gitter config: ' + config) + } else { + console.log('Ok, exiting, no gitter config') + process.exit(4) + } } else { - console.log('Ok, exiting, no gitter config') - process.exit(4) + await fetcher.load(config) } - } else { - await fetcher.load(config) - } - console.log('Have gitter config ✅') + console.log('Have gitter config ✅') - for (let opt of opts) { - var x = kb.any(me, ns.solid(opt)) - console.log(` Config option ${opt}: "${x}"`) - if (x && x.uri) { - gitterConfig[opt] = x.uri - } else { - console.log('\nThis must a a full https: or file: URI ending in a slash, which folder on your pod or local file system you want gitter chat stored.') - x = await readlineSync.question('URI for ' + opt + '? ') - console.log('@@@@@ aaaaa :' + x) - if (x.length > 0 && x.endsWith('/')) { - console.log(`@@ saving config ${opt} = ${x}`) - await kb.updater.update([], [$rdf.st(me, ns.solid(opt), $rdf.sym(x), config)]) - console.log(`saved config ${opt} = ${x}`) + for (let opt of opts) { + var x = kb.any(me, ns.solid(opt)) + console.log(` Config option ${opt}: "${x}"`) + if (x && x.uri) { + gitterConfig[opt] = x.uri } else { - console.log('abort. exit.') - process.exit(6) + console.log('\nThis must a a full https: or file: URI ending in a slash, which folder on your pod or local file system you want gitter chat stored.') + x = await readlineSync.question('URI for ' + opt + '? ') + console.log('@@@@@ aaaaa :' + x) + if (x.length > 0 && x.endsWith('/')) { + console.log(`@@ saving config ${opt} = ${x}`) + await kb.updater.update([], [$rdf.st(me, ns.solid(opt), $rdf.sym(x), config)]) + console.log(`saved config ${opt} = ${x}`) + } else { + console.log('abort. exit.') + process.exit(6) + } } + gitterConfig[opt] = x } - gitterConfig[opt] = x + console.log('We have all config data ✅') + return gitterConfig } - console.log('We have all config data ✅') - return gitterConfig + + } ////////////////////////////////////////////////////////////////// +/** + * This function is the main function that gets called. + * The comments below will sometimes start with (GITTER) or + * (MATRIX). This refers to which constant has to be enabled, + * and thus which api gets used + * + * @returns + * + * + */ async function go () { + // Start the Matrix or Gitter client await init(); + + let rooms = [] + var usernameIndex = {} + var targetRoom; + var roomsToDo = [] + + // (GITTER) Split up rooms var oneToOnes = [] var privateRooms = [] var publicRooms = [] - var usernameIndex = {} - let rooms = [] + + // (GITTER) Collect and split up all rooms if (GITTER) { + // 1: Collect all rooms console.log(`Logging into gitter room ${targetRoomName} ...`) var user try { @@ -980,120 +1026,102 @@ async function go () { } console.log('You are logged into gitter as:', user.username) rooms = await user.rooms() + + // 2: Split up all rooms + var roomIndex = {} + for (let r = 0; r < rooms.length; r++) { + var room = rooms[r] + // const oneToOne = room.oneToOne + // const noun = oneToOne ? 'OneToOne' : 'Room' + roomIndex[room.name] = room + if (room.oneToOne) { + oneToOnes.push(room) + // console.log('@@@@ remembering ' + '@' + room.user.username) + usernameIndex[ '@' + room.user.username] = room + } else { + if (room.public) { + publicRooms.push(room) + } else { + privateRooms.push(room) + } + if (room.name === targetRoomName) { + console.log('Target room found: ' + room.name) + } + } + } + + // 3: Select which rooms to display + if (targetRoomName) { + if (targetRoomName === 'direct') { + roomsToDo = oneToOnes + } else if (targetRoomName === 'private') { + roomsToDo = privateRooms + } else if (targetRoomName === 'public') { + roomsToDo = publicRooms + } else if (targetRoomName === 'all') { + roomsToDo = oneToOnes.concat(privateRooms).concat(publicRooms) + } else { + console.log(`targetRoomName 2 "${targetRoomName}"`) + console.log('@@@@@@ ' + usernameIndex[targetRoomName]) + targetRoom = targetRoomName.startsWith('@') ? usernameIndex[targetRoomName] : roomIndex[targetRoomName] + if (targetRoom) { + roomsToDo = [ targetRoom ] + console.log('Single room selected: ' + targetRoom.name) + } + } + + console.log('targetRoomName 1 ' + targetRoomName) + } + } else { // function (err, data) { // console.log("Public Rooms: %s", JSON.stringify(data)); // }); } - + // (MATRIX) Collect all rooms if (MATRIX) { rooms = setRoomList(matrixClient); + roomsToDo = rooms; } console.log('rooms -- ' + rooms.length) + if (rooms.length < 1) { + console.error("No rooms were found! Exiting...") + process.exit(1); + } + // Start interface + /* let rl = initReadlineAsync(); rl.setPrompt("> "); rl.on("line", function (line) {}) + */ //matrixClient.startClient(numMessagesToShow); // messages for each room. - return - - console.log('@ testing exit ') - process.exit() - - var roomIndex = {} - for (let r = 0; r < rooms.length; r++) { - var room = rooms[r] - // const oneToOne = room.oneToOne - // const noun = oneToOne ? 'OneToOne' : 'Room' - roomIndex[room.name] = room - if (room.oneToOne) { - oneToOnes.push(room) - // console.log('@@@@ remembering ' + '@' + room.user.username) - usernameIndex[ '@' + room.user.username] = room - } else { - if (room.public) { - publicRooms.push(room) - } else { - privateRooms.push(room) - } - if (room.name === targetRoomName) { - console.log('Target room found: ' + room.name) - } - } - } + if (command === 'list') { - console.log('List of direct one-one chats:') - for (let r of oneToOnes) { - var username = r.user.username - if (!username) throw new Error('one-one must have user username!') - username = '@' + username - if (!targetRoomName) { - console.log(` ${r.githubType}: ${username}: ${r.name}`) - } - if (r.name === targetRoomName || username === targetRoomName) { - console.log(' Found ' + username) - console.log(JSON.stringify(r)) - if (room.public) throw new Error('@@@ One-One should not be public!!') - } + if (GITTER) { + commandListGitter(oneToOnes, privateRooms, publicRooms); } - console.log('List of multi person PRIVATE rooms:') - for (let r of privateRooms) { - if (!targetRoomName) { - console.log(` ${r.githubType}: ${r.name} - PRIVATE`) - } - if (r.name === targetRoomName) { - console.log(' found ' + r.name) - console.log(JSON.stringify(r)) - } + if (MATRIX) { + commandListMatrix(); } - console.log('List of multi person Public rooms:') - for (let r of publicRooms) { - if (!targetRoomName) { - console.log(` ${r.githubType}: ${r.name} - Public`) - } - if (r.name === targetRoomName) { - console.log(' found ' + r.name) - console.log(JSON.stringify(r)) - } - } - process.exit(0) // No more processing for list } - var targetRoom - var roomsToDo = [] - console.log('targetRoomName 1 ' + targetRoomName) - - if (targetRoomName) { - if (targetRoomName === 'direct') { - roomsToDo = oneToOnes - } else if (targetRoomName === 'private') { - roomsToDo = privateRooms - } else if (targetRoomName === 'public') { - roomsToDo = publicRooms - } else if (targetRoomName === 'all') { - roomsToDo = oneToOnes.concat(privateRooms).concat(publicRooms) - } else { - console.log(`targetRoomName 2 "${targetRoomName}"`) - console.log('@@@@@@ ' + usernameIndex[targetRoomName]) - targetRoom = targetRoomName.startsWith('@') ? usernameIndex[targetRoomName] : roomIndex[targetRoomName] - if (targetRoom) { - roomsToDo = [ targetRoom ] - console.log('Single room selected: ' + targetRoom.name) - } - } - } + console.log('Rooms to do: ' + roomsToDo.length) if (roomsToDo.length === 0) { console.log(`Room "${targetRoomName}" not found!`) console.log(JSON.stringify(usernameIndex)) process.exit(10) } - console.log('Rooms to do: ' + roomsToDo.length) - const config = await loadConfig() + + + // Check where to save + const config = await loadConfig(); var count = 0 + for (let targetRoom of roomsToDo) { try { await doRoom(targetRoom, config) @@ -1119,4 +1147,50 @@ var peopleDone = {} const opts = ['individualChatFolder', 'privateChatFolder', 'publicChatFolder'] go() +/** + * Functions used in @see go + * Naming structure: command{Name}{Platform} + */ +function commandListGitter(oneToOnes, privateRooms, publicRooms) { + console.log('List of direct one-one chats:') + for (let r of oneToOnes) { + var username = r.user.username + if (!username) throw new Error('one-one must have user username!') + username = '@' + username + if (!targetRoomName) { + console.log(` ${r.githubType}: ${username}: ${r.name}`) + } + if (r.name === targetRoomName || username === targetRoomName) { + console.log(' Found ' + username) + console.log(JSON.stringify(r)) + if (room.public) throw new Error('@@@ One-One should not be public!!') + } + } + console.log('List of multi person PRIVATE rooms:') + for (let r of privateRooms) { + if (!targetRoomName) { + console.log(` ${r.githubType}: ${r.name} - PRIVATE`) + } + if (r.name === targetRoomName) { + console.log(' found ' + r.name) + console.log(JSON.stringify(r)) + } + } + console.log('List of multi person Public rooms:') + for (let r of publicRooms) { + if (!targetRoomName) { + console.log(` ${r.githubType}: ${r.name} - Public`) + } + if (r.name === targetRoomName) { + console.log(' found ' + r.name) + console.log(JSON.stringify(r)) + } + } + process.exit(0) // No more processing for list +} + +function commandListMatrix() { + matrixPrintRoomList(); +} + // ends From 4249426da2ad96682f0e151c0ea49dfa0c268b2c Mon Sep 17 00:00:00 2001 From: Denperidge Date: Fri, 10 Feb 2023 10:48:47 +0100 Subject: [PATCH 05/10] Implemented Matrix command: List --- gitter-solid.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/gitter-solid.js b/gitter-solid.js index 011cefe..490a830 100644 --- a/gitter-solid.js +++ b/gitter-solid.js @@ -97,7 +97,7 @@ let roomList = [] -function matrixShowRoom (room) { +function matrixShowRoomString (room) { var msg = room.timeline[room.timeline.length - 1]; var dateStr = "---"; if (msg) { @@ -112,10 +112,9 @@ function matrixShowRoom (room) { function matrixPrintRoomList() { // console.log(CLEAR_CONSOLE); console.log("Room List:"); - for (let room in roomList) { - console.log(room) - console.log(matrixShowRoom(room)) - } + roomList.forEach((room) => { + console.log(matrixShowRoomString(room)); + }); } function matrixShowMessage (event, myUserId) { @@ -178,7 +177,7 @@ function matrixLoadRoomMessages (room) { async function matrixProcessRooms () { for (let i = 0; i < roomList.length; i++) { const room = roomList[i] - console.log(`\n Room "${i}": <${room.roomId}> ${matrixShowRoom(room)}`) + console.log(`\n Room "${i}": <${room.roomId}> ${matrixShowRoomString(room)}`) console.log(` timeline(${room.timeline.length}`) // console.log(JSON.stringify(room)) From 717c5f85e357d8af0b34e37de457a40a83183d30 Mon Sep 17 00:00:00 2001 From: Denperidge Date: Fri, 10 Feb 2023 11:41:56 +0100 Subject: [PATCH 06/10] Re-implemented and refactored loadConfig --- gitter-solid.js | 105 +++++++++++++++++++++++++----------------------- 1 file changed, 55 insertions(+), 50 deletions(-) diff --git a/gitter-solid.js b/gitter-solid.js index 490a830..5d23e77 100644 --- a/gitter-solid.js +++ b/gitter-solid.js @@ -330,9 +330,9 @@ const auth = new SolidNodeClient({parser:$rdf}) const fetcherOpts = {fetch: auth.fetch.bind(auth), timeout: 900000}; const store = $rdf.graph() -const kb = store // shorthand -- knowledge base -const fetcher = $rdf.fetcher(kb, fetcherOpts) -const updater = new $rdf.UpdateManager(kb) +const rdfStore = store // shorthand -- knowledge base +const fetcher = $rdf.fetcher(rdfStore, fetcherOpts) +const updater = new $rdf.UpdateManager(rdfStore) function delayMs (ms) { console.log('pause ... ') @@ -367,6 +367,10 @@ function gitterArchiveBaseURIFromRoom (room, config) { return (folder.uri) ? folder.uri : folder // needed if config newly created } +function matrixArchiveBaseURIFromRoom(room) { + +} + /** Decide URI of solid chat vchanel from properties of gitter room * * @param room {Room} - like 'solid/chat' @@ -605,7 +609,7 @@ async function rdfUpdateMessage (chatChannel, payload) { var del = [] var ins = [] if (payload.text) { - let oldText = kb.the(message, ns.sioc('content')) + let oldText = rdfStore.the(message, ns.sioc('content')) if (oldText && payload.text === oldText) { console.log(` text unchanged as <${oldText}>`) } else { @@ -614,7 +618,7 @@ async function rdfUpdateMessage (chatChannel, payload) { } } if (payload.html) { - let oldText = kb.the(message, ns.sioc('richContent')) + let oldText = rdfStore.the(message, ns.sioc('richContent')) if (oldText && payload.text === oldText.value) { console.log(` text unchanged as <${oldText}>`) } else { @@ -665,6 +669,7 @@ async function doRoom (room, config) { if (GITTER) { solidChannel = gitterChatChannelFromRoom(room, config); archiveBaseURI = gitterArchiveBaseURIFromRoom(room, config); + } if (MATRIX) { // TODO @@ -750,7 +755,7 @@ async function doRoom (room, config) { async function rdfExtendArchiveBack () { let m0 = await rdfFirstMessage(solidChannel) - let d0 = kb.anyValue(m0, ns.dct('created')) + let d0 = rdfStore.anyValue(m0, ns.dct('created')) console.log('Before extension back, earliest message ' + d0) var newId = m0.uri.split('#')[1] // var newId = await extendBeforeId(id) @@ -820,7 +825,7 @@ async function doRoom (room, config) { } await rdfSaveEverythingBack() let m1 = await rdfFirstMessage(solidChannel) - let d1 = kb.anyValue(m1, ns.dct('created')) + let d1 = rdfStore.anyValue(m1, ns.dct('created')) console.log('After extension back, earliest message now ' + d1) var sortMe = messages.map(gitterMessage => [gitterMessage.sent, gitterMessage]) @@ -929,60 +934,61 @@ async function loadConfig () { } const me = $rdf.sym(webId) console.log('Logged in to Solid as ' + me) - var gitterConfig = {} + var folderConfig = {} await fetcher.load(me.doc()) - const prefs = kb.the(me, ns.space('preferencesFile'), null, me.doc()) + const prefs = rdfStore.the(me, ns.space('preferencesFile'), null, me.doc()) console.log('Loading prefs ' + prefs) await fetcher.load(prefs) console.log('Loaded prefs ✅') - if (GITTER) { - - // TODO make sure config is only needed by gitter - var config = kb.the(me, ns.solid('gitterConfiguationFile'), null, prefs) - if (!config) { - console.log('You don\'t have a gitter configuration. ') - config = $rdf.sym(prefs.dir().uri + 'gitterConfiguration.ttl') - if (await confirm('Make a gitter config file now in your pod at ' + config)) { - console.log(' putting ' + config) - await kb.fetcher.webOperation('PUT', config.uri, {data: '', contentType: 'text/turtle'}) - console.log(' getting ' + config) - await kb.fetcher.load(config) - await kb.updater.update([], [$rdf.st(me, ns.solid('gitterConfiguationFile'), config, prefs)]) - await kb.updater.update([], [$rdf.st(config, ns.dct('title'), 'My gitter config file', config)]) - console.log('Made new gitter config: ' + config) - } else { - console.log('Ok, exiting, no gitter config') - process.exit(4) - } + // Get the config file if it exists + const SOLIDCONFIGFILE = 'solidGitterConfigurationFile'; + let solidConfig = rdfStore.the(me, ns.solid(SOLIDCONFIGFILE), null, prefs) + if (!solidConfig) { + console.log('You don\'t have a solid-gitter configuration. ') + solidConfig = $rdf.sym(prefs.dir().uri + 'solidGitterConfiguationFile.ttl') + if (await confirm('Make a solid-gitter config file now in your pod at ' + solidConfig)) { + console.log(' putting ' + solidConfig) + await rdfStore.fetcher.webOperation('PUT', solidConfig.uri, {data: '', contentType: 'text/turtle'}) + console.log(' getting ' + solidConfig) + await rdfStore.fetcher.load(solidConfig) + await rdfStore.updater.update([], [$rdf.st(me, ns.solid(SOLIDCONFIGFILE), solidConfig, prefs)]) + await rdfStore.updater.update([], [$rdf.st(solidConfig, ns.dct('title'), 'My gitter config file', solidConfig)]) + console.log('Made new solid-gitter config: ' + solidConfig) } else { - await fetcher.load(config) + console.log('Ok, exiting, no gitter config') + process.exit(4) } - console.log('Have gitter config ✅') + } else { + await fetcher.load(solidConfig) + } + + console.log('Have solid-gitter config ✅') - for (let opt of opts) { - var x = kb.any(me, ns.solid(opt)) - console.log(` Config option ${opt}: "${x}"`) - if (x && x.uri) { - gitterConfig[opt] = x.uri + const FOLDERS = ['individualChatFolder', 'privateChatFolder', 'publicChatFolder'] + for (let opt of FOLDERS) { + var x = rdfStore.any(me, ns.solid(opt)) + console.log(` Config option ${opt}: "${x}"`) + if (x && x.uri) { + folderConfig[opt] = x.uri + } else { + console.log('\nThis must a a full https: or file: URI ending in a slash, which folder on your pod or local file system you want gitter chat stored.') + x = await readlineSync.question('URI for ' + opt + '? ') + console.log('@@@@@ aaaaa :' + x) + if (x.length > 0 && x.endsWith('/')) { + console.log(`@@ saving config ${opt} = ${x}`) + await rdfStore.updater.update([], [$rdf.st(me, ns.solid(opt), $rdf.sym(x), solidConfig)]) + console.log(`saved config ${opt} = ${x}`) } else { - console.log('\nThis must a a full https: or file: URI ending in a slash, which folder on your pod or local file system you want gitter chat stored.') - x = await readlineSync.question('URI for ' + opt + '? ') - console.log('@@@@@ aaaaa :' + x) - if (x.length > 0 && x.endsWith('/')) { - console.log(`@@ saving config ${opt} = ${x}`) - await kb.updater.update([], [$rdf.st(me, ns.solid(opt), $rdf.sym(x), config)]) - console.log(`saved config ${opt} = ${x}`) - } else { - console.log('abort. exit.') - process.exit(6) - } + console.log('abort. exit.') + process.exit(6) } - gitterConfig[opt] = x } - console.log('We have all config data ✅') - return gitterConfig + folderConfig[opt] = x } + console.log('We have all config data ✅') + return folderConfig; + } @@ -1143,7 +1149,6 @@ async function go () { var toBePut = [] var peopleDone = {} -const opts = ['individualChatFolder', 'privateChatFolder', 'publicChatFolder'] go() /** From 99c6395aca6243ec3bc644760c04b17f8b939619 Mon Sep 17 00:00:00 2001 From: Denperidge Date: Fri, 10 Feb 2023 11:57:38 +0100 Subject: [PATCH 07/10] Implemented Matrix command: Init --- gitter-solid.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/gitter-solid.js b/gitter-solid.js index 5d23e77..b0acc95 100644 --- a/gitter-solid.js +++ b/gitter-solid.js @@ -367,21 +367,22 @@ function gitterArchiveBaseURIFromRoom (room, config) { return (folder.uri) ? folder.uri : folder // needed if config newly created } -function matrixArchiveBaseURIFromRoom(room) { - +function matrixArchiveBaseURIFromRoom(room, config) { + // TODO: implement different folders. Currently Matrix will just default to public + const folder = config.publicChatFolder; + return (folder.uri) ? folder.uri : folder; } /** Decide URI of solid chat vchanel from properties of gitter room * * @param room {Room} - like 'solid/chat' */ -function gitterChatChannelFromRoom (room, config) { +function chatChannelFromRoom (room, config, archiveBaseURI) { var path let segment = room.name.split('/').map(encodeURIComponent).join('/') // Preseeve the slash begween org and room if (room.githubType === 'ORG') { segment += '/_Organization' // make all multi rooms two level names } - var archiveBaseURI = gitterArchiveBaseURIFromRoom(room, config) if (!archiveBaseURI.endsWith('/')) throw new Error('base should end with slash') if (room.oneToOne) { var username = room.user.username @@ -661,19 +662,21 @@ async function rdfDeleteMessage (chatChannel, payload) { /// ///////////////////////////// Do Room async function doRoom (room, config) { + console.log(room) console.log(`\nDoing room ${room.id}: ${room.name}`) // console.log('@@ bare room: ' + JSON.stringify(room)) - var gitterRoom - let solidChannel, archiveBaseURI; + var gitterRoom; + let archiveBaseURI; if (GITTER) { - solidChannel = gitterChatChannelFromRoom(room, config); archiveBaseURI = gitterArchiveBaseURIFromRoom(room, config); } if (MATRIX) { // TODO + archiveBaseURI = matrixArchiveBaseURIFromRoom(room, config); } + let solidChannel = chatChannelFromRoom(room, config, archiveBaseURI); @@ -736,14 +739,14 @@ async function doRoom (room, config) { } async function initialize () { - const solidChannel = gitterChatChannelFromRoom(room, config) + const solidChannel = chatChannelFromRoom(room, config, archiveBaseURI) console.log(' solid channel ' + solidChannel) // Make the main chat channel file var newChatDoc = solidChannel.doc() let already = await rdfLoadIfExists(newChatDoc) if (!already) { store.add(solidChannel, ns.rdf('type'), ns.meeting('LongChat'), newChatDoc) - store.add(solidChannel, ns.dc('title'), room.name + ' gitter chat archive', newChatDoc) + store.add(solidChannel, ns.dc('title'), room.name + ' solid-gitter chat archive', newChatDoc) await rdfPutResource(newChatDoc) console.log(' New chat channel created. ' + solidChannel) return false From 858cc4dcb7cb8f62d9891e85ab7b503056c0b79d Mon Sep 17 00:00:00 2001 From: Denperidge Date: Fri, 10 Feb 2023 12:02:05 +0100 Subject: [PATCH 08/10] doRoom log can now show Matrix or Gitter roomid --- gitter-solid.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gitter-solid.js b/gitter-solid.js index b0acc95..a8fc7b2 100644 --- a/gitter-solid.js +++ b/gitter-solid.js @@ -662,8 +662,9 @@ async function rdfDeleteMessage (chatChannel, payload) { /// ///////////////////////////// Do Room async function doRoom (room, config) { - console.log(room) - console.log(`\nDoing room ${room.id}: ${room.name}`) + //console.log(room) + const roomId = room.id || room.roomId; + console.log(`\nDoing room ${roomId}: ${room.name}`) // console.log('@@ bare room: ' + JSON.stringify(room)) var gitterRoom; let archiveBaseURI; From a3865fd94e678754179aa8bac15fb64f04bd4af0 Mon Sep 17 00:00:00 2001 From: Denperidge Date: Fri, 10 Feb 2023 13:24:34 +0100 Subject: [PATCH 09/10] First draft of catchup --- gitter-solid.js | 117 ++++++++++++++++++++++++++---------------- src/class-message.mjs | 41 +++++++++++++++ 2 files changed, 115 insertions(+), 43 deletions(-) create mode 100644 src/class-message.mjs diff --git a/gitter-solid.js b/gitter-solid.js index a8fc7b2..d8aa2a5 100644 --- a/gitter-solid.js +++ b/gitter-solid.js @@ -25,6 +25,7 @@ import * as readline from 'readline' import { show } from "./src/utils.mjs" import { setRoomList } from "./src/matrix-utils.mjs"; +import Message from "./src/class-message.mjs"; dotenv.config() @@ -554,15 +555,18 @@ async function authorFromGitter (fromUser, archiveBaseURI) { var newMessages = 0 var oldMessages = 0 -async function gitterStoreMessage (chatChannel, gitterMessage, archiveBaseURI) { - var sent = new Date(gitterMessage.sent) // Like "2014-03-25T11:51:32.289Z" +async function storeMessage (chatChannel, messageObject, archiveBaseURI, gitterMessageObject=null) { + // Gitter needs an extra function to determine maker + if (gitterMessageObject != null) { + messageObject.maker = await authorFromGitter(gitterMessageObject.fromUser, archiveBaseURI); + } // console.log(' Message sent on date ' + sent) - var chatDocument = rdfChatDocumentFromDate(chatChannel, sent) - var message = $rdf.sym(chatDocument.uri + '#' + gitterMessage.id) // like "53316dc47bfc1a000000000f" + var chatDocument = rdfChatDocumentFromDate(chatChannel, messageObject.created) + var messageRdf = $rdf.sym(chatDocument.uri + '#' + messageObject.id) // like "53316dc47bfc1a000000000f" // console.log(' Solid Message ' + message) await rdfLoadIfExists(chatDocument) - if (store.holds(chatChannel, ns.wf('message'), message, chatDocument)) { + if (store.holds(chatChannel, ns.wf('message'), messageRdf, chatDocument)) { // console.log(` already got ${gitterMessage.sent} message ${message}`) oldMessages += 1 return // alraedy got it @@ -570,20 +574,20 @@ async function gitterStoreMessage (chatChannel, gitterMessage, archiveBaseURI) { newMessages += 1 // console.log(`NOT got ${gitterMessage.sent} message ${message}`) - var author = await authorFromGitter(gitterMessage.fromUser, archiveBaseURI) - store.add(chatChannel, ns.wf('message'), message, chatDocument) - store.add(message, ns.sioc('content'), gitterMessage.text, chatDocument) - if (gitterMessage.html && gitterMessage.html !== gitterMessage.text) { // is it new information? - store.add(message, ns.sioc('richContent'), gitterMessage.html, chatDocument) // @@ predicate?? + + store.add(chatChannel, ns.wf('message'), messageRdf, chatDocument) + store.add(messageRdf, ns.sioc('content'), messageObject.content, chatDocument) + if (messageObject.richContent != null) { // is it new information? + store.add(messageRdf, ns.sioc('richContent'), messageObject.richContent, chatDocument) // @@ predicate?? } - store.add(message, ns.dct('created'), sent, chatDocument) - if (gitterMessage.edited) { - store.add(message, ns.dct('modified'), new Date(gitterMessage.edited), chatDocument) + store.add(messageRdf, ns.dct('created'), messageObject.created, chatDocument) + if (messageObject.modified) { + store.add(messageRdf, ns.dct('modified'), messageObject.modified, chatDocument) } - store.add(message, ns.foaf('maker'), author, chatDocument) + store.add(messageRdf, ns.foaf('maker'), messageObject.maker, chatDocument) // if (!toBePut[chatDocument.uri]) console.log(' Queueing to write ' + chatDocument) toBePut[chatDocument.uri] = true - return message + return messageRdf; } /** Update message friomn update operation @@ -666,7 +670,7 @@ async function doRoom (room, config) { const roomId = room.id || room.roomId; console.log(`\nDoing room ${roomId}: ${room.name}`) // console.log('@@ bare room: ' + JSON.stringify(room)) - var gitterRoom; + var gitterRoom, matrixRoom; let archiveBaseURI; if (GITTER) { @@ -684,8 +688,8 @@ async function doRoom (room, config) { console.log(' solid channel ' + solidChannel) - function gitterFindEarliestId (messages) { - var sortMe = messages.map(gitterMessage => [gitterMessage.sent, gitterMessage]) + function messagesFindEarliestId (messages) { + var sortMe = messages.map(messageObject => [messageObject.created, messageObject]) if (sortMe.length === 0) return null sortMe.sort() const earliest = sortMe[0][1] @@ -703,40 +707,67 @@ async function doRoom (room, config) { console.log(JSON.stringify(room)) } - async function gitterCatchup () { + + async function catchup () { newMessages = 0 oldMessages = 0 - gitterRoom = gitterRoom || await gitter.rooms.find(room.id) - var messages = await gitterRoom.chatMessages() // @@@@ ? - if (messages.length !== 50) console.log(' Messages read: ' + messages.length) - for (let gitterMessage of messages) { - await gitterStoreMessage(solidChannel, gitterMessage, archiveBaseURI) + let messages = []; + if (GITTER) { + gitterRoom = gitterRoom || await gitter.rooms.find(room.id) + let gitterMessages = await gitterRoom.chatMessages() // @@@@ ? + + if (gitterMessages.length !== 50) console.log(' Messages read: ' + messages.length) + for (let gitterMessage of gitterMessages) { + let message = new Message(gitterMessage, false); + await storeMessage(solidChannel, message, archiveBaseURI, gitterMessageObject = gitterMessage) + messages.push(message); + } + } else { + matrixRoom = await matrixClient.roomInitialSync(roomId, 100); + console.log("--matrixroom--") + console.log(matrixRoom) + + for (let matrixMessage of matrixRoom.messages.chunk) { + if (matrixMessage.type != "m.room.message") { + console.log("Currently gitter-solid only supports saving messages. Skipping " + matrixMessage.type) + continue; + } + let message = new Message(matrixMessage, true); + await storeMessage(solidChannel, message, archiveBaseURI); + messages.push(message); + + } } + await rdfSaveEverythingBack() if (oldMessages) { console.log('End catchup. Found message we already had.') return true } - var newId = gitterFindEarliestId(messages) + var newId = messagesFindEarliestId(messages) if (!newId) { console.log('Catchup found no gitter messages.') return true } - for (let i = 0; i < 30; i++) { - newId = await gitterExtendBeforeId(newId) - if (!newId) { - console.log(`End catchup. No more gitter messages after ${newMessages} new messages.`) - return true - } - if (oldMessages) { - console.log(`End catchup. Found message we already had, after ${newMessages} .`) - return true + // TODO implement for Matrix + if (GITTER) { + for (let i = 0; i < 30; i++) { + newId = await gitterExtendBeforeId(newId) + if (!newId) { + console.log(`End catchup. No more gitter messages after ${newMessages} new messages.`) + return true + } + if (oldMessages) { + console.log(`End catchup. Found message we already had, after ${newMessages} .`) + return true + } + console.log(' ... pause ...') + await delayMs(3000) // ms give the API a rest } - console.log(' ... pause ...') - await delayMs(3000) // ms give the API a rest + console.log(`FINISHED 30 CATCHUP SESSIONS. NOT DONE after ${newMessages} new messages `) + return false } - console.log(`FINISHED 30 CATCHUP SESSIONS. NOT DONE after ${newMessages} new messages `) - return false + } async function initialize () { @@ -787,7 +818,7 @@ async function doRoom (room, config) { console.log('Text: ', gitterEvent.model.text) console.log('gitterEvent object: ', JSON.stringify(gitterEvent)) if (gitterEvent.operation === 'create') { - var solidMessage = await gitterStoreMessage(solidChannel, gitterEvent.model, archiveBaseURI) + var solidMessage = await storeMessage(solidChannel, gitterEvent.model, archiveBaseURI) console.log('creating solid message ' + solidMessage) var sts = store.connectedStatements(solidMessage) try { @@ -825,7 +856,7 @@ async function doRoom (room, config) { return null } for (let gitterMessage of messages) { - await gitterStoreMessage(solidChannel, gitterMessage, archiveBaseURI) + await storeMessage(solidChannel, gitterMessage, archiveBaseURI) } await rdfSaveEverythingBack() let m1 = await rdfFirstMessage(solidChannel) @@ -842,7 +873,7 @@ async function doRoom (room, config) { console.log('First make the solid chat object if necessary:') await initialize() console.log('Now first catchup recent messages:') - var catchupDone = await gitterCatchup() + var catchupDone = await catchup() if (catchupDone) { console.log('Initial catchup gave no messages, so no archive necessary.✅') return null @@ -864,10 +895,10 @@ async function doRoom (room, config) { } else if (command === 'archive') { await rdfExtendArchiveBack() } else if (command === 'catchup') { - await gitterCatchup() + await catchup() } else if (command === 'stream') { console.log('catching up to make sure we don\'t miss any when we stream') - var ok = await gitterCatchup() + var ok = await catchup() if (!ok) { console.error('catching up FAILED so NOT starting stream as we would get a gap!') throw new Error('Not caught up. Cant stream.') diff --git a/src/class-message.mjs b/src/class-message.mjs new file mode 100644 index 0000000..038d5ce --- /dev/null +++ b/src/class-message.mjs @@ -0,0 +1,41 @@ +// Class to convert Matrix & Gitter message objects into a standardised thing +export default class Message { + constructor (messageObject, matrix=true) { + this.matrix = matrix; + if (matrix) { + this.id = messageObject.event_id; + this.maker = messageObject.sender; + + console.log("--content--") + this.content = messageObject.content.body; + if (messageObject.content.formatted_body) { + this.richContent = messageObject.content.formatted_body; + } + else { + this.richContent = null + } + // TODO implemenet relates_to and in_reply_to? + + this.created = new Date(messageObject.age) + this.modified = null + + + console.log(this.content) + } else { + // Source is gitter + this.id = gitterMessage.id; + this.maker = null; + + this.content = messageObject.text; + if (messageObject.html && messageObject.html !== messageObject.text) { // is it new information? + this.richContent = messageObject.html + } else { + this.richContent = null; + } + + this.created = new Date(messageObject.sent); // Like "2014-03-25T11:51:32.289Z" + this.modified = messageObject.edited ? new Date(messageObject.edited) : null + + } + } +} From 3cce8a0c130e4735a2709b354f5f42db56426082 Mon Sep 17 00:00:00 2001 From: Denperidge Date: Fri, 10 Feb 2023 14:02:12 +0100 Subject: [PATCH 10/10] Fixed Matrix Message.created & crash on no content --- gitter-solid.js | 4 ++++ src/class-message.mjs | 12 +++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/gitter-solid.js b/gitter-solid.js index d8aa2a5..95dd4f0 100644 --- a/gitter-solid.js +++ b/gitter-solid.js @@ -732,6 +732,10 @@ async function doRoom (room, config) { console.log("Currently gitter-solid only supports saving messages. Skipping " + matrixMessage.type) continue; } + if (!Message.matrixMessageHasContent(matrixMessage)) { + console.log("No content in message! Skipping.") + continue; + } let message = new Message(matrixMessage, true); await storeMessage(solidChannel, message, archiveBaseURI); messages.push(message); diff --git a/src/class-message.mjs b/src/class-message.mjs index 038d5ce..da283fe 100644 --- a/src/class-message.mjs +++ b/src/class-message.mjs @@ -1,5 +1,9 @@ // Class to convert Matrix & Gitter message objects into a standardised thing export default class Message { + static matrixMessageHasContent(messageObject) { + return (Object.keys(messageObject.content).length > 0); + } + constructor (messageObject, matrix=true) { this.matrix = matrix; if (matrix) { @@ -7,6 +11,8 @@ export default class Message { this.maker = messageObject.sender; console.log("--content--") + console.log(messageObject) + console.log(messageObject.content) this.content = messageObject.content.body; if (messageObject.content.formatted_body) { this.richContent = messageObject.content.formatted_body; @@ -16,7 +22,11 @@ export default class Message { } // TODO implemenet relates_to and in_reply_to? - this.created = new Date(messageObject.age) + this.created = new Date(); + this.created.setTime(this.created.getTime() - messageObject.age); + console.log("--date") + console.log(messageObject.age) + console.log(this.created); this.modified = null