From f110d03fdc96299bc0c4c848f8681c046c94d57e Mon Sep 17 00:00:00 2001 From: HF Date: Wed, 16 Mar 2022 20:51:26 +0100 Subject: [PATCH] parse basic markdown for sending images --- avatars/ca.png | Bin 0 -> 17121 bytes ppfun-bridge/README.md | 3 +- ppfun-bridge/ecosystem.yml | 1 + ppfun-bridge/index.js | 4 +- ppfun-bridge/src/markdown/MString.js | 204 ++++++++++++++++++ .../src/markdown/MarkdownParserLight.js | 104 +++++++++ ppfun-bridge/src/markdown/parse.js | 28 +++ ppfun-bridge/src/ppfunMatrixBridge.js | 78 ++++--- 8 files changed, 390 insertions(+), 32 deletions(-) create mode 100644 avatars/ca.png create mode 100644 ppfun-bridge/src/markdown/MString.js create mode 100644 ppfun-bridge/src/markdown/MarkdownParserLight.js create mode 100644 ppfun-bridge/src/markdown/parse.js diff --git a/avatars/ca.png b/avatars/ca.png new file mode 100644 index 0000000000000000000000000000000000000000..292ac886c9b2a1d530c82b2ea65bbe729856426e GIT binary patch literal 17121 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 literal 0 HcmV?d00001 diff --git a/ppfun-bridge/README.md b/ppfun-bridge/README.md index 4a46736..69a730b 100644 --- a/ppfun-bridge/README.md +++ b/ppfun-bridge/README.md @@ -26,8 +26,9 @@ app_service_config_files: ``` Edit ecosystem.yml and set the path to the `ppfun-registration.yml` as REGISTRATION_YAML. -Set the pixelplanet APISOCKET_KEY and APISOCKET_URL (like `https://pixelplanet.fun/mcws`) and PPFUN_UID to a user-id from pixelplanet that the bridge sends messages as (can be any number, but its better if its an existing user made for the bridge). +Set the pixelplanet APISOCKET_KEY and APISOCKET_URL (like `https://pixelplanet.fun/mcws`). HOMESERVER_URL should be the local url to matrix-synapse like `http://localhost:8008` and HOMESERVER_DOMAIN its base_url / server_name like `pixelplanet.fun` +MEDIA_URL is the http[s] url from which the matrix server is reachable from the outside, which is usually the base_url, it is needed to send links Now you can start the brige with pm2: diff --git a/ppfun-bridge/ecosystem.yml b/ppfun-bridge/ecosystem.yml index 5d84329..5d1d3fd 100644 --- a/ppfun-bridge/ecosystem.yml +++ b/ppfun-bridge/ecosystem.yml @@ -9,3 +9,4 @@ apps: REGISTRATION_YAML: "/etc/matrix-synapse/ppfun-registration.yaml" HOMESERVER_URL: "http://localhost:8008" HOMESERVER_DOMAIN: "pixelplanet.fun" + MEDIA_URL: "https://matrix.pixelplanet.fun" diff --git a/ppfun-bridge/index.js b/ppfun-bridge/index.js index 18567ab..57574dd 100644 --- a/ppfun-bridge/index.js +++ b/ppfun-bridge/index.js @@ -8,19 +8,19 @@ import PPfunMatrixBridge from './src/ppfunMatrixBridge.js'; const PORT = parseInt(process.env.PORT, 10) || 8009; const APISOCKET_KEY = process.env.APISOCKET_KEY || ''; const APISOCKET_URL = process.env.APISOCKET_URL || 'wss://dev.pixelplanet.fun/mcws'; -const PPFUN_UID = parseInt(process.env.PPFUN_UID, 10) || 1; const REGISTRATION_YAML = process.env.REGISTRATION_YAML || '/etc/matrix-synapse/ppfun-registration.yaml'; const HOMESERVER_URL = process.env.HOMESERVER_URL || 'http://localhost:8008'; const HOMESERVER_DOMAIN = process.env.HOMESERVER_DOMAIN || 'pixelplanet.fun'; +const MEDIA_URL = process.env.MEDIA_URL || `https://${HOMESERVER_DOMAIN}`; const lmao = new PPfunMatrixBridge({ apiSocketKey: APISOCKET_KEY, apiSocketUrl: APISOCKET_URL, - ppfunId: PPFUN_UID, homeserverUrl: HOMESERVER_URL, domain: HOMESERVER_DOMAIN, registration: REGISTRATION_YAML, port: PORT, + mediaUrl: MEDIA_URL }); lmao.run(); diff --git a/ppfun-bridge/src/markdown/MString.js b/ppfun-bridge/src/markdown/MString.js new file mode 100644 index 0000000..498bc6e --- /dev/null +++ b/ppfun-bridge/src/markdown/MString.js @@ -0,0 +1,204 @@ +/* + * class for string iterations + * that is used by MarkdownParser.js + */ + +export default class MString { + constructor(text, start) { + this.txt = text; + this.iter = start || 0; + } + + done() { + return (this.iter >= this.txt.length); + } + + moveForward() { + this.iter += 1; + return (this.iter < this.txt.length); + } + + setIter(iter) { + this.iter = iter; + } + + getChar() { + return this.txt[this.iter]; + } + + slice(start, end) { + return this.txt.slice(start, end || this.iter); + } + + has(str) { + return this.txt.startsWith(str, this.iter); + } + + move(cnt) { + this.iter += cnt; + return (this.iter < this.txt.length); + } + + static isWhiteSpace(chr) { + return (chr === ' ' || chr === '\t' || chr === '\n'); + } + + /* + * check if the current '[' is part of a [y](z) enclosure + * returns [y, z] if it is enclosure, null otherwise + * moves iter to last closing braked if it is enclosure + */ + checkIfEnclosure(zIsLink) { + const yStart = this.iter + 1; + + let yEnd = yStart; + while (this.txt[yEnd] !== ']') { + const chr = this.txt[yEnd]; + if (yEnd >= this.txt.length + || chr === '\n' + || chr === '[' + || chr === '(' + ) { + return null; + } + yEnd += 1; + } + + let zStart = yEnd + 1; + if (this.txt[zStart] !== '(') { + return null; + } + zStart += 1; + + let zEnd = zStart; + let z = null; + while (this.txt[zEnd] !== ')') { + const chr = this.txt[zEnd]; + if (zEnd >= this.txt.length + || chr === '\n' + || chr === '[' + || chr === '(' + ) { + return null; + } + if (zIsLink && chr === ':') { + // set this.iter temporarily to be able to use thischeckIfLink + const oldIter = this.iter; + this.iter = zEnd; + z = this.checkIfLink(); + zEnd = this.iter; + this.iter = oldIter; + if (z === null) { + return null; + } + continue; + } + zEnd += 1; + } + if (zEnd < zStart + 1 || (!z && zIsLink)) { + return null; + } + + if (!zIsLink) { + z = this.txt.slice(zStart, zEnd); + } + const y = this.txt.slice(yStart, yEnd); + + this.iter = zEnd; + return [y, z]; + } + + /* + * Convoluted way to check if the current ':' is part of a link + * we do not check for a 'http' because we might support application links + * like tg://... or discord://.. + * returns the link or false if there is none + * moves iter forward to after the link, if there's one + */ + checkIfLink() { + let cIter = this.iter; + if (!this.txt.startsWith('://', cIter) || cIter < 3) { + return null; + } + + let linkStart = cIter - 1; + for (; linkStart >= 0 + && !MString.isWhiteSpace(this.txt[linkStart]) + && this.txt[linkStart] !== '('; linkStart -= 1); + linkStart += 1; + + cIter += 3; + for (; cIter < this.txt.length + && !MString.isWhiteSpace(this.txt[cIter]) + && this.txt[cIter] !== ')'; cIter += 1 + ); + if (cIter < this.iter + 4) { + return null; + } + + /* special case where someone pasted a http link after a text + * without space in between + */ + let link = this.txt.slice(linkStart, cIter); + const httpOc = link.indexOf('http'); + if (httpOc !== -1 && httpOc !== 0) { + linkStart += httpOc; + link = this.txt.slice(linkStart, cIter); + } + + this.iter = cIter; + return link; + } + + /* + * Check if current '#' is part of ppfun coordinates (example: #d,23,11,-10) + * @return null if not coords, otherwise the coords string + */ + checkIfCoords() { + let cIter = this.iter + 1; + while (cIter < this.txt.length) { + const chr = this.txt[cIter]; + if (chr === ',') { + break; + } + if (MString.isWhiteSpace(chr) + || !Number.isNaN(Number(chr)) + ) { + return null; + } + cIter += 1; + } + if (cIter >= this.txt.length + || cIter - this.iter > 12 + || cIter === this.iter + ) { + return null; + } + cIter += 1; + const curChr = this.txt[cIter]; + if (curChr !== '-' && Number.isNaN(curChr)) { + return null; + } + cIter += 1; + let sectCount = 1; + while (cIter < this.txt.length && !MString.isWhiteSpace(this.txt[cIter])) { + const chr = this.txt[cIter]; + if (chr === ',') { + sectCount += 1; + } else if (chr !== '-' && Number.isNaN(Number(chr))) { + return null; + } + cIter += 1; + } + if (sectCount < 2 + || sectCount > 3 + || this.txt[cIter - 1] === ',' + ) { + return null; + } + + const coords = this.txt.slice(this.iter, cIter); + this.iter = cIter; + return coords; + } +} diff --git a/ppfun-bridge/src/markdown/MarkdownParserLight.js b/ppfun-bridge/src/markdown/MarkdownParserLight.js new file mode 100644 index 0000000..e5f3d04 --- /dev/null +++ b/ppfun-bridge/src/markdown/MarkdownParserLight.js @@ -0,0 +1,104 @@ +/* + * Markdown parsing + * + * Parses the very basics that are needed for the bridge + */ + +import MString from './MString.js'; + +function parseMParagraph(text) { + const pArray = []; + let pStart = text.iter; + let chr = null; + while (!text.done()) { + chr = text.getChar(); + + if (chr === '\n') { + text.moveForward(); + break; + } + + if (chr === '\\') { + /* + * escape character + */ + if (pStart !== text.iter) { + pArray.push(text.slice(pStart)); + } + pStart = text.iter + 1; + text.moveForward(); + } else if (chr === '#') { + /* + * ppfun coords #d,34,23,-10 + */ + const oldPos = text.iter; + const coords = text.checkIfCoords(); + if (coords) { + if (pStart !== oldPos) { + pArray.push(text.slice(pStart, oldPos)); + } + pArray.push(['l', null, `https://pixelplanet.fun/${coords}`]); + pStart = text.iter; + } + } else if (chr === ':') { + /* + * pure link + */ + const link = text.checkIfLink(); + if (link !== null) { + const startLink = text.iter - link.length; + if (pStart < startLink) { + pArray.push(text.slice(pStart, startLink)); + } + pArray.push(['l', null, link]); + pStart = text.iter; + continue; + } + } else if (chr === '[') { + /* + * x[y](z) enclosure + */ + let oldPos = text.iter; + let x = null; + if (text.iter > 0) { + text.move(-1); + x = text.getChar(); + text.setIter(oldPos); + } + /* + * x decides what element it is + * defaults to ordinary link + */ + let tag = 'l'; + let zIsLink = true; + if (x === '!') { + tag = 'img'; + oldPos -= 1; + } else if (x === '@') { + zIsLink = false; + tag = '@'; + oldPos -= 1; + } + + const encArr = text.checkIfEnclosure(zIsLink); + if (encArr !== null) { + if (pStart < oldPos) { + pArray.push(text.slice(pStart, oldPos)); + } + pArray.push([tag, encArr[0], encArr[1]]); + pStart = text.iter + 1; + } + } + + text.moveForward(); + } + if (pStart !== text.iter) { + pArray.push(text.slice(pStart)); + } + return pArray; +} + +export function parseParagraph(text,) { + const mText = new MString(text); + return parseMParagraph(mText); +} diff --git a/ppfun-bridge/src/markdown/parse.js b/ppfun-bridge/src/markdown/parse.js new file mode 100644 index 0000000..cfa0285 --- /dev/null +++ b/ppfun-bridge/src/markdown/parse.js @@ -0,0 +1,28 @@ +import { parseParagraph } from './MarkdownParserLight.js'; + +export default function parseMsg(msg) { + const pArray = parseParagraph(msg); + let output = ''; + for (let i = 0; i < pArray.length; i += 1) { + const part = pArray[i]; + if (!Array.isArray(part)) { + output += part; + } else { + const type = part[0]; + switch (type) { + case '@': { + output += `@${part[1]}`; + break; + } + case 'img': + case 'l': { + output += part[2]; + break; + } + default: + output += type; + } + } + } + return output; +} diff --git a/ppfun-bridge/src/ppfunMatrixBridge.js b/ppfun-bridge/src/ppfunMatrixBridge.js index 392ee70..9b0e2d3 100644 --- a/ppfun-bridge/src/ppfunMatrixBridge.js +++ b/ppfun-bridge/src/ppfunMatrixBridge.js @@ -2,6 +2,7 @@ import { Bridge } from 'matrix-appservice-bridge'; +import parseMsg from './markdown/parse.js' import PPfunSocket from './ppfunsocket.js'; import { parseCanvasLinks } from './pixelplanet/index.js'; @@ -11,6 +12,7 @@ class PPfunMatrixBridge { apiSocketKey, apiSocketUrl, homeserverUrl, + mediaUrl, domain, registration, port @@ -39,6 +41,7 @@ class PPfunMatrixBridge { }); this.port = port; this.domain = domain; + this.mediaUrl = mediaUrl; this.prefix = 'pp'; this.ppfunSocket.on('chanList', this.connectRooms.bind(this)); @@ -69,12 +72,13 @@ class PPfunMatrixBridge { console.warn(`Dropping a message because channels aren't synced yet`); return; } + const parsedMsg = parseMsg(msg); this.echoSuppression.set(`${uid}:${cid}`, Date.now()); - console.log(`PPFUN ${name}: ${msg}`); + console.log(`PPFUN ${name}: ${parsedMsg}`); this.sendMatrix( name, `@${this.prefix}_${uid}:${this.domain}`, - msg, + parsedMsg, matrixRoom, ); } @@ -112,33 +116,7 @@ class PPfunMatrixBridge { async recMatrix(request, context) { const event = request.getData(); - if (event.type === "m.room.message" - && event.content - && event.content.msgtype === "m.text" - ) { - const cid = this.mToProomMap.get(event.room_id); - if (!cid) { - return; - } - const msg = event.content.body; - this.sendCanvasSnapshotIfNeccessary(event.room_id, msg); - const userId = event.sender; - const uid = (userId.startsWith(`@${this.prefix}_`) - && userId.endsWith(this.domain)) - ? userId.slice(2 + this.prefix.length, -this.domain.length - 1) - : null; - if (this.echoSuppression.delete(`${uid}:${cid}`)) { - return; - } - let name = this.idToNameMap.get(userId); - if (!name) { - this.idToNameMap.set(userId, userId.substring(1)); - name = await this.getDisplayName(userId); - } - console.log(`MATRIX ${name}: ${msg}`); - this.sendPPfun(name, parseInt(uid, 10), msg, cid); - return; - } + // user joined or set displayname if (event.type === "m.room.member" && event.user_id && event.content @@ -149,6 +127,48 @@ class PPfunMatrixBridge { console.log(`User ${userId} joined or set displayname to ${name}`); this.idToNameMap.set(userId, name); } + // Only room messages from bridged rooms past this point + if (event.type !== "m.room.message" || !event.content) { + return; + } + const cid = this.mToProomMap.get(event.room_id); + if (!cid) { + return; + } + let msg; + // Media: send url as markdown + if ((event.content.msgtype === "m.image" + || event.content.msgtype === "m.video") + && event.content.url + ) { + // adding a query parameter so that client can guess what it is + const url = `${this.mediaUrl}/_matrix/media/r0/download/${event.content.url.substring("mxc://".length)}?type=${event.content.msgtype.substring(2)}` + msg = `[${event.content.body}](${url})`; + // Text + } else if (event.content.msgtype === "m.text") { + msg = event.content.body; + this.sendCanvasSnapshotIfNeccessary(event.room_id, msg); + } + if (!msg) { + return; + } + + const userId = event.sender; + const uid = (userId.startsWith(`@${this.prefix}_`) + && userId.endsWith(this.domain)) + ? userId.slice(2 + this.prefix.length, -this.domain.length - 1) + : null; + if (this.echoSuppression.delete(`${uid}:${cid}`)) { + return; + } + let name = this.idToNameMap.get(userId); + if (!name) { + this.idToNameMap.set(userId, userId.substring(1)); + name = await this.getDisplayName(userId); + } + console.log(`MATRIX ${name}: ${msg}`); + this.sendPPfun(name, parseInt(uid, 10), msg, cid); + return; } /*