src/events/messageCreate.ts (view raw)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
import config from "@/config";
import { logger as _logger } from "@/utils/logging";
import vision from "@google-cloud/vision";
import {
type Attachment,
ChannelType,
type Client,
Events,
type Message,
} from "discord.js";
type ImageResult = {
image: Attachment;
isCat: boolean;
};
const logger = _logger.child(["google-cloud", "vision"]);
type ImageResults = Record<string, ImageResult>;
const client = new vision.ImageAnnotatorClient({
projectId: config.data.googleCloud?.projectId,
credentials: {
client_email: config.data.googleCloud?.credentials.clientEmail,
private_key: config.data.googleCloud?.credentials.privateKey,
},
});
async function processImage(
message: Message,
): Promise<ImageResults | undefined> {
if (!config.data.googleCloud) {
logger.warn("gcp vision api not configured");
return;
}
const imageAttachments = message.attachments.filter((attachment) =>
attachment.contentType?.startsWith("image/"),
);
if (imageAttachments.size === 0) return;
const results: ImageResults = {};
for (const [id, attachment] of imageAttachments) {
try {
const response = await fetch(attachment.url);
if (!response.ok) {
logger.error(`failed to download image ${attachment.url}`);
results[id] = { image: attachment, isCat: false };
continue;
}
const buffer = await response.arrayBuffer();
if (client.objectLocalization === undefined) {
logger.warn(
"objectLocalization method not available in Google Cloud Vision client",
);
return;
}
const [result] = await client.objectLocalization({
image: { content: Buffer.from(buffer) },
});
const objects = result.localizedObjectAnnotations || [];
const hasCat = objects.some(
(obj) =>
obj.name?.toLowerCase().includes("cat") && (obj.score || 0) > 0.5,
);
results[id] = { image: attachment, isCat: hasCat };
logger.debug(`image ${attachment.name}: cat detected = ${hasCat}`);
} catch (error) {
logger.error(`error processing image ${attachment.name}:`, error);
results[id] = { image: attachment, isCat: false };
}
}
return results;
}
export default {
event: Events.MessageCreate,
async execute(client: Client, message: Message) {
if (
!message.attachments.some((attachment) =>
attachment.contentType?.startsWith("image/"),
) ||
!config.data.catChannel?.channelId ||
message.author.bot
)
return;
if (message.channel.id !== config.data.catChannel.channelId) return;
const channel = client.channels.cache.get(config.data.catChannel.channelId);
if (
!channel ||
!channel.isTextBased() ||
channel.type !== ChannelType.GuildText
)
return;
if (!config.data.googleCloud) return message.react("😻");
const results = await processImage(message);
if (!results) return;
const allResults = Object.values(results);
const catImages = allResults.filter((r) => r.isCat);
const nonCatImages = allResults.filter((r) => !r.isCat);
if (nonCatImages.length > 0) {
const replyContent = [
"this channel is only for cat images!",
...(allResults.length > 1
? [`i found ${nonCatImages.length} non-cat images:`]
: []),
].join(" ");
await message.reply({
content: replyContent,
});
} else if (catImages.length > 0) {
await message.react("😻");
}
},
};
|