mirror of
https://github.com/artiemis/artemis.js.git
synced 2026-02-14 10:21:54 +00:00
yandex ocr + error handling fixes
This commit is contained in:
parent
3e2d9634a9
commit
1357417efe
19
bun.lock
19
bun.lock
@ -8,6 +8,7 @@
|
||||
"@sapphire/discord.js-utilities": "^7.3.2",
|
||||
"cheerio": "^1.0.0",
|
||||
"discord.js": "^14.17.3",
|
||||
"file-type": "^20.1.0",
|
||||
"ky": "^1.7.4",
|
||||
"lru-cache": "^11.0.2",
|
||||
"nanoid": "^5.0.9",
|
||||
@ -88,6 +89,10 @@
|
||||
|
||||
"@sapphire/utilities": ["@sapphire/utilities@3.18.2", "", {}, "sha512-QGLdC9+pT74Zd7aaObqn0EUfq40c4dyTL65pFnkM6WO1QYN7Yg/s4CdH+CXmx0Zcu6wcfCWILSftXPMosJHP5A=="],
|
||||
|
||||
"@tokenizer/inflate": ["@tokenizer/inflate@0.2.6", "", { "dependencies": { "debug": "^4.3.7", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-SdR/i05U7Xhnsq36iyIq/ZiGGw4PKzw4ww3bOq80Pjj4wyXpqyTcgrgdDdGlcatnlvzNJx8CQw3hp6QZvkUwhA=="],
|
||||
|
||||
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.2.2", "", { "dependencies": { "bun-types": "1.2.2" } }, "sha512-tr74gdku+AEDN5ergNiBnplr7hpDp3V1h7fqI2GcR/rsUaM39jpSeKH0TFibRvU0KwniRx5POgaYnaXbk0hU+w=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="],
|
||||
@ -218,8 +223,12 @@
|
||||
|
||||
"fecha": ["fecha@4.2.3", "", {}, "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="],
|
||||
|
||||
"fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="],
|
||||
|
||||
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
|
||||
|
||||
"file-type": ["file-type@20.1.0", "", { "dependencies": { "@tokenizer/inflate": "^0.2.6", "strtok3": "^10.2.0", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-XoxU+lETfCf+bYK3SXkxFusAvmtYQl1u/ZC4zw1DBLEsHUvh339uwYucgQnnSMz1mRCWYJrCzsbJJ95hsQbZ8A=="],
|
||||
|
||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||
|
||||
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
|
||||
@ -242,6 +251,8 @@
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
||||
|
||||
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||
@ -326,6 +337,8 @@
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"peek-readable": ["peek-readable@6.1.1", "", {}, "sha512-7QmvgRKhxM0E2PGV4ocfROItVode+ELI27n4q+lpufZ+tRKBu/pBP8WOmw9HXn2ui/AUizqtvaVQhcJrOkRqYg=="],
|
||||
|
||||
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||
@ -362,12 +375,16 @@
|
||||
|
||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||
|
||||
"strtok3": ["strtok3@10.2.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^6.1.1" } }, "sha512-Q2dTnW3UXokAvXmXvrvMoUj/me3LyJI76HNHeuGMh2o0As/vzd7eHV3ncLOyvu928vQIDbE7Vf9ldEnC7cwy1w=="],
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"text-hex": ["text-hex@1.0.0", "", {}, "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"token-types": ["token-types@6.0.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA=="],
|
||||
|
||||
"triple-beam": ["triple-beam@1.4.1", "", {}, "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg=="],
|
||||
|
||||
"ts-api-utils": ["ts-api-utils@2.0.1", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w=="],
|
||||
@ -382,6 +399,8 @@
|
||||
|
||||
"typescript-eslint": ["typescript-eslint@8.23.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.23.0", "@typescript-eslint/parser": "8.23.0", "@typescript-eslint/utils": "8.23.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-/LBRo3HrXr5LxmrdYSOCvoAMm7p2jNizNfbIpCgvG4HMsnoprRUOce/+8VJ9BDYWW68rqIENE/haVLWPeFZBVQ=="],
|
||||
|
||||
"uint8array-extras": ["uint8array-extras@1.4.0", "", {}, "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ=="],
|
||||
|
||||
"undici": ["undici@6.19.8", "", {}, "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g=="],
|
||||
|
||||
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
"@sapphire/discord.js-utilities": "^7.3.2",
|
||||
"cheerio": "^1.0.0",
|
||||
"discord.js": "^14.17.3",
|
||||
"file-type": "^20.1.0",
|
||||
"ky": "^1.7.4",
|
||||
"lru-cache": "^11.0.2",
|
||||
"nanoid": "^5.0.9",
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
import { env } from "./env";
|
||||
import { API } from "@discordjs/core";
|
||||
import { REST } from "discord.js";
|
||||
|
||||
const rest = new REST().setToken(env.DISCORD_TOKEN);
|
||||
export const api = new API(rest);
|
||||
@ -1,7 +1,6 @@
|
||||
import { Client, Collection, GatewayIntentBits } from "discord.js";
|
||||
import { Client, Collection, GatewayIntentBits, REST } from "discord.js";
|
||||
import { env } from "./env";
|
||||
import { ActivityType, API } from "@discordjs/core";
|
||||
import { api } from "./api";
|
||||
import type { Command } from "./types/command";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs/promises";
|
||||
@ -31,7 +30,8 @@ export class ArtemisClient extends Client {
|
||||
},
|
||||
});
|
||||
|
||||
this.api = api;
|
||||
const rest = new REST().setToken(env.DISCORD_TOKEN);
|
||||
this.api = new API(rest);
|
||||
|
||||
this.on("error", (err) => {
|
||||
log.error("Unhandled Client Error", err);
|
||||
|
||||
@ -4,6 +4,7 @@ import { getDefinitions, getSuggestions } from "../../utils/wiktionary";
|
||||
import { stripHtml } from "../../utils/functions";
|
||||
import { PaginatedMessage } from "@sapphire/discord.js-utilities";
|
||||
import { LRUCache } from "lru-cache";
|
||||
import { abort } from "../../utils/error";
|
||||
|
||||
const titleCache = new LRUCache<string, string>({ max: 100 });
|
||||
|
||||
@ -51,10 +52,7 @@ export default defineCommand({
|
||||
|
||||
const definitions = await getDefinitions(term);
|
||||
if (!definitions?.length) {
|
||||
await interaction.reply({
|
||||
content: "No definitions found",
|
||||
});
|
||||
return;
|
||||
abort("No definitions found");
|
||||
}
|
||||
|
||||
const title = titleCache.get(term) ?? term;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { MessageFlags, SlashCommandBuilder } from "discord.js";
|
||||
import { client } from "../../client";
|
||||
import { defineCommand } from "..";
|
||||
import { abort } from "../../utils/error";
|
||||
|
||||
export default defineCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
@ -13,10 +14,7 @@ export default defineCommand({
|
||||
const counts = await client.syncCommands();
|
||||
|
||||
if (!counts) {
|
||||
await interaction.followUp({
|
||||
content: "No commands to sync",
|
||||
});
|
||||
return;
|
||||
abort("No commands to sync");
|
||||
}
|
||||
|
||||
const { guildCount, globalCount } = counts;
|
||||
|
||||
61
src/commands/utility/ocr.ts
Normal file
61
src/commands/utility/ocr.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { codeBlock, inlineCode, SlashCommandBuilder } from "discord.js";
|
||||
import { defineCommand } from "..";
|
||||
import { downloadFile } from "../../utils/http";
|
||||
import { abort } from "../../utils/error";
|
||||
import { yandexOcr } from "../../utils/api";
|
||||
|
||||
export default defineCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("ocr")
|
||||
.setDescription("OCR an image using Yandex")
|
||||
.addAttachmentOption((option) =>
|
||||
option
|
||||
.setName("image")
|
||||
.setDescription("The image to OCR")
|
||||
.setRequired(true)
|
||||
),
|
||||
|
||||
async execute(interaction) {
|
||||
const attachment = interaction.options.getAttachment("image", true);
|
||||
if (!attachment.contentType?.startsWith("image/")) {
|
||||
abort("The file must be an image!");
|
||||
}
|
||||
|
||||
const { data, type } = await downloadFile(attachment.url);
|
||||
if (!type?.mime.startsWith("image/")) {
|
||||
abort("The file must be an image!");
|
||||
}
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
const { text, detected_lang } = await yandexOcr(data, type.mime);
|
||||
|
||||
const languageName =
|
||||
new Intl.DisplayNames(["en"], { type: "language" }).of(
|
||||
detected_lang ?? "unknown"
|
||||
) ?? "unknown";
|
||||
|
||||
const content = `Detected language: ${inlineCode(
|
||||
languageName
|
||||
)}\n${codeBlock(text)}`;
|
||||
|
||||
if (content.length > 2000) {
|
||||
await interaction.editReply({
|
||||
content: `Detected language: ${inlineCode(languageName)}`,
|
||||
files: [
|
||||
{
|
||||
name: "ocr.txt",
|
||||
attachment: text,
|
||||
},
|
||||
attachment,
|
||||
],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.editReply({
|
||||
content,
|
||||
files: [attachment],
|
||||
});
|
||||
},
|
||||
});
|
||||
@ -1,6 +1,7 @@
|
||||
import { inlineCode, SlashCommandBuilder } from "discord.js";
|
||||
import { client } from "../../client";
|
||||
import { defineCommand } from "..";
|
||||
import { abort } from "../../utils/error";
|
||||
|
||||
export default defineCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
@ -9,10 +10,9 @@ export default defineCommand({
|
||||
|
||||
async execute(interaction) {
|
||||
if (client.ws.ping < 1) {
|
||||
await interaction.reply(
|
||||
abort(
|
||||
":ping_pong: Pong!\nThe bot is still starting up, accurate latency will be available shortly."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const msg = (
|
||||
|
||||
@ -10,6 +10,8 @@ const envSchema = z.object({
|
||||
.default("development"),
|
||||
DEV_GUILD_ID: z.string(),
|
||||
DEV_CHANNEL_ID: z.string(),
|
||||
API_URL: z.string(),
|
||||
API_TOKEN: z.string(),
|
||||
});
|
||||
|
||||
export const env = envSchema.parse(process.env);
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
import { client } from "../client";
|
||||
import { log } from "../utils/logger";
|
||||
import { defineEvent } from ".";
|
||||
import { isCommandError, notifyError } from "../utils/error";
|
||||
import { isExplicitCommandError, notifyError } from "../utils/error";
|
||||
import { nanoid } from "../utils/functions";
|
||||
|
||||
const running = new Map<string, number>();
|
||||
@ -54,11 +54,11 @@ async function handleChatInputCommand(
|
||||
try {
|
||||
await command.execute(interaction);
|
||||
} catch (err) {
|
||||
let content = isCommandError(err)
|
||||
let content = isExplicitCommandError(err)
|
||||
? err.message
|
||||
: "An unknown error occurred!";
|
||||
|
||||
if (!isCommandError(err)) {
|
||||
if (!isExplicitCommandError(err)) {
|
||||
const trace = nanoid();
|
||||
content += `\ntrace: ${inlineCode(trace)}`;
|
||||
log.error("Unhandled Command Error", { trace, err });
|
||||
|
||||
26
src/utils/api.ts
Normal file
26
src/utils/api.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import ky from "ky";
|
||||
import { env } from "../env";
|
||||
import { version } from "discord.js";
|
||||
|
||||
type OCRResult = {
|
||||
text: string;
|
||||
detected_lang?: string;
|
||||
};
|
||||
|
||||
const client = ky.create({
|
||||
prefixUrl: env.API_URL,
|
||||
headers: {
|
||||
"User-Agent": `artemis (discord.js v${version})`,
|
||||
Authorization: `Bearer ${env.API_TOKEN}`,
|
||||
},
|
||||
});
|
||||
|
||||
export async function yandexOcr(image: Buffer, mime: string) {
|
||||
const res = await client.post("ocr/yandex", {
|
||||
json: {
|
||||
file: image.toString("base64"),
|
||||
mime,
|
||||
},
|
||||
});
|
||||
return res.json<OCRResult>();
|
||||
}
|
||||
@ -2,10 +2,16 @@ import { codeBlock, type TextChannel } from "discord.js";
|
||||
import { client } from "../client";
|
||||
import { env } from "../env";
|
||||
|
||||
export class CommandError extends Error {}
|
||||
export class ExplicitCommandError extends Error {}
|
||||
|
||||
export function isCommandError(error: any): error is CommandError {
|
||||
return error instanceof CommandError;
|
||||
export function abort(message: string): never {
|
||||
throw new ExplicitCommandError(message);
|
||||
}
|
||||
|
||||
export function isExplicitCommandError(
|
||||
error: any
|
||||
): error is ExplicitCommandError {
|
||||
return error instanceof ExplicitCommandError;
|
||||
}
|
||||
|
||||
export async function notifyError(trace: string, error: any) {
|
||||
|
||||
8
src/utils/http.ts
Normal file
8
src/utils/http.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { fileTypeFromBuffer } from "file-type";
|
||||
|
||||
export async function downloadFile(url: string) {
|
||||
const res = await fetch(url);
|
||||
const data = Buffer.from(await res.arrayBuffer());
|
||||
const type = await fileTypeFromBuffer(data);
|
||||
return { data, type };
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user