be able to send uid null
This commit is contained in:
parent
5ebf16aa08
commit
7344945a43
Binary file not shown.
After Width: | Height: | Size: 72 KiB |
|
@ -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;
|
|
@ -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;
|
|
@ -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}`,
|
||||
}
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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');
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in New Issue