feat: image classification and cursivify

This commit is contained in:
2025-06-28 21:03:32 +02:00
committed by SrIzan10
parent cf97b1b463
commit 86685cb4e6
4 changed files with 86 additions and 7 deletions

View File

@@ -7,8 +7,8 @@
"@napi-rs/canvas": "^0.1.72",
"@prisma/client": "^6.10.1",
"@sern/handler": "^4.0.0",
"@sern/publisher": "^1.1.1",
"discord.js": "latest",
"@sern/publisher": "1.1.2",
"discord.js": "^14.21.0",
"dotenv": "^16.3.1",
"mongodb": "^6.17.0",
"sharp": "^0.34.2",
@@ -185,7 +185,7 @@
"@sern/ioc": ["@sern/ioc@1.1.2", "", {}, "sha512-n84w7n5hB1dl8N6dfSbeYIo0QYORMS1bpG/P7J7GoMNTu8c28EYVZ8uGs3Md9GB09UseOKn3mfv1QBDtRsbb1g=="],
"@sern/publisher": ["@sern/publisher@1.1.4", "", {}, "sha512-2O3GmKTzSdZr2cZhrVRFXg1iB0VwZT3vKfuubtJYLneu7F0j1oU6Db4QjGqloiZvOE4HBdQM1AhLx37ej22lgA=="],
"@sern/publisher": ["@sern/publisher@1.1.2", "", {}, "sha512-1zh99JZykKUhqHhE75ZXfiLsBtf1WI+NnDCojv8UlpnGBEyzO8xyI1X7PNf6cPKRs4W9XqY3PqTJ+hrqzIsMkg=="],
"@types/luxon": ["@types/luxon@3.4.2", "", {}, "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA=="],
@@ -257,7 +257,7 @@
"discord.js": ["discord.js@14.21.0", "", { "dependencies": { "@discordjs/builders": "^1.11.2", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.1", "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.1", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.1", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-U5w41cEmcnSfwKYlLv5RJjB8Joa+QJyRwIJz5i/eg+v2Qvv6EYpCRhN9I2Rlf0900LuqSDg8edakUATrDZQncQ=="],
"dotenv": ["dotenv@16.6.0", "", {}, "sha512-Omf1L8paOy2VJhILjyhrhqwLIdstqm1BvcDPKg4NGAlkwEu9ODyrFbvk8UymUOMCT+HXo31jg1lArIrVAAhuGA=="],
"dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
@@ -423,7 +423,7 @@
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"ws": ["ws@8.18.2", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ=="],
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"yocto-queue": ["yocto-queue@1.2.1", "", {}, "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg=="],

View File

@@ -20,8 +20,8 @@
"@napi-rs/canvas": "^0.1.72",
"@prisma/client": "^6.10.1",
"@sern/handler": "^4.0.0",
"@sern/publisher": "^1.1.1",
"discord.js": "latest",
"@sern/publisher": "1.1.2",
"discord.js": "^14.21.0",
"dotenv": "^16.3.1",
"mongodb": "^6.17.0",
"sharp": "^0.34.2"

View File

@@ -0,0 +1,16 @@
import { commandModule, CommandType } from '@sern/handler';
export default commandModule({
name: 'cursivify',
type: CommandType.CtxMsg,
plugins: [],
execute: async (ctx) => {
await ctx.deferReply()
const trimmedstring = ctx.targetMessage.content.replaceAll('*', '');
if (trimmedstring.length === 0) {
await ctx.editReply('No hay nada que cursivificar!');
} else {
await ctx.editReply(`*${trimmedstring}*`);
}
},
});

View File

@@ -0,0 +1,63 @@
import { commandModule, CommandType } from '@sern/handler';
import { AttachmentBuilder, codeBlock } from 'discord.js';
import { createCanvas, loadImage } from '@napi-rs/canvas';
import sharp from 'sharp';
export default commandModule({
name: 'Clasifica una imagen',
type: CommandType.CtxMsg,
plugins: [],
execute: async (ctx) => {
await ctx.deferReply();
if (ctx.targetMessage.attachments.size === 0)
return ctx.editReply('No hay ninguna imagen para clasificar!');
const image = ctx.targetMessage.attachments.first()!;
if (!image.contentType!.startsWith('image/') && image.contentType !== 'image/gif')
return ctx.editReply('El archivo no es una imagen!');
const imageBuffer = await fetch(image.url).then(async (res) => await res.arrayBuffer());
const compressed = sharp(imageBuffer)
.png({ quality: 70 })
.jpeg({ quality: 70 })
.webp({ quality: 70 })
.tiff({ quality: 70 });
const imageUint8Array = new Uint8Array(await compressed.toBuffer());
const request = await fetch(
`https://api.cloudflare.com/client/v4/accounts/${process.env.CF_AI_ACC}/ai/run/@cf/facebook/detr-resnet-50`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CF_AI_TOKEN}`,
'Content-Type': 'application/octet-stream',
},
body: imageUint8Array,
}
).then(async (res) => await res.json());
if (request.errors.length > 0) {
return ctx.editReply(`Hubo un error! ${codeBlock(JSON.stringify(request.errors))}`);
}
// all canvas stuff, this was fun to make
const imgMetadata = await compressed.metadata();
const canvas = createCanvas(imgMetadata.width!, imgMetadata.height!);
const ctxCanvas = canvas.getContext('2d');
const img = await loadImage(image.url);
ctxCanvas.drawImage(img, 0, 0, imgMetadata.width!, imgMetadata.height!);
ctxCanvas.font = '30px sans-serif';
ctxCanvas.fillStyle = 'red';
ctxCanvas.strokeStyle = 'red';
ctxCanvas.lineWidth = 3;
for (const result of request.result) {
if (result.score < 0.5) continue;
const box = result.box;
ctxCanvas.strokeRect(box.xmin, box.ymin, box.xmax - box.xmin, box.ymax - box.ymin);
ctxCanvas.fillText(result.label, box.xmin, box.ymin - 5);
}
const canvasBuffer = canvas.toBuffer('image/png');
const attachment = new AttachmentBuilder(canvasBuffer, { name: 'generatedImage.png' });
await ctx.editReply({ files: [attachment] });
},
});