extremely claude-assisted go game based on atproto! working on cleaning up and giving a more unique design, still has a bit of a slop vibe to it.
0
fork

Configure Feed

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

Convert OG image from SVG to PNG format

Social platforms don't support SVG for og:image tags.
Use @resvg/resvg-js to render SVG to PNG on the server.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+308 -2
+294
package-lock.json
··· 18 18 "@atproto/lexicon": "^0.4.0", 19 19 "@atproto/oauth-client-node": "^0.1.0", 20 20 "@atproto/xrpc-server": "^0.6.0", 21 + "@resvg/resvg-js": "^2.6.2", 21 22 "@types/better-sqlite3": "^7.6.0", 22 23 "better-sqlite3": "^11.0.0", 23 24 "dotenv": "^17.2.3", ··· 1164 1165 "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", 1165 1166 "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", 1166 1167 "dev": true 1168 + }, 1169 + "node_modules/@resvg/resvg-js": { 1170 + "version": "2.6.2", 1171 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.6.2.tgz", 1172 + "integrity": "sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==", 1173 + "engines": { 1174 + "node": ">= 10" 1175 + }, 1176 + "optionalDependencies": { 1177 + "@resvg/resvg-js-android-arm-eabi": "2.6.2", 1178 + "@resvg/resvg-js-android-arm64": "2.6.2", 1179 + "@resvg/resvg-js-darwin-arm64": "2.6.2", 1180 + "@resvg/resvg-js-darwin-x64": "2.6.2", 1181 + "@resvg/resvg-js-linux-arm-gnueabihf": "2.6.2", 1182 + "@resvg/resvg-js-linux-arm64-gnu": "2.6.2", 1183 + "@resvg/resvg-js-linux-arm64-musl": "2.6.2", 1184 + "@resvg/resvg-js-linux-x64-gnu": "2.6.2", 1185 + "@resvg/resvg-js-linux-x64-musl": "2.6.2", 1186 + "@resvg/resvg-js-win32-arm64-msvc": "2.6.2", 1187 + "@resvg/resvg-js-win32-ia32-msvc": "2.6.2", 1188 + "@resvg/resvg-js-win32-x64-msvc": "2.6.2" 1189 + } 1190 + }, 1191 + "node_modules/@resvg/resvg-js-android-arm-eabi": { 1192 + "version": "2.6.2", 1193 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.6.2.tgz", 1194 + "integrity": "sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==", 1195 + "cpu": [ 1196 + "arm" 1197 + ], 1198 + "optional": true, 1199 + "os": [ 1200 + "android" 1201 + ], 1202 + "engines": { 1203 + "node": ">= 10" 1204 + } 1205 + }, 1206 + "node_modules/@resvg/resvg-js-android-arm64": { 1207 + "version": "2.6.2", 1208 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.6.2.tgz", 1209 + "integrity": "sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==", 1210 + "cpu": [ 1211 + "arm64" 1212 + ], 1213 + "optional": true, 1214 + "os": [ 1215 + "android" 1216 + ], 1217 + "engines": { 1218 + "node": ">= 10" 1219 + } 1220 + }, 1221 + "node_modules/@resvg/resvg-js-darwin-arm64": { 1222 + "version": "2.6.2", 1223 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.6.2.tgz", 1224 + "integrity": "sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A==", 1225 + "cpu": [ 1226 + "arm64" 1227 + ], 1228 + "optional": true, 1229 + "os": [ 1230 + "darwin" 1231 + ], 1232 + "engines": { 1233 + "node": ">= 10" 1234 + } 1235 + }, 1236 + "node_modules/@resvg/resvg-js-darwin-x64": { 1237 + "version": "2.6.2", 1238 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.6.2.tgz", 1239 + "integrity": "sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==", 1240 + "cpu": [ 1241 + "x64" 1242 + ], 1243 + "optional": true, 1244 + "os": [ 1245 + "darwin" 1246 + ], 1247 + "engines": { 1248 + "node": ">= 10" 1249 + } 1250 + }, 1251 + "node_modules/@resvg/resvg-js-linux-arm-gnueabihf": { 1252 + "version": "2.6.2", 1253 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.6.2.tgz", 1254 + "integrity": "sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==", 1255 + "cpu": [ 1256 + "arm" 1257 + ], 1258 + "optional": true, 1259 + "os": [ 1260 + "linux" 1261 + ], 1262 + "engines": { 1263 + "node": ">= 10" 1264 + } 1265 + }, 1266 + "node_modules/@resvg/resvg-js-linux-arm64-gnu": { 1267 + "version": "2.6.2", 1268 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.6.2.tgz", 1269 + "integrity": "sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==", 1270 + "cpu": [ 1271 + "arm64" 1272 + ], 1273 + "optional": true, 1274 + "os": [ 1275 + "linux" 1276 + ], 1277 + "engines": { 1278 + "node": ">= 10" 1279 + } 1280 + }, 1281 + "node_modules/@resvg/resvg-js-linux-arm64-musl": { 1282 + "version": "2.6.2", 1283 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.6.2.tgz", 1284 + "integrity": "sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==", 1285 + "cpu": [ 1286 + "arm64" 1287 + ], 1288 + "optional": true, 1289 + "os": [ 1290 + "linux" 1291 + ], 1292 + "engines": { 1293 + "node": ">= 10" 1294 + } 1295 + }, 1296 + "node_modules/@resvg/resvg-js-linux-x64-gnu": { 1297 + "version": "2.6.2", 1298 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.6.2.tgz", 1299 + "integrity": "sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==", 1300 + "cpu": [ 1301 + "x64" 1302 + ], 1303 + "optional": true, 1304 + "os": [ 1305 + "linux" 1306 + ], 1307 + "engines": { 1308 + "node": ">= 10" 1309 + } 1310 + }, 1311 + "node_modules/@resvg/resvg-js-linux-x64-musl": { 1312 + "version": "2.6.2", 1313 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.6.2.tgz", 1314 + "integrity": "sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==", 1315 + "cpu": [ 1316 + "x64" 1317 + ], 1318 + "optional": true, 1319 + "os": [ 1320 + "linux" 1321 + ], 1322 + "engines": { 1323 + "node": ">= 10" 1324 + } 1325 + }, 1326 + "node_modules/@resvg/resvg-js-win32-arm64-msvc": { 1327 + "version": "2.6.2", 1328 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.6.2.tgz", 1329 + "integrity": "sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==", 1330 + "cpu": [ 1331 + "arm64" 1332 + ], 1333 + "optional": true, 1334 + "os": [ 1335 + "win32" 1336 + ], 1337 + "engines": { 1338 + "node": ">= 10" 1339 + } 1340 + }, 1341 + "node_modules/@resvg/resvg-js-win32-ia32-msvc": { 1342 + "version": "2.6.2", 1343 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.6.2.tgz", 1344 + "integrity": "sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==", 1345 + "cpu": [ 1346 + "ia32" 1347 + ], 1348 + "optional": true, 1349 + "os": [ 1350 + "win32" 1351 + ], 1352 + "engines": { 1353 + "node": ">= 10" 1354 + } 1355 + }, 1356 + "node_modules/@resvg/resvg-js-win32-x64-msvc": { 1357 + "version": "2.6.2", 1358 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.6.2.tgz", 1359 + "integrity": "sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==", 1360 + "cpu": [ 1361 + "x64" 1362 + ], 1363 + "optional": true, 1364 + "os": [ 1365 + "win32" 1366 + ], 1367 + "engines": { 1368 + "node": ">= 10" 1369 + } 1167 1370 }, 1168 1371 "node_modules/@rollup/rollup-android-arm-eabi": { 1169 1372 "version": "4.57.0", ··· 5040 5243 "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", 5041 5244 "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", 5042 5245 "dev": true 5246 + }, 5247 + "@resvg/resvg-js": { 5248 + "version": "2.6.2", 5249 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.6.2.tgz", 5250 + "integrity": "sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==", 5251 + "requires": { 5252 + "@resvg/resvg-js-android-arm-eabi": "2.6.2", 5253 + "@resvg/resvg-js-android-arm64": "2.6.2", 5254 + "@resvg/resvg-js-darwin-arm64": "2.6.2", 5255 + "@resvg/resvg-js-darwin-x64": "2.6.2", 5256 + "@resvg/resvg-js-linux-arm-gnueabihf": "2.6.2", 5257 + "@resvg/resvg-js-linux-arm64-gnu": "2.6.2", 5258 + "@resvg/resvg-js-linux-arm64-musl": "2.6.2", 5259 + "@resvg/resvg-js-linux-x64-gnu": "2.6.2", 5260 + "@resvg/resvg-js-linux-x64-musl": "2.6.2", 5261 + "@resvg/resvg-js-win32-arm64-msvc": "2.6.2", 5262 + "@resvg/resvg-js-win32-ia32-msvc": "2.6.2", 5263 + "@resvg/resvg-js-win32-x64-msvc": "2.6.2" 5264 + } 5265 + }, 5266 + "@resvg/resvg-js-android-arm-eabi": { 5267 + "version": "2.6.2", 5268 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.6.2.tgz", 5269 + "integrity": "sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==", 5270 + "optional": true 5271 + }, 5272 + "@resvg/resvg-js-android-arm64": { 5273 + "version": "2.6.2", 5274 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.6.2.tgz", 5275 + "integrity": "sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==", 5276 + "optional": true 5277 + }, 5278 + "@resvg/resvg-js-darwin-arm64": { 5279 + "version": "2.6.2", 5280 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.6.2.tgz", 5281 + "integrity": "sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A==", 5282 + "optional": true 5283 + }, 5284 + "@resvg/resvg-js-darwin-x64": { 5285 + "version": "2.6.2", 5286 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.6.2.tgz", 5287 + "integrity": "sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==", 5288 + "optional": true 5289 + }, 5290 + "@resvg/resvg-js-linux-arm-gnueabihf": { 5291 + "version": "2.6.2", 5292 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.6.2.tgz", 5293 + "integrity": "sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==", 5294 + "optional": true 5295 + }, 5296 + "@resvg/resvg-js-linux-arm64-gnu": { 5297 + "version": "2.6.2", 5298 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.6.2.tgz", 5299 + "integrity": "sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==", 5300 + "optional": true 5301 + }, 5302 + "@resvg/resvg-js-linux-arm64-musl": { 5303 + "version": "2.6.2", 5304 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.6.2.tgz", 5305 + "integrity": "sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==", 5306 + "optional": true 5307 + }, 5308 + "@resvg/resvg-js-linux-x64-gnu": { 5309 + "version": "2.6.2", 5310 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.6.2.tgz", 5311 + "integrity": "sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==", 5312 + "optional": true 5313 + }, 5314 + "@resvg/resvg-js-linux-x64-musl": { 5315 + "version": "2.6.2", 5316 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.6.2.tgz", 5317 + "integrity": "sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==", 5318 + "optional": true 5319 + }, 5320 + "@resvg/resvg-js-win32-arm64-msvc": { 5321 + "version": "2.6.2", 5322 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.6.2.tgz", 5323 + "integrity": "sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==", 5324 + "optional": true 5325 + }, 5326 + "@resvg/resvg-js-win32-ia32-msvc": { 5327 + "version": "2.6.2", 5328 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.6.2.tgz", 5329 + "integrity": "sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==", 5330 + "optional": true 5331 + }, 5332 + "@resvg/resvg-js-win32-x64-msvc": { 5333 + "version": "2.6.2", 5334 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.6.2.tgz", 5335 + "integrity": "sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==", 5336 + "optional": true 5043 5337 }, 5044 5338 "@rollup/rollup-android-arm-eabi": { 5045 5339 "version": "4.57.0",
+1
package.json
··· 32 32 "@atproto/lexicon": "^0.4.0", 33 33 "@atproto/oauth-client-node": "^0.1.0", 34 34 "@atproto/xrpc-server": "^0.6.0", 35 + "@resvg/resvg-js": "^2.6.2", 35 36 "@types/better-sqlite3": "^7.6.0", 36 37 "better-sqlite3": "^11.0.0", 37 38 "dotenv": "^17.2.3",
+13 -2
src/routes/og-image/[id]/+server.ts
··· 4 4 import { gameTitle } from '$lib/game-titles'; 5 5 import { resolveDidToHandle, fetchGameActionsFromPds } from '$lib/atproto-client'; 6 6 import tenuki from 'tenuki'; 7 + import { Resvg } from '@resvg/resvg-js'; 7 8 8 9 export const GET: RequestHandler = async ({ params }) => { 9 10 const gameRkey = params.id; ··· 184 185 </svg> 185 186 `.trim(); 186 187 187 - return new Response(svg, { 188 + // Convert SVG to PNG using resvg 189 + const resvg = new Resvg(svg, { 190 + fitTo: { 191 + mode: 'width', 192 + value: 1200, 193 + }, 194 + }); 195 + const pngData = resvg.render(); 196 + const pngBuffer = pngData.asPng(); 197 + 198 + return new Response(pngBuffer, { 188 199 headers: { 189 - 'Content-Type': 'image/svg+xml', 200 + 'Content-Type': 'image/png', 190 201 'Cache-Control': 'public, max-age=300', // 5 min cache for active games 191 202 }, 192 203 });