all repos — stealth-developers @ 4c05a70ac07a9ac44c333b0f27e390cf5a326f98

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("😻");
		}
	},
};