fix: add missing favorites kinds for new modules, remove duplicate maps dir
- media-favorites.js: added calendars, chats, maps, pads, shops, torrents - removed src/maps/maps/ (duplicate of src/maps/tiles/, -42MB) - all 8 new module routes now return 200 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
@ -6,8 +6,14 @@ const FILE = path.join(__dirname, "../configs/media-favorites.json");
|
||||||
const DEFAULT = {
|
const DEFAULT = {
|
||||||
audios: [],
|
audios: [],
|
||||||
bookmarks: [],
|
bookmarks: [],
|
||||||
|
calendars: [],
|
||||||
|
chats: [],
|
||||||
documents: [],
|
documents: [],
|
||||||
images: [],
|
images: [],
|
||||||
|
maps: [],
|
||||||
|
pads: [],
|
||||||
|
shops: [],
|
||||||
|
torrents: [],
|
||||||
videos: []
|
videos: []
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,223 +0,0 @@
|
||||||
const { execFileSync } = require("child_process");
|
|
||||||
const path = require("path");
|
|
||||||
const fs = require("fs");
|
|
||||||
const crypto = require("crypto");
|
|
||||||
|
|
||||||
const BASE_MAP = path.join(__dirname, "..", "client", "assets", "images", "worldmap-z2.png");
|
|
||||||
const CACHE_DIR = path.join(__dirname, "cache");
|
|
||||||
const TILES_DIR = path.join(__dirname, "tiles");
|
|
||||||
const MAP_W = 1024;
|
|
||||||
const MAP_H = 1024;
|
|
||||||
|
|
||||||
const latLngToPx = (lat, lng) => {
|
|
||||||
const latRad = lat * Math.PI / 180;
|
|
||||||
const x = Math.round((lng + 180) / 360 * MAP_W);
|
|
||||||
const y = Math.round((1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2 * MAP_H);
|
|
||||||
return { x: Math.max(12, Math.min(MAP_W - 12, x)), y: Math.max(12, Math.min(MAP_H - 12, y)) };
|
|
||||||
};
|
|
||||||
|
|
||||||
const pxToLatLng = (px, py) => {
|
|
||||||
const lng = px / MAP_W * 360 - 180;
|
|
||||||
const n = Math.PI - 2 * Math.PI * py / MAP_H;
|
|
||||||
const lat = 180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
|
|
||||||
return { lat: Math.round(lat * 100) / 100, lng: Math.round(lng * 100) / 100 };
|
|
||||||
};
|
|
||||||
|
|
||||||
const getMaxTileZoom = () => {
|
|
||||||
try {
|
|
||||||
const dirs = fs.readdirSync(TILES_DIR).filter(d => /^\d+$/.test(d) && fs.existsSync(path.join(TILES_DIR, d, '0_0.png')));
|
|
||||||
return dirs.length ? Math.max(...dirs.map(Number)) : 0;
|
|
||||||
} catch (_) { return 0; }
|
|
||||||
};
|
|
||||||
|
|
||||||
const getViewportBounds = (centerLat, centerLng, zoom) => {
|
|
||||||
const effectiveZ = Math.min(zoom, getMaxTileZoom());
|
|
||||||
const n = Math.pow(2, effectiveZ);
|
|
||||||
const tileSize = 256;
|
|
||||||
const worldPx = n * tileSize;
|
|
||||||
const scale = Math.pow(2, zoom - effectiveZ);
|
|
||||||
const vw = MAP_W / scale;
|
|
||||||
const vh = MAP_H / scale;
|
|
||||||
|
|
||||||
const latRad = centerLat * Math.PI / 180;
|
|
||||||
const cx = (centerLng + 180) / 360 * worldPx;
|
|
||||||
const cy = (1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2 * worldPx;
|
|
||||||
|
|
||||||
const x0 = cx - vw / 2;
|
|
||||||
const y0 = cy - vh / 2;
|
|
||||||
const x1 = cx + vw / 2;
|
|
||||||
const y1 = cy + vh / 2;
|
|
||||||
|
|
||||||
const pxToLng = (px) => px / worldPx * 360 - 180;
|
|
||||||
const pxToLat = (py) => { const nn = Math.PI - 2 * Math.PI * py / worldPx; return 180 / Math.PI * Math.atan(0.5 * (Math.exp(nn) - Math.exp(-nn))); };
|
|
||||||
|
|
||||||
return {
|
|
||||||
latMin: Math.max(-85, pxToLat(Math.min(y1, worldPx - 1))),
|
|
||||||
latMax: Math.min(85, pxToLat(Math.max(y0, 0))),
|
|
||||||
lngMin: Math.max(-180, pxToLng(Math.max(x0, 0))),
|
|
||||||
lngMax: Math.min(180, pxToLng(Math.min(x1, worldPx - 1)))
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderMapWithPins = (markers, mainIdx) => {
|
|
||||||
const pins = (Array.isArray(markers) ? markers : [])
|
|
||||||
.filter((m) => m && typeof m.lat === "number" && typeof m.lng === "number")
|
|
||||||
.map((m, i) => ({ ...latLngToPx(m.lat, m.lng), main: i === (mainIdx || 0) }));
|
|
||||||
|
|
||||||
const hash = crypto.createHash("md5")
|
|
||||||
.update(pins.map((p) => `${p.x},${p.y},${p.main}`).join(";"))
|
|
||||||
.digest("hex")
|
|
||||||
.slice(0, 12);
|
|
||||||
|
|
||||||
const outFile = path.join(CACHE_DIR, `map_${hash}.png`);
|
|
||||||
|
|
||||||
if (fs.existsSync(outFile)) return `map_${hash}.png`;
|
|
||||||
|
|
||||||
const script = `
|
|
||||||
from PIL import Image, ImageDraw
|
|
||||||
import sys, json
|
|
||||||
|
|
||||||
pins = json.loads(sys.argv[1])
|
|
||||||
im = Image.open(sys.argv[2]).copy()
|
|
||||||
draw = ImageDraw.Draw(im)
|
|
||||||
|
|
||||||
for p in pins:
|
|
||||||
x, y, main = p['x'], p['y'], p.get('main', False)
|
|
||||||
sw = 3 if main else 2
|
|
||||||
sh = 18 if main else 13
|
|
||||||
clr = '#e74c3c' if main else '#3498db'
|
|
||||||
dark = '#c0392b' if main else '#2980b9'
|
|
||||||
draw.polygon([(x, y + 2), (x - sw, y - sh + sw * 2), (x + sw, y - sh + sw * 2)], fill=clr)
|
|
||||||
draw.ellipse([x - sw - 1, y - sh - sw, x + sw + 1, y - sh + sw], fill=dark, outline='white', width=1)
|
|
||||||
|
|
||||||
im.save(sys.argv[3], optimize=True)
|
|
||||||
`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
||||||
execFileSync("python3", [
|
|
||||||
"-c", script,
|
|
||||||
JSON.stringify(pins),
|
|
||||||
BASE_MAP,
|
|
||||||
outFile
|
|
||||||
], { timeout: 10000 });
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `map_${hash}.png`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderZoomedMapWithPins = (centerLat, centerLng, zoom, markers, mainIdx) => {
|
|
||||||
const maxZ = getMaxTileZoom();
|
|
||||||
if (!maxZ || zoom <= 2) return renderMapWithPins(markers, mainIdx);
|
|
||||||
|
|
||||||
const effectiveZ = Math.min(zoom, maxZ);
|
|
||||||
const scale = Math.pow(2, zoom - effectiveZ);
|
|
||||||
const n = Math.pow(2, effectiveZ);
|
|
||||||
const tileSize = 256;
|
|
||||||
const worldPx = n * tileSize;
|
|
||||||
|
|
||||||
const latRad = centerLat * Math.PI / 180;
|
|
||||||
const cx = (centerLng + 180) / 360 * worldPx;
|
|
||||||
const cy = (1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2 * worldPx;
|
|
||||||
|
|
||||||
const vw = MAP_W / scale;
|
|
||||||
const vh = MAP_H / scale;
|
|
||||||
const x0 = cx - vw / 2;
|
|
||||||
const y0 = cy - vh / 2;
|
|
||||||
|
|
||||||
const pinData = (Array.isArray(markers) ? markers : [])
|
|
||||||
.filter((m) => m && typeof m.lat === "number" && typeof m.lng === "number")
|
|
||||||
.map((m, i) => {
|
|
||||||
const latR = m.lat * Math.PI / 180;
|
|
||||||
const wx = (m.lng + 180) / 360 * worldPx;
|
|
||||||
const wy = (1 - Math.log(Math.tan(latR) + 1 / Math.cos(latR)) / Math.PI) / 2 * worldPx;
|
|
||||||
return { px: (wx - x0) * scale, py: (wy - y0) * scale, main: i === (mainIdx || 0) };
|
|
||||||
});
|
|
||||||
|
|
||||||
const hashInput = `z${zoom}_${Math.round(centerLat * 100)}_${Math.round(centerLng * 100)}_` + pinData.map(p => `${Math.round(p.px)},${Math.round(p.py)},${p.main}`).join(";");
|
|
||||||
const hash = crypto.createHash("md5").update(hashInput).digest("hex").slice(0, 12);
|
|
||||||
const outFile = path.join(CACHE_DIR, `map_${hash}.png`);
|
|
||||||
|
|
||||||
if (fs.existsSync(outFile)) return `map_${hash}.png`;
|
|
||||||
|
|
||||||
const txMin = Math.max(0, Math.floor(x0 / tileSize));
|
|
||||||
const txMax = Math.min(n - 1, Math.floor((x0 + vw) / tileSize));
|
|
||||||
const tyMin = Math.max(0, Math.floor(y0 / tileSize));
|
|
||||||
const tyMax = Math.min(n - 1, Math.floor((y0 + vh) / tileSize));
|
|
||||||
|
|
||||||
const tiles = [];
|
|
||||||
for (let tx = txMin; tx <= txMax; tx++) {
|
|
||||||
for (let ty = tyMin; ty <= tyMax; ty++) {
|
|
||||||
const tp = path.join(TILES_DIR, String(effectiveZ), `${tx}_${ty}.png`);
|
|
||||||
tiles.push({ tx, ty, tp, exists: fs.existsSync(tp) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const script = `
|
|
||||||
from PIL import Image, ImageDraw
|
|
||||||
import sys, json, os
|
|
||||||
|
|
||||||
args = json.loads(sys.argv[1])
|
|
||||||
out_file = sys.argv[2]
|
|
||||||
|
|
||||||
tile_size = 256
|
|
||||||
tx_min = args['txMin']
|
|
||||||
ty_min = args['tyMin']
|
|
||||||
tx_max = args['txMax']
|
|
||||||
ty_max = args['tyMax']
|
|
||||||
x0 = args['x0']
|
|
||||||
y0 = args['y0']
|
|
||||||
vw = args['vw']
|
|
||||||
vh = args['vh']
|
|
||||||
scale = args['scale']
|
|
||||||
pins = args['pins']
|
|
||||||
tiles = args['tiles']
|
|
||||||
|
|
||||||
canvas_w = (tx_max - tx_min + 1) * tile_size
|
|
||||||
canvas_h = (ty_max - ty_min + 1) * tile_size
|
|
||||||
canvas = Image.new('RGB', (canvas_w, canvas_h), (170, 211, 223))
|
|
||||||
|
|
||||||
for t in tiles:
|
|
||||||
if t['exists']:
|
|
||||||
try:
|
|
||||||
tile = Image.open(t['tp']).convert('RGB')
|
|
||||||
canvas.paste(tile, ((t['tx'] - tx_min) * tile_size, (t['ty'] - ty_min) * tile_size))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
crop_x = x0 - tx_min * tile_size
|
|
||||||
crop_y = y0 - ty_min * tile_size
|
|
||||||
cropped = canvas.crop((int(crop_x), int(crop_y), int(crop_x + vw), int(crop_y + vh)))
|
|
||||||
result = cropped.resize((1024, 1024), Image.LANCZOS)
|
|
||||||
|
|
||||||
draw = ImageDraw.Draw(result)
|
|
||||||
for p in pins:
|
|
||||||
px, py, main = p['px'], p['py'], p.get('main', False)
|
|
||||||
if -20 <= px <= 1044 and -20 <= py <= 1044:
|
|
||||||
sw = 3 if main else 2
|
|
||||||
sh = 18 if main else 13
|
|
||||||
clr = '#e74c3c' if main else '#3498db'
|
|
||||||
dark = '#c0392b' if main else '#2980b9'
|
|
||||||
draw.polygon([(px, py + 2), (px - sw, py - sh + sw * 2), (px + sw, py - sh + sw * 2)], fill=clr)
|
|
||||||
draw.ellipse([px - sw - 1, py - sh - sw, px + sw + 1, py - sh + sw], fill=dark, outline='white', width=1)
|
|
||||||
|
|
||||||
result.save(out_file, optimize=True)
|
|
||||||
`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
||||||
execFileSync("python3", [
|
|
||||||
"-c", script,
|
|
||||||
JSON.stringify({ txMin, tyMin, txMax, tyMax, x0, y0, vw, vh, scale, pins: pinData, tiles }),
|
|
||||||
outFile
|
|
||||||
], { timeout: 15000 });
|
|
||||||
} catch (e) {
|
|
||||||
return renderMapWithPins(markers, mainIdx);
|
|
||||||
}
|
|
||||||
|
|
||||||
return `map_${hash}.png`;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = { renderMapWithPins, renderZoomedMapWithPins, getViewportBounds, getMaxTileZoom, latLngToPx, pxToLatLng, MAP_W, MAP_H };
|
|
||||||
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 5 KiB |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 883 B |
|
Before Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 811 B |
|
Before Width: | Height: | Size: 3 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 980 B |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 256 B |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 829 B |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 947 B |
|
Before Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 203 B |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 984 B |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 2 KiB |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 5 KiB |
|
Before Width: | Height: | Size: 688 B |
|
Before Width: | Height: | Size: 237 B |
|
Before Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 904 B |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 560 B |
|
Before Width: | Height: | Size: 348 B |
|
Before Width: | Height: | Size: 973 B |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 902 B |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 592 B |
|
Before Width: | Height: | Size: 516 B |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 478 B |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 748 B |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 4 KiB |
|
Before Width: | Height: | Size: 8 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 775 B |