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.1 - 🛠️ Switched from `jsr:@gfx/canvas` to `npm:@napi-rs/canvas` for compatibility with aarch64

Clay d9fb2c91 42ef2859

+91 -93
+4
CHANGELOG.md
··· 1 1 # Changelog 2 2 3 + ## 0.6.1 4 + 5 + - 🛠️ Switched from `jsr:@gfx/canvas` to `npm:@napi-rs/canvas` for compatibility with aarch64 6 + 3 7 ## **0.6.0** 4 8 5 9 - ⭐ Add thumbnails to Discord and Bluesky posts for new levels
+1 -1
README.md
··· 1 - # PVZM Backend ![v0.6.0](https://img.shields.io/badge/version-v0.6.0-darklime) 1 + # PVZM Backend ![v0.6.1](https://img.shields.io/badge/version-v0.6.1-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
+6 -4
deno.json
··· 1 1 { 2 - "version": "0.6.0", 2 + "version": "0.6.1", 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", ··· 13 13 "ffi": true, 14 14 "read": true, 15 15 "write": true, 16 - "import": true 16 + "import": true, 17 + "sys": true 17 18 }, 18 19 "dev": { 19 20 "env": true, ··· 21 22 "ffi": true, 22 23 "read": true, 23 24 "write": true, 24 - "import": true 25 + "import": true, 26 + "sys": true 25 27 } 26 28 }, 27 29 "lint": { ··· 32 34 "imports": { 33 35 "@atproto/api": "npm:@atproto/api@^0.18.20", 34 36 "@db/sqlite": "jsr:@db/sqlite@^0.13.0", 35 - "@gfx/canvas": "jsr:@gfx/canvas@^0.5.8", 37 + "@napi-rs/canvas": "npm:@napi-rs/canvas@^0.1.65", 36 38 "@mathis/turnstile-verify": "jsr:@mathis/turnstile-verify@^1.2.0", 37 39 "@msgpack/msgpack": "npm:@msgpack/msgpack@^3.1.3", 38 40 "@openai/openai": "jsr:@openai/openai@^6.17.0",
+76 -76
deno.lock
··· 3 3 "specifiers": { 4 4 "jsr:@db/sqlite@0.13": "0.13.0", 5 5 "jsr:@denosaurs/plug@1": "1.1.0", 6 - "jsr:@denosaurs/plug@1.0.5": "1.0.5", 7 - "jsr:@gfx/canvas@~0.5.8": "0.5.8", 8 6 "jsr:@mathis/turnstile-verify@^1.2.0": "1.2.0", 9 7 "jsr:@openai/openai@^6.17.0": "6.17.0", 10 - "jsr:@std/assert@0.214": "0.214.0", 11 - "jsr:@std/assert@0.217": "0.217.0", 12 - "jsr:@std/encoding@0.214": "0.214.0", 13 - "jsr:@std/encoding@0.217.0": "0.217.0", 14 8 "jsr:@std/encoding@1": "1.0.10", 15 - "jsr:@std/fmt@0.214": "0.214.0", 16 9 "jsr:@std/fmt@1": "1.0.9", 17 - "jsr:@std/fs@0.214": "0.214.0", 18 - "jsr:@std/fs@0.217.0": "0.217.0", 19 10 "jsr:@std/fs@1": "1.0.22", 20 11 "jsr:@std/fs@^1.0.22": "1.0.22", 21 12 "jsr:@std/internal@^1.0.12": "1.0.12", 22 - "jsr:@std/path@0.214": "0.214.0", 23 - "jsr:@std/path@0.217": "0.217.0", 24 - "jsr:@std/path@0.217.0": "0.217.0", 25 13 "jsr:@std/path@1": "1.1.4", 26 14 "jsr:@std/path@1.0": "1.0.9", 27 15 "jsr:@std/path@^1.1.4": "1.1.4", 28 16 "npm:@atproto/api@~0.18.20": "0.18.20", 29 17 "npm:@msgpack/msgpack@^3.1.3": "3.1.3", 18 + "npm:@napi-rs/canvas@~0.1.65": "0.1.91", 30 19 "npm:@types/cors@^2.8.19": "2.8.19", 31 20 "npm:@types/express@^5.0.6": "5.0.6", 32 21 "npm:@types/node@^25.2.0": "25.2.0", ··· 48 37 "@db/sqlite@0.13.0": { 49 38 "integrity": "4545c635e0b3d4ddfdc0f2240f932f24b8ad0178e9c2e3a0f9403e7b18ae2fb5", 50 39 "dependencies": [ 51 - "jsr:@denosaurs/plug@1", 40 + "jsr:@denosaurs/plug", 52 41 "jsr:@std/path@1.0" 53 42 ] 54 43 }, 55 - "@denosaurs/plug@1.0.5": { 56 - "integrity": "04cd988da558adc226202d88c3a434d5fcc08146eaf4baf0cea0c2284b16d2bf", 57 - "dependencies": [ 58 - "jsr:@std/encoding@0.214", 59 - "jsr:@std/fmt@0.214", 60 - "jsr:@std/fs@0.214", 61 - "jsr:@std/path@0.214" 62 - ] 63 - }, 64 44 "@denosaurs/plug@1.1.0": { 65 45 "integrity": "eb2f0b7546c7bca2000d8b0282c54d50d91cf6d75cb26a80df25a6de8c4bc044", 66 46 "dependencies": [ 67 - "jsr:@std/encoding@1", 68 - "jsr:@std/fmt@1", 47 + "jsr:@std/encoding", 48 + "jsr:@std/fmt", 69 49 "jsr:@std/fs@1", 70 50 "jsr:@std/path@1" 71 - ] 72 - }, 73 - "@gfx/canvas@0.5.8": { 74 - "integrity": "a61c80292528e7433d428556b494a0ea496dd8e6abd4a338b8b25fc04e46ea3e", 75 - "dependencies": [ 76 - "jsr:@denosaurs/plug@1", 77 - "jsr:@denosaurs/plug@1.0.5", 78 - "jsr:@std/encoding@0.217.0", 79 - "jsr:@std/fs@0.217.0", 80 - "jsr:@std/path@0.217.0" 81 51 ] 82 52 }, 83 53 "@mathis/turnstile-verify@1.2.0": { ··· 89 59 "npm:zod" 90 60 ] 91 61 }, 92 - "@std/assert@0.214.0": { 93 - "integrity": "55d398de76a9828fd3b1aa653f4dba3eee4c6985d90c514865d2be9bd082b140" 94 - }, 95 - "@std/assert@0.217.0": { 96 - "integrity": "c98e279362ca6982d5285c3b89517b757c1e3477ee9f14eb2fdf80a45aaa9642" 97 - }, 98 - "@std/encoding@0.214.0": { 99 - "integrity": "30a8713e1db22986c7e780555ffd2fefd1d4f9374d734bb41f5970f6c3352af5" 100 - }, 101 - "@std/encoding@0.217.0": { 102 - "integrity": "b03e8ff94c98d6b6a02c02c5cf8e5d203400155516248964fc4559abc04669dc" 103 - }, 104 62 "@std/encoding@1.0.10": { 105 63 "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" 106 - }, 107 - "@std/fmt@0.214.0": { 108 - "integrity": "40382cff88a0783b347b4d69b94cf931ab8e549a733916718cb866c08efac4d4" 109 64 }, 110 65 "@std/fmt@1.0.9": { 111 66 "integrity": "2487343e8899fb2be5d0e3d35013e54477ada198854e52dd05ed0422eddcabe0" 112 67 }, 113 - "@std/fs@0.214.0": { 114 - "integrity": "bc880fea0be120cb1550b1ed7faf92fe071003d83f2456a1e129b39193d85bea", 115 - "dependencies": [ 116 - "jsr:@std/assert@0.214", 117 - "jsr:@std/path@0.214" 118 - ] 119 - }, 120 - "@std/fs@0.217.0": { 121 - "integrity": "0bfff5f3618d68c385b28b4ffbf3a15c98293a0f1186444458b62e0111ce77b2", 122 - "dependencies": [ 123 - "jsr:@std/assert@0.217", 124 - "jsr:@std/path@0.217" 125 - ] 126 - }, 127 68 "@std/fs@1.0.22": { 128 69 "integrity": "de0f277a58a867147a8a01bc1b181d0dfa80bfddba8c9cf2bacd6747bcec9308", 129 70 "dependencies": [ ··· 134 75 "@std/internal@1.0.12": { 135 76 "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" 136 77 }, 137 - "@std/path@0.214.0": { 138 - "integrity": "d5577c0b8d66f7e8e3586d864ebdf178bb326145a3611da5a51c961740300285", 139 - "dependencies": [ 140 - "jsr:@std/assert@0.214" 141 - ] 142 - }, 143 - "@std/path@0.217.0": { 144 - "integrity": "1217cc25534bca9a2f672d7fe7c6f356e4027df400c0e85c0ef3e4343bc67d11", 145 - "dependencies": [ 146 - "jsr:@std/assert@0.217" 147 - ] 148 - }, 149 78 "@std/path@1.0.9": { 150 79 "integrity": "260a49f11edd3db93dd38350bf9cd1b4d1366afa98e81b86167b4e3dd750129e" 151 80 }, ··· 281 210 }, 282 211 "@msgpack/msgpack@3.1.3": { 283 212 "integrity": "sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA==" 213 + }, 214 + "@napi-rs/canvas-android-arm64@0.1.91": { 215 + "integrity": "sha512-SLLzXXgSnfct4zy/BVAfweZQkYkPJsNsJ2e5DOE8DFEHC6PufyUrwb12yqeu2So2IOIDpWJJaDAxKY/xpy6MYQ==", 216 + "os": ["android"], 217 + "cpu": ["arm64"] 218 + }, 219 + "@napi-rs/canvas-darwin-arm64@0.1.91": { 220 + "integrity": "sha512-bzdbCjIjw3iRuVFL+uxdSoMra/l09ydGNX9gsBxO/zg+5nlppscIpj6gg+nL6VNG85zwUarDleIrUJ+FWHvmuA==", 221 + "os": ["darwin"], 222 + "cpu": ["arm64"] 223 + }, 224 + "@napi-rs/canvas-darwin-x64@0.1.91": { 225 + "integrity": "sha512-q3qpkpw0IsG9fAS/dmcGIhCVoNxj8ojbexZKWwz3HwxlEWsLncEQRl4arnxrwbpLc2nTNTyj4WwDn7QR5NDAaA==", 226 + "os": ["darwin"], 227 + "cpu": ["x64"] 228 + }, 229 + "@napi-rs/canvas-linux-arm-gnueabihf@0.1.91": { 230 + "integrity": "sha512-Io3g8wJZVhK8G+Fpg1363BE90pIPqg+ZbeehYNxPWDSzbgwU3xV0l8r/JBzODwC7XHi1RpFEk+xyUTMa2POj6w==", 231 + "os": ["linux"], 232 + "cpu": ["arm"] 233 + }, 234 + "@napi-rs/canvas-linux-arm64-gnu@0.1.91": { 235 + "integrity": "sha512-HBnto+0rxx1bQSl8bCWA9PyBKtlk2z/AI32r3cu4kcNO+M/5SD4b0v1MWBWZyqMQyxFjWgy3ECyDjDKMC6tY1A==", 236 + "os": ["linux"], 237 + "cpu": ["arm64"] 238 + }, 239 + "@napi-rs/canvas-linux-arm64-musl@0.1.91": { 240 + "integrity": "sha512-/eJtVe2Xw9A86I4kwXpxxoNagdGclu12/NSMsfoL8q05QmeRCbfjhg1PJS7ENAuAvaiUiALGrbVfeY1KU1gztQ==", 241 + "os": ["linux"], 242 + "cpu": ["arm64"] 243 + }, 244 + "@napi-rs/canvas-linux-riscv64-gnu@0.1.91": { 245 + "integrity": "sha512-floNK9wQuRWevUhhXRcuis7h0zirdytVxPgkonWO+kQlbvxV7gEUHGUFQyq4n55UHYFwgck1SAfJ1HuXv/+ppQ==", 246 + "os": ["linux"], 247 + "cpu": ["riscv64"] 248 + }, 249 + "@napi-rs/canvas-linux-x64-gnu@0.1.91": { 250 + "integrity": "sha512-c3YDqBdf7KETuZy2AxsHFMsBBX1dWT43yFfWUq+j1IELdgesWtxf/6N7csi3VPf6VA3PmnT9EhMyb+M1wfGtqw==", 251 + "os": ["linux"], 252 + "cpu": ["x64"] 253 + }, 254 + "@napi-rs/canvas-linux-x64-musl@0.1.91": { 255 + "integrity": "sha512-RpZ3RPIwgEcNBHSHSX98adm+4VP8SMT5FN6250s5jQbWpX/XNUX5aLMfAVJS/YnDjS1QlsCgQxFOPU0aCCWgag==", 256 + "os": ["linux"], 257 + "cpu": ["x64"] 258 + }, 259 + "@napi-rs/canvas-win32-arm64-msvc@0.1.91": { 260 + "integrity": "sha512-gF8MBp4X134AgVurxqlCdDA2qO0WaDdi9o6Sd5rWRVXRhWhYQ6wkdEzXNLIrmmros0Tsp2J0hQzx4ej/9O8trQ==", 261 + "os": ["win32"], 262 + "cpu": ["arm64"] 263 + }, 264 + "@napi-rs/canvas-win32-x64-msvc@0.1.91": { 265 + "integrity": "sha512-++gtW9EV/neKI8TshD8WFxzBYALSPag2kFRahIJV+LYsyt5kBn21b1dBhEUDHf7O+wiZmuFCeUa7QKGHnYRZBA==", 266 + "os": ["win32"], 267 + "cpu": ["x64"] 268 + }, 269 + "@napi-rs/canvas@0.1.91": { 270 + "integrity": "sha512-eeIe1GoB74P1B0Nkw6pV8BCQ3hfCfvyYr4BntzlCsnFXzVJiPMDnLeIx3gVB0xQMblHYnjK/0nCLvirEhOjr5g==", 271 + "optionalDependencies": [ 272 + "@napi-rs/canvas-android-arm64", 273 + "@napi-rs/canvas-darwin-arm64", 274 + "@napi-rs/canvas-darwin-x64", 275 + "@napi-rs/canvas-linux-arm-gnueabihf", 276 + "@napi-rs/canvas-linux-arm64-gnu", 277 + "@napi-rs/canvas-linux-arm64-musl", 278 + "@napi-rs/canvas-linux-riscv64-gnu", 279 + "@napi-rs/canvas-linux-x64-gnu", 280 + "@napi-rs/canvas-linux-x64-musl", 281 + "@napi-rs/canvas-win32-arm64-msvc", 282 + "@napi-rs/canvas-win32-x64-msvc" 283 + ] 284 284 }, 285 285 "@posthog/core@1.19.0": { 286 286 "integrity": "sha512-OMcdu5cJcvkle2hw0rpe+1mTOFRlerTHTtZKZFvB8z0hgzbN1WeaGZfGFY5wOq42LVTSxwdUgK1MYERyzG1Epw==", ··· 1000 1000 "workspace": { 1001 1001 "dependencies": [ 1002 1002 "jsr:@db/sqlite@0.13", 1003 - "jsr:@gfx/canvas@~0.5.8", 1004 1003 "jsr:@mathis/turnstile-verify@^1.2.0", 1005 1004 "jsr:@openai/openai@^6.17.0", 1006 1005 "jsr:@std/fs@^1.0.22", 1007 1006 "npm:@atproto/api@~0.18.20", 1008 1007 "npm:@msgpack/msgpack@^3.1.3", 1008 + "npm:@napi-rs/canvas@~0.1.65", 1009 1009 "npm:@types/cors@^2.8.19", 1010 1010 "npm:@types/express@^5.0.6", 1011 1011 "npm:@types/node@^25.2.0",
+2 -10
modules/renderThumbnail.ts
··· 1 1 import type { ServerConfig } from "./config.ts"; 2 - import { createCanvas, Image } from "@gfx/canvas"; 2 + import { createCanvas, loadImage } 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 - 8 - async function loadImage(url: string): Promise<Image> { 9 - const res = await fetch(url); 10 - const buf = await res.arrayBuffer(); 11 - const img = new Image(); 12 - img.src = new Uint8Array(buf); 13 - return img; 14 - } 15 7 16 8 export async function renderThumbnailCanvas( 17 9 thumb: number[][], ··· 67 59 ctx.drawImage(images[i], x, y, w, h); 68 60 }); 69 61 70 - return canvas.encode("png"); 62 + return new Uint8Array(await canvas.encode("png")); 71 63 }
+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.0 5 + version: 0.6.1 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.0 48 + example: 0.6.1 49 49 50 50 /api/config: 51 51 get: