be able to send uid null

This commit is contained in:
HF 2022-01-15 20:06:20 +01:00
parent 5ebf16aa08
commit 7344945a43
8 changed files with 363 additions and 7 deletions

BIN
avatars/flags.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

@ -0,0 +1,127 @@
class Palette {
constructor(colors) {
this.length = colors.length;
this.rgb = new Uint8Array(this.length * 3);
this.colors = new Array(this.length);
this.abgr = new Uint32Array(this.length);
this.fl = new Array(this.length);
let cnt = 0;
for (let index = 0; index < colors.length; index++) {
const r = colors[index][0];
const g = colors[index][1];
const b = colors[index][2];
this.rgb[cnt++] = r;
this.rgb[cnt++] = g;
this.rgb[cnt++] = b;
this.colors[index] = `rgb(${r}, ${g}, ${b})`;
this.abgr[index] = (0xFF000000) | (b << 16) | (g << 8) | (r);
this.fl[index] = [r / 256, g / 256, b / 256];
}
}
/*
* Check if a color is light (closer to white) or dark (closer to black)
* @param color Index of color in palette
* @return dark True if color is dark
*/
isDark(color) {
color *= 3;
const r = this.rgb[color++];
const g = this.rgb[color++];
const b = this.rgb[color];
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
return (luminance < 128);
}
/*
* Get last matching color index of RGB color
* @param r r
* @param g g
* @param b b
* @return index of color
*/
getIndexOfColor(r, g, b) {
const { rgb } = this;
let i = rgb.length / 3;
while (i > 0) {
i -= 1;
const off = i * 3;
if (rgb[off] === r
&& rgb[off + 1] === g
&& rgb[off + 2] === b
) {
return i;
}
}
return null;
}
/*
* Take a buffer of indexed pixels and output it as ABGR Array
* @param chunkBuffer Buffer of indexed pixels
* @return ABRG Buffer
*/
buffer2ABGR(chunkBuffer) {
const { length } = chunkBuffer;
const colors = new Uint32Array(length);
let value;
const buffer = chunkBuffer;
let pos = 0;
for (let i = 0; i < length; i++) {
value = (buffer[i] & 0x3F);
colors[pos++] = this.abgr[value];
}
return colors;
}
/*
* Take a buffer of indexed pixels and output it as RGB Array
* @param chunkBuffer Buffer of indexed pixels
* @return RGB Buffer
*/
buffer2RGB(chunkBuffer) {
const { length } = chunkBuffer;
const colors = new Uint8Array(length * 3);
let color;
let value;
const buffer = chunkBuffer;
let c = 0;
for (let i = 0; i < length; i++) {
value = buffer[i];
color = (value & 0x3F) * 3;
colors[c++] = this.rgb[color++];
colors[c++] = this.rgb[color++];
colors[c++] = this.rgb[color];
}
return colors;
}
/*
* Create a RGB Buffer of a specific size with just one color
* @param color Color Index of color to use
* @param length Length of needed Buffer
* @return RGB Buffer of wanted size with just one color
*/
oneColorBuffer(color, length) {
const buffer = new Uint8Array(length * 3);
const r = this.rgb[color * 3];
const g = this.rgb[color * 3 + 1];
const b = this.rgb[color * 3 + 2];
let pos = 0;
for (let i = 0; i < length; i++) {
buffer[pos++] = r;
buffer[pos++] = g;
buffer[pos++] = b;
}
return buffer;
}
}
export default Palette;

View File

@ -0,0 +1,5 @@
export const WIDTH = 1024;
export const HEIGHT = 768;
export const MAX_SCALE = 40; // 52 in log2
export const TILE_SIZE = 256;
export const TILE_ZOOM_LEVEL = 4;

View File

@ -0,0 +1,73 @@
import fetch from 'node-fetch';
import renderCanvas from './renderCanvas';
import Palette from './Palette';
import {
TILE_SIZE,
TILE_ZOOM_LEVEL,
} from './constants';
const linkRegExp = /(#[a-z]*,-?[0-9]*,-?[0-9]*(,-?[0-9]+)?)/gi;
const linkRegExpFilter = (val, ind) => ((ind % 3) !== 2);
let canvases = null;
async function fetchCanvasData() {
try {
const response = await fetch('https://pixelplanet.fun/api/me');
if (response.status >= 300) {
throw new Error('Can not connect to pixelplanet!');
}
const data = await response.json();
const canvasMap = new Map();
const ids = Object.keys(data.canvases);
for (let i = 0; i < ids.length; i += 1) {
const id = ids[i];
const canvas = data.canvases[id];
canvas.id = id;
canvas.palette = new Palette(canvas.colors);
canvas.maxTiledZoom = Math.log2(canvas.size / TILE_SIZE) / TILE_ZOOM_LEVEL * 2;
canvasMap.set(canvas.ident, canvas);
}
canvases = canvasMap;
console.log('Successfully fetched canvas data from pixelplanet');
} catch (e) {
console.log('Couldn\'t connect to pixelplanet, trying to connect again in 60s');
setTimeout(fetchCanvasData, 60000);
throw(e)
}
}
fetchCanvasData();
export async function parseCanvasLinks(msg) {
const {
text,
} = msg;
if (!text || !canvases) {
return;
}
const msgArray = text.split(linkRegExp).filter(linkRegExpFilter);
if (msgArray.length > 1) {
const coordsParts = msgArray[1].substr(1).split(',');
const [ canvasIdent, x, y, z ] = coordsParts;
const canvas = canvases.get(canvasIdent);
if (!canvas) {
return;
}
console.log(`Fetch canvas ${canvas.title} on ${x}/${y} with zoom ${z}`);
const image = await renderCanvas(canvas, x, y, z);
msg.reply({
attachments: [
{
image,
text: `${canvas.title} on ${x}/${y} with zoom ${z}`,
}
],
});
}
}

View File

@ -0,0 +1,62 @@
import fetch from 'node-fetch';
import {
createCanvas,
loadImage,
createImageData,
} from 'canvas';
import {
TILE_SIZE,
} from './constants';
export async function getChunk(
canvas,
zoom,
cx,
cy,
) {
const tile = createCanvas(TILE_SIZE, TILE_SIZE);
const canvasId = canvas.id;
if (canvas.maxTiledZoom === zoom) {
await fetchBaseChunk(canvasId, canvas.palette, zoom, cx, cy, tile);
} else {
await fetchTile(canvasId, zoom, cx, cy, tile);
}
return tile;
}
async function fetchBaseChunk(
canvasId,
palette,
zoom,
cx, cy,
tile,
) {
const url = `https://pixelplanet.fun/chunks/${canvasId}/${cx}/${cy}.bmp`;
console.log(`Fetching ${url}`);
const response = await fetch(url);
if (response.ok) {
const arrayBuffer = await response.arrayBuffer();
if (arrayBuffer.byteLength) {
const chunkArray = new Uint8Array(arrayBuffer);
const abgr = palette.buffer2ABGR(chunkArray);
const imageData = createImageData(
new Uint8ClampedArray(abgr.buffer),
TILE_SIZE,
TILE_SIZE,
);
const ctx = tile.getContext('2d');
ctx.putImageData(imageData, 0, 0);
}
}
}
async function fetchTile(canvasId, zoom, cx, cy, tile) {
const url = `https://pixelplanet.fun/tiles/${canvasId}/${zoom}/${cx}/${cy}.png`;
console.log(`Fetching ${url}`);
const image = await loadImage(url);
const ctx = tile.getContext('2d');
ctx.drawImage(image, 0, 0);
}

View File

@ -0,0 +1,93 @@
import { createCanvas } from 'canvas';
import { getChunk } from './loadChunk';
import {
WIDTH,
HEIGHT,
MAX_SCALE,
TILE_SIZE,
TILE_ZOOM_LEVEL,
} from './constants';
function coordToChunk(z, canvasSize, tiledScale) {
return Math.floor((z + canvasSize / 2) / TILE_SIZE * tiledScale);
}
function chunkToCoord(z, canvasSize, tiledScale) {
return Math.round(z * TILE_SIZE / tiledScale - canvasSize / 2);
}
async function drawChunk(
ctx, xOff, yOff,
canvas, tiledZoom, xc, yc,
) {
try {
const chunk = await getChunk(canvas, tiledZoom, xc, yc);
ctx.drawImage(chunk, xOff,yOff);
} catch(e) {
console.log(`Chunk ${xc} / ${yc} - ${tiledZoom} is empty`);
}
}
export default async function renderCanvas(
canvas,
x,
y,
z,
) {
const can = createCanvas(WIDTH, HEIGHT);
const ctx = can.getContext('2d');
const canvasSize = canvas.size;
let scale = 2 ** (z / 10);
scale = Math.max(scale, TILE_SIZE / canvas.size);
scale = Math.min(scale, MAX_SCALE);
let tiledScale = (scale > 0.5)
? 0
: Math.round(Math.log2(scale) / 2);
tiledScale = TILE_ZOOM_LEVEL ** tiledScale;
const tiledZoom = canvas.maxTiledZoom + Math.log2(tiledScale) / 2;
const relScale = scale / tiledScale;
ctx.scale(relScale, relScale);
ctx.fillStyle = canvas.palette.colors[0];
ctx.imageSmoothingEnabled = false;
ctx.patternQuality = "nearest";
ctx.antialias = 'none';
ctx.fillRect(0, 0, WIDTH, HEIGHT);
const tlX = Math.floor(x - WIDTH / 2 / scale);
const tlY = Math.floor(y - HEIGHT / 2 / scale);
const brX = Math.floor(x - 1 + WIDTH / 2 / scale);
const brY = Math.floor(y - 1 + HEIGHT / 2 / scale);
const tlCX = coordToChunk(tlX, canvasSize, tiledScale);
const tlCY = coordToChunk(tlY, canvasSize, tiledScale);
const brCX = coordToChunk(brX, canvasSize, tiledScale);
const brCY = coordToChunk(brY, canvasSize, tiledScale);
console.log(`Load chunks from ${tlCX} / ${tlCY} to ${brCX} / ${brCY}`);
const chunkMax = canvasSize / TILE_SIZE * tiledScale;
const promises = [];
for (let xc = tlCX; xc <= brCX; xc += 1) {
for (let yc = tlCY; yc <= brCY; yc += 1) {
const xOff = Math.round((chunkToCoord(xc, canvasSize, tiledScale) - tlX) * tiledScale);
const yOff = Math.round((chunkToCoord(yc, canvasSize, tiledScale) - tlY) * tiledScale);
if (xc < 0 || xc >= chunkMax || yc < 0 || yc >= chunkMax) {
ctx.clearRect(xOff, yOff, TILE_SIZE, TILE_SIZE);
continue;
}
promises.push(
drawChunk(ctx, xOff, yOff, canvas, tiledZoom, xc, yc),
);
}
}
await Promise.all(promises);
return can.toBuffer('image/png');
}

View File

@ -9,7 +9,6 @@ class PPfunMatrixBridge {
const {
apiSocketKey,
apiSocketUrl,
ppfunId,
homeserverUrl,
domain,
registration,
@ -27,7 +26,6 @@ class PPfunMatrixBridge {
this.ppfunSocket = new PPfunSocket(
apiSocketUrl,
apiSocketKey,
ppfunId,
);
this.matrixBridge = new MatrixBridge({
homeserverUrl,
@ -39,7 +37,6 @@ class PPfunMatrixBridge {
}
});
this.port = port;
this.ppfunId = ppfunId;
this.domain = domain;
this.prefix = 'pp';
@ -153,7 +150,7 @@ class PPfunMatrixBridge {
this.ppfunSocket.emit(
'sendChatMessage',
(uid) ? name : `[mx] ${name}`,
uid || this.ppfunId,
uid || null,
msg,
cid,
);

View File

@ -4,15 +4,14 @@ const EventEmitter = require('events');
const WebSocket = require('ws')
class PPfunSocket extends EventEmitter {
constructor(url, apisocketkey, ppfunId) {
constructor(url, apisocketkey) {
super();
this.url = url;
this.apisocketkey = apisocketkey;
this.ppfunId = ppfunId;
this.timeConnected = null;
this.isConnected = false;
this.flagMap = new Map();
this.flagMap.set(ppfunId, 'yy');
this.flagMap.set(null , 'yy');
this.ws;
this.onWsClose = this.onWsClose.bind(this);
console.log('PPfunSocket: Connecting to WebSocket')