A Deno-powered backend service for Plants vs. Zombies: MODDED. [Read-only GitHub mirror] docs.pvzm.net
express typescript expressjs plant deno jspvz pvzm game online backend plants-vs-zombies zombie javascript plants modded vs plantsvszombies openapi pvz noads
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

0.6.2 - 🛠️ Cache game images used for thumbnail rendering - ⭐ Add `GAME_URL_SECRET` to bypass WAF/bot protection on game asset requests

Clay 2bae40ce d9fb2c91

+34 -10
+1
.env.example
··· 11 11 USE_TEST_UI=true # enable access to test interface (/index.html) 12 12 USE_ADMIN_UI=true # enable access to admin interface (/admin.html) 13 13 GAME_URL=https://pvzm.net # url to the pvzm game, used in share links 14 + GAME_URL_SECRET=abcdefghijklmnopqrstuvwxyz0123456789 # secret appended to game URL requests to bypass WAF/bot protection. generate with openssl rand --hex 32 14 15 BACKEND_URL=https://backend.pvzm.net # url to this backend, used in links for admins 15 16 16 17 # CORS CONFIGURATION
+6 -1
CHANGELOG.md
··· 1 1 # Changelog 2 2 3 + ## 0.6.2 4 + 5 + - 🛠️ Cache game images used for thumbnail rendering 6 + - ⭐ Add `GAME_URL_SECRET` to bypass WAF/bot protection on game asset requests 7 + 3 8 ## 0.6.1 4 9 5 - - 🛠️ Switched from `jsr:@gfx/canvas` to `npm:@napi-rs/canvas` for compatibility with aarch64 10 + - 🛠️ Switch from `jsr:@gfx/canvas` to `npm:@napi-rs/canvas` for compatibility with AArch64 6 11 7 12 ## **0.6.0** 8 13
+2 -1
README.md
··· 1 - # PVZM Backend ![v0.6.1](https://img.shields.io/badge/version-v0.6.1-darklime) 1 + # PVZM Backend ![v0.6.2](https://img.shields.io/badge/version-v0.6.2-darklime) 2 2 3 3 > A Deno-powered backend service for [Plants vs. Zombies: MODDED](https://github.com/roblnet13/pvz). This service provides APIs for uploading, downloading, listing, favoriting, and reporting user-created _I, Zombie_ levels. 4 4 ··· 73 73 | USE_TEST_UI | Enable test UI route (`/index.html`) | true | 74 74 | USE_ADMIN_UI | Enable admin UI route (`/admin.html`) | true | 75 75 | GAME_URL | Game URL used in generated links (reports/uploads) | <https://pvzm.net> | 76 + | GAME_URL_SECRET | Secret appended to game URL requests to bypass WAF/bot protection | | 76 77 | BACKEND_URL | Backend URL used in generated links (reports/uploads) | <https://backend.pvzm.net> | 77 78 | CORS_ENABLED | Enable CORS | true | 78 79 | ALLOWED_ORIGINS | Comma-separated list of allowed origins (no spaces) | `https://pvzm.net,https://backend.pvzm.net` |
+1 -1
deno.json
··· 1 1 { 2 - "version": "0.6.1", 2 + "version": "0.6.2", 3 3 "tasks": { 4 4 "dev": "deno run --watch -P=dev --env-file=.env main.ts", 5 5 "start": "deno run -P --env-file=.env main.ts",
+3
modules/config.ts
··· 31 31 blueskyPds: string; 32 32 33 33 gameUrl: string; 34 + gameUrlSecret: string; 34 35 backendUrl: string; 35 36 36 37 dbPath: string; ··· 92 93 const blueskyPds = Deno.env.get("BLUESKY_PDS") || "https://bsky.social"; 93 94 94 95 const gameUrl = Deno.env.get("GAME_URL") || "https://pvzm.net"; 96 + const gameUrlSecret = Deno.env.get("GAME_URL_SECRET") || ""; 95 97 const backendUrl = Deno.env.get("BACKEND_URL") || "https://backend.pvzm.net"; 96 98 97 99 const dbPath = Deno.env.get("DB_PATH") || "./database.db"; ··· 143 145 blueskyPds, 144 146 145 147 gameUrl, 148 + gameUrlSecret, 146 149 backendUrl, 147 150 148 151 dbPath,
+3 -1
modules/plantImages.ts
··· 12 12 13 13 export async function getPlantImages(config: ServerConfig): Promise<{ [key: string]: PlantData }> { 14 14 const gameUrl = config.gameUrl.endsWith("/") ? config.gameUrl.slice(0, -1) : config.gameUrl; 15 + const secret = config.gameUrlSecret; 16 + const suffix = secret ? `?secret=${encodeURIComponent(secret)}` : ""; 15 17 16 18 // snapshot existing keys so we can clean up after import 17 19 const keysBefore = new Set(Object.keys(g)); ··· 45 47 46 48 try { 47 49 // dynamic import resolves all plant file imports via the game URL 48 - await import(`${gameUrl}/game/js/CPlants.js`); 50 + await import(`${gameUrl}/game/js/CPlants.js${suffix}`); 49 51 50 52 // CPlants.js sets window[plantName] for each plant. 51 53 // In Deno, window === globalThis, so they're on g now.
+16 -4
modules/renderThumbnail.ts
··· 1 1 import type { ServerConfig } from "./config.ts"; 2 - import { createCanvas, loadImage } from "@napi-rs/canvas"; 2 + import { createCanvas, loadImage, type Image } from "@napi-rs/canvas"; 3 3 import { izombiePlantsMap } from "./levels_io.ts"; 4 4 import type { PlantData } from "./plantImages.ts"; 5 5 6 6 const PUMPKIN_HEAD_INDEX = izombiePlantsMap.indexOf("oPumpkinHead"); 7 7 8 + const imageCache = new Map<string, Image>(); 9 + 10 + async function cachedLoadImage(url: string): Promise<Image> { 11 + const cached = imageCache.get(url); 12 + if (cached) return cached; 13 + const img = await loadImage(url); 14 + imageCache.set(url, img); 15 + return img; 16 + } 17 + 8 18 export async function renderThumbnailCanvas( 9 19 thumb: number[][], 10 20 isWater: boolean, ··· 12 22 config: ServerConfig 13 23 ): Promise<Uint8Array> { 14 24 const gameUrl = config.gameUrl.endsWith("/") ? config.gameUrl.slice(0, -1) : config.gameUrl; 25 + const secret = config.gameUrlSecret; 26 + const suffix = secret ? `?secret=${encodeURIComponent(secret)}` : ""; 15 27 const baseUrl = `${gameUrl}/game/`; 16 28 17 29 const canvas = createCanvas(900, 600); ··· 19 31 20 32 // draw background 21 33 const bgPath = isWater ? "images/interface/background4.jpg" : "images/interface/background2.jpg"; 22 - const bgImg = await loadImage(`${baseUrl}${bgPath}`); 34 + const bgImg = await cachedLoadImage(`${baseUrl}${bgPath}${suffix}`); 23 35 ctx.drawImage(bgImg, -115, 0, 1400, 600); 24 36 25 37 // sort by zindex (plant[5]) 26 38 thumb.sort((a, b) => a[5] - b[5]); 27 39 28 40 // preload all plant images + shadow 29 - const shadowImg = await loadImage(`${baseUrl}images/interface/plantshadow32.png`); 41 + const shadowImg = await cachedLoadImage(`${baseUrl}images/interface/plantshadow32.png${suffix}`); 30 42 const images = await Promise.all( 31 43 thumb.map((plant) => { 32 44 const plantName = izombiePlantsMap[plant[0]]; 33 45 const data = plantImages[plantName]; 34 46 const src = plant[0] !== PUMPKIN_HEAD_INDEX ? data.PicArr[1] : data.PicArr[8]; 35 - return loadImage(`${baseUrl}${src}`); 47 + return cachedLoadImage(`${baseUrl}${src}${suffix}`); 36 48 }) 37 49 ); 38 50
+2 -2
openapi.yaml
··· 2 2 info: 3 3 title: PVZM Backend API 4 4 description: "API for the Plants vs. Zombies: MODDED level sharing platform. Supports level uploading, downloading, browsing, favoriting, reporting, and admin management." 5 - version: 0.6.1 5 + version: 0.6.2 6 6 contact: 7 7 url: https://pvzm.net 8 8 ··· 45 45 format: date-time 46 46 version: 47 47 type: string 48 - example: 0.6.1 48 + example: 0.6.2 49 49 50 50 /api/config: 51 51 get: