parse basic markdown for sending images
This commit is contained in:
parent
91a71c79b2
commit
f110d03fdc
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
|
@ -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:
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
Loading…
Reference in New Issue