diff options
author | Yarmo Mackenbach <yarmo@yarmo.eu> | 2022-04-05 08:33:30 +0200 |
---|---|---|
committer | Yarmo Mackenbach <yarmo@yarmo.eu> | 2022-04-05 08:33:30 +0200 |
commit | 53bf124fc825b7393ec9abe522d3e0505199a22f (patch) | |
tree | 7739f0bbd6297ae110ccdf3117c64e672ef7c029 |
Initial commit
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | .prettierignore | 2 | ||||
-rw-r--r-- | .prettierrc | 6 | ||||
-rw-r--r-- | Dockerfile | 12 | ||||
-rw-r--r-- | package.json | 22 | ||||
-rw-r--r-- | src/db.js | 159 | ||||
-rw-r--r-- | src/index.js | 372 | ||||
-rw-r--r-- | yarn.lock | 489 |
8 files changed, 1065 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f10206 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +data +.env
\ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..aa15018 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +node_modules +data
\ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..e74ed9f --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 4, + "semi": false, + "singleQuote": true +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f38598e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM node:16-alpine + +RUN mkdir /app +RUN mkdir /data +WORKDIR /app + +COPY ./src /app +COPY ./package.json /app +COPY ./yarn.lock /app +RUN yarn --production --pure-lockfile + +CMD yarn start
\ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..c7eb170 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "matrix-shared-expenses-bot", + "version": "0.0.1", + "description": "Shared expenses bot for Matrix", + "main": "./src/index.js", + "type": "module", + "scripts": { + "start": "node ./", + "prettier:check": "prettier --check .", + "prettier:write": "prettier --write ." + }, + "author": "Yarmo Mackenbach <yarmo@yarmo.eu>", + "license": "MIT", + "dependencies": { + "dotenv": "^16.0.0", + "lowdb": "^3.0.0", + "matrix-js-sdk": "^16.0.1" + }, + "devDependencies": { + "prettier": "^2.6.2" + } +} diff --git a/src/db.js b/src/db.js new file mode 100644 index 0000000..f1d41fc --- /dev/null +++ b/src/db.js @@ -0,0 +1,159 @@ +import { join, dirname, resolve } from 'path' +import { Low, JSONFile } from 'lowdb' +import { fileURLToPath } from 'url' +import crypto from 'crypto' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const file = join(__dirname, '../data', 'db.json') +let adapter +let db + +const init = async () => { + adapter = new JSONFile(file) + db = new Low(adapter) + + await db.read() + db.data ||= { rooms: {} } + await db.write() +} + +const read = async () => { + await db.read() + return db.data +} + +const write = async (obj) => { + await db.read() + let roomHash + + switch (obj.type) { + case 'introduction': + case 'expense': + roomHash = _getHash(obj.roomId) + db.data.rooms[roomHash] ||= { events: [] } + db.data.rooms[roomHash].events.push(obj) + break + + default: + throw new Error('Invalid type for db.write()') + break + } + + await db.write() +} + +const undo = async (roomId) => { + const roomHash = _getHash(roomId) + await db.read() + + db.data.rooms[roomHash] ||= { events: [] } + + db.data.rooms[roomHash].events.pop() + + await db.write() +} + +const reset = async (roomId) => { + const roomHash = _getHash(roomId) + await db.read() + + db.data.rooms[roomHash] ||= { events: [] } + + db.data.rooms[roomHash].events = db.data.rooms[roomHash].events.filter( + (e) => { + return !(e.type === 'expense' || e.type === 'transaction') + } + ) + + await db.write() +} + +const fullreset = async (roomId) => { + const roomHash = _getHash(roomId) + await db.read() + + db.data.rooms[roomHash] ||= { events: [] } + + delete db.data.rooms[roomHash] + + await db.write() +} + +const getUsers = async (roomId) => { + const roomHash = _getHash(roomId) + const data = await read() + + let userIds = [] + + data.rooms[roomHash] ||= { events: [] } + + data.rooms[roomHash].events.forEach((event) => { + if (event.type === 'introduction') { + userIds.push(event.userId) + } + }) + + return userIds +} + +const getEventLog = async (roomId, limit) => { + const roomHash = _getHash(roomId) + const data = await read() + + data.rooms[roomHash] ||= { events: [] } + + return data.rooms[roomHash].events.slice(-(limit || 4)) +} + +const getBalance = async (roomId) => { + const roomHash = _getHash(roomId) + const data = await read() + + let balance = { + userIds: [], + } + + data.rooms[roomHash] ||= { events: [] } + + data.rooms[roomHash].events.forEach((event) => { + switch (event.type) { + case 'introduction': + balance.userIds.push(event.userId) + balance[event.userId] = { + totalSpent: 0, + spentForUserId: {}, + } + break + + case 'expense': + balance[event.userId].totalSpent += event.data.amount + balance.userIds.forEach((userId) => { + balance[event.userId].spentForUserId[userId] ||= 0 + balance[event.userId].spentForUserId[userId] += + event.data.amount / balance.userIds.length + }) + break + + default: + break + } + }) + + return balance +} + +const _getHash = (string) => { + return crypto.createHash('sha256').update(string, 'utf8').digest('hex') +} + +export default { + init: init, + read: read, + write: write, + undo: undo, + reset: reset, + fullreset: fullreset, + getUsers: getUsers, + getEventLog: getEventLog, + getBalance: getBalance, +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..13296e0 --- /dev/null +++ b/src/index.js @@ -0,0 +1,372 @@ +import 'dotenv/config' +import * as sdk from 'matrix-js-sdk' +import db from './db.js' + +// Initialize database +db.init() + +// Prepare Matrix client +const botUserId = process.env.MATRIX_USERID +const matrixClient = sdk.createClient({ + baseUrl: process.env.MATRIX_BASEURL, + accessToken: process.env.MATRIX_ACCESS_TOKEN, + userId: botUserId, +}) + +// Prepare Matrix functions +const sendMessage = (roomId, message, formattedMessage) => { + let content = { + body: message, + msgtype: 'm.text', + } + + if (formattedMessage) { + content.format = 'org.matrix.custom.html' + content.formatted_body = formattedMessage + } + + matrixClient.sendEvent( + roomId, + 'm.room.message', + content, + '', + (err, res) => { + console.log(err) + } + ) +} + +// Automatically join new rooms +matrixClient.on('RoomMember.membership', function (event, member) { + if (member.membership === 'invite' && member.userId === botUserId) { + matrixClient.joinRoom(member.roomId).then(function () { + console.log('Auto-joined %s', member.roomId) + }) + } +}) + +// Listen for new messages +matrixClient.on('Room.timeline', async (event, room, toStartOfTimeline) => { + // Don't print paginated results + if (toStartOfTimeline) { + return + } + + // Ignore non-messages + if (event.getType() !== 'm.room.message') { + return + } + + // Ignore own messages + if (event.getSender() === botUserId) { + return + } + + // Ignore old messages + if (event.getAge() && event.getAge() > 10 * 1000) { + // in milliseconds + return + } + + // Get the message + const message = `${event.getContent().body}` + + // Log the message + console.log('(%s) %s :: %s', room.name, event.getSender(), message) + + // Try and find a command in the message + const match = message.match(/^!([a-zA-Z]*)(?: (.*))?/) + if (match) { + const command = match[1] + const args = (match[2] || '').split(' ') + + let users = await db.getUsers(room.roomId) + + switch (command) { + case 'h': + case 'help': + sendMessage( + room.roomId, + '!hi: introduce yourself to Seb', + '<b>!hi</b>: introduce yourself to Seb' + ) + sendMessage( + room.roomId, + '!e 42: add a new expense', + '<b>!e 42</b>: add a new expense' + ) + sendMessage( + room.roomId, + '!e 42 Saturday market: describe the expense', + '<b>!e 42 Saturday market</b>: describe the expense' + ) + sendMessage( + room.roomId, + '!b: get the balance', + '<b>!b</b>: get the balance' + ) + sendMessage( + room.roomId, + '!u: get the list of users', + '<b>!u</b>: get the list of users' + ) + sendMessage( + room.roomId, + "!l: list the last 4 actions", + "<b>!l</b>: list the last 4 actions" + ) + sendMessage( + room.roomId, + '!l 6: request a specific number of actions', + '<b>!l 6</b>: request a specific number of actions' + ) + sendMessage( + room.roomId, + '!undo: undo the last action', + '<b>!undo</b>: undo the last action' + ) + sendMessage( + room.roomId, + '!reset: remove all expenses and transactions (cannot be undone!)', + '<b>!reset</b>: remove all expenses and transactions (cannot be undone!)' + ) + sendMessage( + room.roomId, + '!fullreset: remove all data (cannot be undone!)', + '<b>!fullreset</b>: remove all data (cannot be undone!)' + ) + break + + case 'hi': + if ( + (await db.getUsers(room.roomId)).includes(event.getSender()) + ) { + sendMessage( + room.roomId, + `👋 We've already met, ${event.getSender()}` + ) + return + } + + db.write({ + type: 'introduction', + timestamp: event.getDate(), + roomId: room.roomId, + userId: event.getSender(), + data: {}, + }) + .then(() => { + sendMessage( + room.roomId, + `👋 Pleased to meet you, ${event.getSender()}` + ) + }) + .catch((e) => { + sendMessage( + room.roomId, + `❌ Whoops, something went wrong (${e})` + ) + }) + break + + case 'u': + case 'users': + if (users.length === 0) { + sendMessage( + room.roomId, + "🤷 I don't know anyone here. Do say !hi to get started" + ) + } + users.forEach((user) => { + sendMessage(room.roomId, user) + }) + break + + case 'l': + case 'log': + case 'list': + ;(await db.getEventLog(room.roomId, parseInt(args[0]))).forEach( + (event) => { + const ts = new Date(event.timestamp) + const formattedTs = `${ts + .getDate() + .toString() + .padStart(2, '0')}-${(ts.getMonth() + 1) + .toString() + .padStart(2, '0')}-${ts + .getFullYear() + .toString()} ${ts + .getHours() + .toString() + .padStart(2, '0')}:${ts + .getMinutes() + .toString() + .padStart(2, '0')}` + + switch (event.type) { + case 'introduction': + sendMessage( + room.roomId, + `⌚ ${formattedTs} → ${event.type}\n 👋 ${event.userId}` + ) + break + + case 'expense': + sendMessage( + room.roomId, + `⌚ ${formattedTs} → ${ + event.type + }\n 🪙 ${event.data.currency} ${ + event.data.amount + }\n 🧑 ${event.userId}\n 📜 ${ + event.data.description || + 'No description' + }` + ) + break + + default: + break + } + } + ) + break + + case 'e': + case 'exp': + case 'expense': + let amount = parseFloat(args[0]) + let description = args + .slice(1) + .filter((a) => { + console.log(a, a[0]) + return a[0] !== '?' + }) + .join(' ') + + if (!amount) { + sendMessage( + room.roomId, + '❌ That expense is invalid, need !help ?' + ) + return + } + + db.write({ + type: 'expense', + timestamp: event.getDate(), + roomId: room.roomId, + userId: event.getSender(), + data: { + amount: amount, + currency: 'EUR', + description: description, + }, + }) + .then(() => { + sendMessage(room.roomId, '👍 Expense noted') + }) + .catch((e) => { + sendMessage( + room.roomId, + `❌ Whoops, something went wrong (${e})` + ) + }) + break + + case 'b': + case 'balance': + if (users.length === 0) { + sendMessage( + room.roomId, + '🤷 No users to make a balance for' + ) + } + let balance = await db.getBalance(room.roomId) + + // number of combinations = (n^2 + n)/2 + for (let index = 0; index < balance.userIds.length; index++) { + const userId1 = balance.userIds[index] + + for ( + let index2 = index + 1; + index2 < balance.userIds.length; + index2++ + ) { + const userId2 = balance.userIds[index2] + + const diff = + (balance[userId2].spentForUserId[userId1] || 0) - + (balance[userId1].spentForUserId[userId2] || 0) + + sendMessage( + room.roomId, + `${userId1} ${ + diff > 0 ? '→' : '←' + } ${userId2}: ${Math.abs(diff).toFixed(2)}` + ) + } + } + break + + case 'undo': + db.undo(room.roomId) + .then(() => { + sendMessage(room.roomId, '👍 Last action was undone') + }) + .catch((e) => { + sendMessage( + room.roomId, + `❌ Whoops, something went wrong (${e})` + ) + }) + break + + case 'reset': + db.reset(room.roomId) + .then(() => { + sendMessage( + room.roomId, + '👍 All expenses and transactions have been erased' + ) + }) + .catch((e) => { + sendMessage( + room.roomId, + `❌ Whoops, something went wrong (${e})` + ) + }) + break + + case 'fullreset': + db.fullreset(room.roomId) + .then(() => { + sendMessage(room.roomId, '👍 All data has been erased') + }) + .catch((e) => { + sendMessage( + room.roomId, + `❌ Whoops, something went wrong (${e})` + ) + }) + break + + default: + sendMessage( + room.roomId, + '🤷 Not sure what you are asking for. Need !help ?' + ) + break + } + return + } + + // Reply to cookie + if (message.toLowerCase().includes('cookie')) { + sendMessage(room.roomId, 'Did someone say cookie?') + return + } +}) + +// Start the Matrix client +matrixClient.startClient() diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..7543818 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,489 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/runtime@^7.12.5": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2" + integrity sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA== + dependencies: + regenerator-runtime "^0.13.4" + +"@types/retry@^0.12.0": + version "0.12.1" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065" + integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g== + +ajv@^6.12.3: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +another-json@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/another-json/-/another-json-0.2.0.tgz#b5f4019c973b6dd5c6506a2d93469cb6d32aeedc" + integrity sha1-tfQBnJc7bdXGUGotk0acttMq7tw= + +asn1@~0.2.3: + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" + integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== + +base-x@^3.0.2: + version "3.0.9" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" + integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== + dependencies: + safe-buffer "^5.0.1" + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +browser-request@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/browser-request/-/browser-request-0.3.3.tgz#9ece5b5aca89a29932242e18bf933def9876cc17" + integrity sha1-ns5bWsqJopkyJC4Yv5M975h2zBc= + +bs58@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" + integrity sha1-vhYedsNU9veIrkBx9j806MTwpCo= + dependencies: + base-x "^3.0.2" + +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +content-type@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +dotenv@^16.0.0: + version "16.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.0.tgz#c619001253be89ebb638d027b609c75c26e47411" + integrity sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q== + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +get-intrinsic@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + +has-symbols@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +jsprim@^1.2.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.4.0" + verror "1.10.0" + +loglevel@^1.7.1: + version "1.8.0" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.0.tgz#e7ec73a57e1e7b419cb6c6ac06bf050b67356114" + integrity sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA== + +lowdb@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lowdb/-/lowdb-3.0.0.tgz#c10ab4e7eb86f1cbe255e35e60ffb0c6f42049e0" + integrity sha512-9KZRulmIcU8fZuWiaM0d5e2/nPnrFyXkeXVpqT+MJS+vgbgOf1EbtvgQmba8HwUFgDl1oeZR6XqEJnkJmQdKmg== + dependencies: + steno "^2.1.0" + +matrix-events-sdk@^0.0.1-beta.7: + version "0.0.1-beta.7" + resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1-beta.7.tgz#5ffe45eba1f67cc8d7c2377736c728b322524934" + integrity sha512-9jl4wtWanUFSy2sr2lCjErN/oC8KTAtaeaozJtrgot1JiQcEI4Rda9OLgQ7nLKaqb4Z/QUx/fR3XpDzm5Jy1JA== + +matrix-js-sdk@^16.0.1: + version "16.0.1" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-16.0.1.tgz#9b389ef16677ba648efad2929a7802af6f1dc81f" + integrity sha512-GRYZY7JZRqsVFa2nKO2qJbU4gQail2+1PgX2QDcibWizTL5Gh8YS384twprpIKqzdLHJ3d7H7A0L+uqc562ZsQ== + dependencies: + "@babel/runtime" "^7.12.5" + another-json "^0.2.0" + browser-request "^0.3.3" + bs58 "^4.0.1" + content-type "^1.0.4" + loglevel "^1.7.1" + matrix-events-sdk "^0.0.1-beta.7" + p-retry "^4.5.0" + qs "^6.9.6" + request "^2.88.2" + unhomoglyph "^1.0.6" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +object-inspect@^1.9.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" + integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== + +p-retry@^4.5.0: + version "4.6.1" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.1.tgz#8fcddd5cdf7a67a0911a9cf2ef0e5df7f602316c" + integrity sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA== + dependencies: + "@types/retry" "^0.12.0" + retry "^0.13.1" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +prettier@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032" + integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew== + +psl@^1.1.28: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +qs@^6.9.6: + version "6.10.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" + integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== + dependencies: + side-channel "^1.0.4" + +qs@~6.5.2: + version "6.5.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" + integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== + +regenerator-runtime@^0.13.4: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + +request@^2.88.2: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + +safe-buffer@^5.0.1, safe-buffer@^5.1.2: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +sshpk@^1.7.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" + integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +steno@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/steno/-/steno-2.1.0.tgz#05a9c378ce42ed04f642cda6fcb41787a10e4e33" + integrity sha512-mauOsiaqTNGFkWqIfwcm3y/fq+qKKaIWf1vf3ocOuTdco9XoHCO2AGF1gFYXuZFSWuP38Q8LBHBGJv2KnJSXyA== + +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +unhomoglyph@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/unhomoglyph/-/unhomoglyph-1.0.6.tgz#ea41f926d0fcf598e3b8bb2980c2ddac66b081d3" + integrity sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" |