Compare commits

..

No commits in common. "5a241ff13c656041f908cfe1520ae6a9293fe3e0" and "07fc0c71f2dbea209235679e7849a344c0101d62" have entirely different histories.

13 changed files with 92 additions and 96 deletions

View File

@ -14,16 +14,12 @@ You can put these in `.env` file or pass them as environment variables.
- `TG_BOT_TOKEN` - Telegram bot token. Get yours from [@BotFather](https://t.me/BotFather). - `TG_BOT_TOKEN` - Telegram bot token. Get yours from [@BotFather](https://t.me/BotFather).
Required. Required.
- `TG_ADMIN_USERNAMES` - Comma separated list of usernames of users that can use admin commands. - `TG_ADMIN_USERNAMES` - Comma separated list of usernames of users that can use admin commands.
- `LOG_LEVEL` - [Log level](https://deno.land/std@0.201.0/log/mod.ts?s=LogLevels). Default: `INFO`.
## Running ## Running
- Start stable diffusion webui: `cd sd-webui`, `./webui.sh --api` - Start stable diffusion webui: `cd sd-webui`, `./webui.sh --api`
- Start bot: `deno task start` - Start bot: `deno task start`
To connect your SD to the bot, open the [Eris UI](http://localhost:5999/), login as admin and add a
worker.
## Codegen ## Codegen
The Stable Diffusion API in `app/sdApi.ts` is auto-generated. To regenerate it, first start your SD The Stable Diffusion API in `app/sdApi.ts` is auto-generated. To regenerate it, first start your SD

View File

@ -1,10 +1,12 @@
import { deepMerge } from "std/collections/deep_merge.ts"; import { deepMerge } from "std/collections/deep_merge.ts";
import { info } from "std/log/mod.ts"; import { getLogger } from "std/log/mod.ts";
import { createEndpoint, createMethodFilter } from "t_rest/server"; import { createEndpoint, createMethodFilter } from "t_rest/server";
import { configSchema, getConfig, setConfig } from "../app/config.ts"; import { configSchema, getConfig, setConfig } from "../app/config.ts";
import { bot } from "../bot/mod.ts"; import { bot } from "../bot/mod.ts";
import { sessions } from "./sessionsRoute.ts"; import { sessions } from "./sessionsRoute.ts";
export const logger = () => getLogger();
export const paramsRoute = createMethodFilter({ export const paramsRoute = createMethodFilter({
GET: createEndpoint( GET: createEndpoint(
{ query: null, body: null }, { query: null, body: null },
@ -36,7 +38,7 @@ export const paramsRoute = createMethodFilter({
if (!config?.adminUsernames?.includes(chat.username)) { if (!config?.adminUsernames?.includes(chat.username)) {
return { status: 403, body: { type: "text/plain", data: "Must be an admin" } }; return { status: 403, body: { type: "text/plain", data: "Must be an admin" } };
} }
info(`User ${chat.username} updated default params: ${JSON.stringify(body.data)}`); logger().info(`User ${chat.username} updated default params: ${JSON.stringify(body.data)}`);
const defaultParams = deepMerge(config.defaultParams ?? {}, body.data); const defaultParams = deepMerge(config.defaultParams ?? {}, body.data);
await setConfig({ defaultParams }); await setConfig({ defaultParams });
return { status: 200, body: { type: "application/json", data: config.defaultParams } }; return { status: 200, body: { type: "application/json", data: config.defaultParams } };

View File

@ -1,16 +1,18 @@
import { subMinutes } from "date-fns";
import { Model } from "indexed_kv";
import createOpenApiFetch from "openapi_fetch";
import { info } from "std/log/mod.ts";
import { createEndpoint, createMethodFilter, createPathFilter } from "t_rest/server"; import { createEndpoint, createMethodFilter, createPathFilter } from "t_rest/server";
import { getConfig } from "../app/config.ts";
import { activeGenerationWorkers } from "../app/generationQueue.ts"; import { activeGenerationWorkers } from "../app/generationQueue.ts";
import { generationStore } from "../app/generationStore.ts"; import { getConfig } from "../app/config.ts";
import * as SdApi from "../app/sdApi.ts"; import * as SdApi from "../app/sdApi.ts";
import { WorkerInstance, workerInstanceStore } from "../app/workerInstanceStore.ts"; import createOpenApiFetch from "openapi_fetch";
import { bot } from "../bot/mod.ts";
import { getAuthHeader } from "../utils/getAuthHeader.ts";
import { sessions } from "./sessionsRoute.ts"; import { sessions } from "./sessionsRoute.ts";
import { bot } from "../bot/mod.ts";
import { getLogger } from "std/log/mod.ts";
import { WorkerInstance, workerInstanceStore } from "../app/workerInstanceStore.ts";
import { getAuthHeader } from "../utils/getAuthHeader.ts";
import { Model } from "indexed_kv";
import { generationStore } from "../app/generationStore.ts";
import { subMinutes } from "date-fns";
const logger = () => getLogger();
export type WorkerData = Omit<WorkerInstance, "sdUrl" | "sdAuth"> & { export type WorkerData = Omit<WorkerInstance, "sdUrl" | "sdAuth"> & {
id: string; id: string;
@ -122,7 +124,7 @@ export const workersRoute = createPathFilter({
sdUrl: body.data.sdUrl, sdUrl: body.data.sdUrl,
sdAuth: body.data.sdAuth, sdAuth: body.data.sdAuth,
}); });
info(`User ${chat.username} created worker ${workerInstance.id}`); logger().info(`User ${chat.username} created worker ${workerInstance.id}`);
const worker = await getWorkerData(workerInstance); const worker = await getWorkerData(workerInstance);
return { return {
status: 200, status: 200,
@ -200,7 +202,7 @@ export const workersRoute = createPathFilter({
if (body.data.auth !== undefined) { if (body.data.auth !== undefined) {
workerInstance.value.sdAuth = body.data.auth; workerInstance.value.sdAuth = body.data.auth;
} }
info( logger().info(
`User ${chat.username} updated worker ${params.workerId}: ${JSON.stringify(body.data)}`, `User ${chat.username} updated worker ${params.workerId}: ${JSON.stringify(body.data)}`,
); );
await workerInstance.update(); await workerInstance.update();
@ -236,7 +238,7 @@ export const workersRoute = createPathFilter({
if (!config?.adminUsernames?.includes(chat.username)) { if (!config?.adminUsernames?.includes(chat.username)) {
return { status: 403, body: { type: "text/plain", data: "Must be an admin" } }; return { status: 403, body: { type: "text/plain", data: "Must be an admin" } };
} }
info(`User ${chat.username} deleted worker ${params.workerId}`); logger().info(`User ${chat.username} deleted worker ${params.workerId}`);
await workerInstance.delete(); await workerInstance.delete();
return { status: 200, body: { type: "application/json", data: null } }; return { status: 200, body: { type: "application/json", data: null } };
}, },

View File

@ -1,11 +1,13 @@
import { UTCDateMini } from "@date-fns/utc"; import { UTCDateMini } from "@date-fns/utc";
import { hoursToMilliseconds, isSameDay, minutesToMilliseconds } from "date-fns"; import { hoursToMilliseconds, isSameDay, minutesToMilliseconds } from "date-fns";
import { info } from "std/log/mod.ts"; import { getLogger } from "std/log/mod.ts";
import { JsonSchema, jsonType } from "t_rest/server"; import { JsonSchema, jsonType } from "t_rest/server";
import { db } from "./db.ts"; import { db } from "./db.ts";
import { generationStore } from "./generationStore.ts"; import { generationStore } from "./generationStore.ts";
import { kvMemoize } from "./kvMemoize.ts"; import { kvMemoize } from "./kvMemoize.ts";
const logger = () => getLogger();
export const dailyStatsSchema = { export const dailyStatsSchema = {
type: "object", type: "object",
properties: { properties: {
@ -34,7 +36,7 @@ export const getDailyStats = kvMemoize(
const after = new Date(Date.UTC(year, month - 1, day)); const after = new Date(Date.UTC(year, month - 1, day));
const before = new Date(Date.UTC(year, month - 1, day + 1)); const before = new Date(Date.UTC(year, month - 1, day + 1));
info(`Calculating daily stats for ${year}-${month}-${day}`); logger().info(`Calculating daily stats for ${year}-${month}-${day}`);
for await ( for await (
const generation of generationStore.listAll({ after, before }) const generation of generationStore.listAll({ after, before })

View File

@ -4,7 +4,7 @@ import { JobData, Queue, Worker } from "kvmq";
import createOpenApiClient from "openapi_fetch"; import createOpenApiClient from "openapi_fetch";
import { delay } from "std/async/delay.ts"; import { delay } from "std/async/delay.ts";
import { decode, encode } from "std/encoding/base64.ts"; import { decode, encode } from "std/encoding/base64.ts";
import { debug, error, info } from "std/log/mod.ts"; import { getLogger } from "std/log/mod.ts";
import { ulid } from "ulid"; import { ulid } from "ulid";
import { bot } from "../bot/mod.ts"; import { bot } from "../bot/mod.ts";
import { PngInfo } from "../bot/parsePngInfo.ts"; import { PngInfo } from "../bot/parsePngInfo.ts";
@ -19,6 +19,8 @@ import * as SdApi from "./sdApi.ts";
import { uploadQueue } from "./uploadQueue.ts"; import { uploadQueue } from "./uploadQueue.ts";
import { workerInstanceStore } from "./workerInstanceStore.ts"; import { workerInstanceStore } from "./workerInstanceStore.ts";
const logger = () => getLogger();
interface GenerationJob { interface GenerationJob {
task: task:
| { | {
@ -71,7 +73,7 @@ export async function processGenerationQueue() {
.catch((error) => { .catch((error) => {
workerInstance.update({ lastError: { message: error.message, time: Date.now() } }) workerInstance.update({ lastError: { message: error.message, time: Date.now() } })
.catch(() => undefined); .catch(() => undefined);
debug(`Worker ${workerInstance.value.key} is down: ${error}`); logger().debug(`Worker ${workerInstance.value.key} is down: ${error}`);
}); });
if (!activeWorkerStatus?.data) { if (!activeWorkerStatus?.data) {
@ -84,7 +86,7 @@ export async function processGenerationQueue() {
}); });
newWorker.addEventListener("error", (e) => { newWorker.addEventListener("error", (e) => {
error( logger().error(
`Generation failed for ${formatUserChat(e.detail.job.state)}: ${e.detail.error}`, `Generation failed for ${formatUserChat(e.detail.job.state)}: ${e.detail.error}`,
); );
bot.api.sendMessage( bot.api.sendMessage(
@ -101,7 +103,7 @@ export async function processGenerationQueue() {
newWorker.stopProcessing(); newWorker.stopProcessing();
workerInstance.update({ lastError: { message: e.detail.error.message, time: Date.now() } }) workerInstance.update({ lastError: { message: e.detail.error.message, time: Date.now() } })
.catch(() => undefined); .catch(() => undefined);
info(`Stopped worker ${workerInstance.value.key}`); logger().info(`Stopped worker ${workerInstance.value.key}`);
}); });
newWorker.addEventListener("complete", () => { newWorker.addEventListener("complete", () => {
@ -111,7 +113,7 @@ export async function processGenerationQueue() {
await workerInstance.update({ lastOnlineTime: Date.now() }); await workerInstance.update({ lastOnlineTime: Date.now() });
newWorker.processJobs(); newWorker.processJobs();
activeGenerationWorkers.set(workerInstance.id, newWorker); activeGenerationWorkers.set(workerInstance.id, newWorker);
info(`Started worker ${workerInstance.value.key}`); logger().info(`Started worker ${workerInstance.value.key}`);
} }
await delay(60_000); await delay(60_000);
} }
@ -137,7 +139,7 @@ async function processGenerationJob(
}); });
state.workerInstanceKey = workerInstance.value.key; state.workerInstanceKey = workerInstance.value.key;
state.progress = 0; state.progress = 0;
debug(`Generation started for ${formatUserChat(state)}`); logger().debug(`Generation started for ${formatUserChat(state)}`);
await updateJob({ state: state }); await updateJob({ state: state });
// check if bot can post messages in this chat // check if bot can post messages in this chat
@ -251,7 +253,7 @@ async function processGenerationJob(
).catch(() => undefined); ).catch(() => undefined);
} }
await Promise.race([delay(2_000), responsePromise]).catch(() => undefined); await Promise.race([delay(1000), responsePromise]).catch(() => undefined);
} while (await promiseState(responsePromise) === "pending"); } while (await promiseState(responsePromise) === "pending");
// check response // check response
@ -296,7 +298,7 @@ async function processGenerationJob(
{ maxAttempts: 1 }, { maxAttempts: 1 },
).catch(() => undefined); ).catch(() => undefined);
debug(`Generation finished for ${formatUserChat(state)}`); logger().debug(`Generation finished for ${formatUserChat(state)}`);
} }
/** /**

View File

@ -4,13 +4,15 @@ import { bold, fmt } from "grammy_parse_mode";
import { Chat, Message, User } from "grammy_types"; import { Chat, Message, User } from "grammy_types";
import { Queue } from "kvmq"; import { Queue } from "kvmq";
import { format } from "std/fmt/duration.ts"; import { format } from "std/fmt/duration.ts";
import { debug, error } from "std/log/mod.ts"; import { getLogger } from "std/log/mod.ts";
import { bot } from "../bot/mod.ts"; import { bot } from "../bot/mod.ts";
import { formatUserChat } from "../utils/formatUserChat.ts"; import { formatUserChat } from "../utils/formatUserChat.ts";
import { db, fs } from "./db.ts"; import { db, fs } from "./db.ts";
import { generationStore, SdGenerationInfo } from "./generationStore.ts"; import { generationStore, SdGenerationInfo } from "./generationStore.ts";
import { globalStats } from "./globalStats.ts"; import { globalStats } from "./globalStats.ts";
const logger = () => getLogger();
interface UploadJob { interface UploadJob {
from: User; from: User;
chat: Chat; chat: Chat;
@ -61,16 +63,12 @@ export async function processUploadQueue() {
]); ]);
// parse files from reply JSON // parse files from reply JSON
let size = 0;
const types = new Set<string>();
const inputFiles = await Promise.all( const inputFiles = await Promise.all(
state.imageKeys.map(async (fileKey, idx) => { state.imageKeys.map(async (fileKey, idx) => {
const imageBuffer = await fs.get(fileKey).then((entry) => entry.value); const imageBuffer = await fs.get(fileKey).then((entry) => entry.value);
if (!imageBuffer) throw new Error("File not found"); if (!imageBuffer) throw new Error("File not found");
const imageType = await fileTypeFromBuffer(imageBuffer); const imageType = await fileTypeFromBuffer(imageBuffer);
if (!imageType) throw new Error("Image has unknown type"); if (!imageType) throw new Error("Image has unknown type");
size += imageBuffer.byteLength;
types.add(imageType.ext);
return InputMediaBuilder.photo( return InputMediaBuilder.photo(
new InputFile(imageBuffer, `image${idx}.${imageType.ext}`), new InputFile(imageBuffer, `image${idx}.${imageType.ext}`),
// if it can fit, add caption for first photo // if it can fit, add caption for first photo
@ -123,19 +121,13 @@ export async function processUploadQueue() {
globalStats.userIds = [...userIdSet]; globalStats.userIds = [...userIdSet];
} }
debug(
`Uploaded ${state.imageKeys.length} ${[...types].join(",")} images (${
Math.trunc(size / 1024)
}kB) for ${formatUserChat(state)}`,
);
// delete the status message // delete the status message
await bot.api.deleteMessage(state.replyMessage.chat.id, state.replyMessage.message_id) await bot.api.deleteMessage(state.replyMessage.chat.id, state.replyMessage.message_id)
.catch(() => undefined); .catch(() => undefined);
}, { concurrency: 3 }); }, { concurrency: 3 });
uploadWorker.addEventListener("error", (e) => { uploadWorker.addEventListener("error", (e) => {
error(`Upload failed for ${formatUserChat(e.detail.job.state)}: ${e.detail.error}`); logger().error(`Upload failed for ${formatUserChat(e.detail.job.state)}: ${e.detail.error}`);
bot.api.sendMessage( bot.api.sendMessage(
e.detail.job.state.requestMessage.chat.id, e.detail.job.state.requestMessage.chat.id,
`Upload failed: ${e.detail.error}\n\n` + `Upload failed: ${e.detail.error}\n\n` +

View File

@ -1,11 +1,13 @@
import { minutesToMilliseconds } from "date-fns"; import { minutesToMilliseconds } from "date-fns";
import { Store } from "indexed_kv"; import { Store } from "indexed_kv";
import { info } from "std/log/mod.ts"; import { getLogger } from "std/log/mod.ts";
import { JsonSchema, jsonType } from "t_rest/server"; import { JsonSchema, jsonType } from "t_rest/server";
import { db } from "./db.ts"; import { db } from "./db.ts";
import { generationStore } from "./generationStore.ts"; import { generationStore } from "./generationStore.ts";
import { kvMemoize } from "./kvMemoize.ts"; import { kvMemoize } from "./kvMemoize.ts";
const logger = () => getLogger();
export const userStatsSchema = { export const userStatsSchema = {
type: "object", type: "object",
properties: { properties: {
@ -61,7 +63,7 @@ export const getUserStats = kvMemoize(
let pixelStepCount = 0; let pixelStepCount = 0;
const tagCountMap: Record<string, number> = {}; const tagCountMap: Record<string, number> = {};
info(`Calculating user stats for ${userId}`); logger().info(`Calculating user stats for ${userId}`);
for await ( for await (
const generation of generationStore.listBy("fromId", { value: userId }) const generation of generationStore.listBy("fromId", { value: userId })

View File

@ -1,11 +1,10 @@
import { CommandContext } from "grammy"; import { CommandContext } from "grammy";
import { bold, fmt, FormattedString } from "grammy_parse_mode"; import { bold, fmt, FormattedString } from "grammy_parse_mode";
import { distinctBy } from "std/collections/distinct_by.ts"; import { distinctBy } from "std/collections/distinct_by.ts";
import { error, info } from "std/log/mod.ts";
import { getConfig } from "../app/config.ts"; import { getConfig } from "../app/config.ts";
import { generationStore } from "../app/generationStore.ts"; import { generationStore } from "../app/generationStore.ts";
import { formatUserChat } from "../utils/formatUserChat.ts"; import { formatUserChat } from "../utils/formatUserChat.ts";
import { ErisContext } from "./mod.ts"; import { ErisContext, logger } from "./mod.ts";
export async function broadcastCommand(ctx: CommandContext<ErisContext>) { export async function broadcastCommand(ctx: CommandContext<ErisContext>) {
if (!ctx.from?.username) { if (!ctx.from?.username) {
@ -47,10 +46,10 @@ export async function broadcastCommand(ctx: CommandContext<ErisContext>) {
for (const gen of gens) { for (const gen of gens) {
try { try {
await ctx.api.sendMessage(gen.value.from.id, text); await ctx.api.sendMessage(gen.value.from.id, text);
info(`Broadcasted to ${formatUserChat({ from: gen.value.from })}`); logger().info(`Broadcasted to ${formatUserChat({ from: gen.value.from })}`);
sentCount++; sentCount++;
} catch (err) { } catch (err) {
error(`Broadcasting to ${formatUserChat({ from: gen.value.from })} failed: ${err}`); logger().error(`Broadcasting to ${formatUserChat({ from: gen.value.from })} failed: ${err}`);
errors.push(fmt`${bold(formatUserChat({ from: gen.value.from }))} - ${err.message}\n`); errors.push(fmt`${bold(formatUserChat({ from: gen.value.from }))} - ${err.message}\n`);
} }
const fmtMessage = getMessage(); const fmtMessage = getMessage();

View File

@ -1,11 +1,10 @@
import { CommandContext } from "grammy"; import { CommandContext } from "grammy";
import { StatelessQuestion } from "grammy_stateless_question"; import { StatelessQuestion } from "grammy_stateless_question";
import { maxBy } from "std/collections/max_by.ts"; import { maxBy } from "std/collections/max_by.ts";
import { debug } from "std/log/mod.ts";
import { getConfig } from "../app/config.ts"; import { getConfig } from "../app/config.ts";
import { generationQueue } from "../app/generationQueue.ts"; import { generationQueue } from "../app/generationQueue.ts";
import { formatUserChat } from "../utils/formatUserChat.ts"; import { formatUserChat } from "../utils/formatUserChat.ts";
import { ErisContext } from "./mod.ts"; import { ErisContext, logger } from "./mod.ts";
import { parsePngInfo, PngInfo } from "./parsePngInfo.ts"; import { parsePngInfo, PngInfo } from "./parsePngInfo.ts";
type QuestionState = { fileId?: string; params?: Partial<PngInfo> }; type QuestionState = { fileId?: string; params?: Partial<PngInfo> };
@ -127,5 +126,5 @@ async function img2img(
replyMessage: replyMessage, replyMessage: replyMessage,
}, { retryCount: 3, repeatDelayMs: 10_000 }); }, { retryCount: 3, repeatDelayMs: 10_000 });
debug(`Generation (img2img) enqueued for ${formatUserChat(ctx.message)}`); logger().debug(`Generation (img2img) enqueued for ${formatUserChat(ctx.message)}`);
} }

View File

@ -2,7 +2,7 @@ import { Api, Bot, Context, RawApi, session, SessionFlavor } from "grammy";
import { FileFlavor, hydrateFiles } from "grammy_files"; import { FileFlavor, hydrateFiles } from "grammy_files";
import { hydrateReply, ParseModeFlavor } from "grammy_parse_mode"; import { hydrateReply, ParseModeFlavor } from "grammy_parse_mode";
import { run, sequentialize } from "grammy_runner"; import { run, sequentialize } from "grammy_runner";
import { error, info, warning } from "std/log/mod.ts"; import { getLogger } from "std/log/mod.ts";
import { sessions } from "../api/sessionsRoute.ts"; import { sessions } from "../api/sessionsRoute.ts";
import { getConfig, setConfig } from "../app/config.ts"; import { getConfig, setConfig } from "../app/config.ts";
import { formatUserChat } from "../utils/formatUserChat.ts"; import { formatUserChat } from "../utils/formatUserChat.ts";
@ -13,6 +13,8 @@ import { pnginfoCommand, pnginfoQuestion } from "./pnginfoCommand.ts";
import { queueCommand } from "./queueCommand.ts"; import { queueCommand } from "./queueCommand.ts";
import { txt2imgCommand, txt2imgQuestion } from "./txt2imgCommand.ts"; import { txt2imgCommand, txt2imgQuestion } from "./txt2imgCommand.ts";
export const logger = () => getLogger();
interface SessionData { interface SessionData {
chat: ErisChatData; chat: ErisChatData;
user: ErisUserData; user: ErisUserData;
@ -75,7 +77,7 @@ bot.api.config.use(async (prev, method, payload, signal) => {
if (attempt >= maxAttempts) return result; if (attempt >= maxAttempts) return result;
const retryAfter = result.parameters?.retry_after ?? (attempt * 5); const retryAfter = result.parameters?.retry_after ?? (attempt * 5);
if (retryAfter > maxWait) return result; if (retryAfter > maxWait) return result;
warning( logger().warning(
`${method} (attempt ${attempt}) failed: ${result.error_code} ${result.description}`, `${method} (attempt ${attempt}) failed: ${result.error_code} ${result.description}`,
); );
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000)); await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
@ -83,7 +85,7 @@ bot.api.config.use(async (prev, method, payload, signal) => {
}); });
bot.catch((err) => { bot.catch((err) => {
error( logger().error(
`Handling update from ${formatUserChat(err.ctx)} failed: ${err.name} ${err.message}`, `Handling update from ${formatUserChat(err.ctx)} failed: ${err.name} ${err.message}`,
); );
}); });
@ -129,7 +131,7 @@ bot.command("start", async (ctx) => {
} }
session.userId = ctx.from?.id; session.userId = ctx.from?.id;
sessions.set(id, session); sessions.set(id, session);
info(`User ${formatUserChat(ctx)} logged in`); logger().info(`User ${formatUserChat(ctx)} logged in`);
// TODO: show link to web ui // TODO: show link to web ui
await ctx.reply("Login successful! You can now return to the WebUI.", { await ctx.reply("Login successful! You can now return to the WebUI.", {
reply_to_message_id: ctx.message?.message_id, reply_to_message_id: ctx.message?.message_id,
@ -167,7 +169,7 @@ bot.command("pause", async (ctx) => {
await setConfig({ await setConfig({
pausedReason: ctx.match || "No reason given", pausedReason: ctx.match || "No reason given",
}); });
warning(`Bot paused by ${ctx.from.first_name} because ${config.pausedReason}`); logger().warning(`Bot paused by ${ctx.from.first_name} because ${config.pausedReason}`);
return ctx.reply("Paused"); return ctx.reply("Paused");
}); });
@ -177,7 +179,7 @@ bot.command("resume", async (ctx) => {
if (!config.adminUsernames.includes(ctx.from.username)) return; if (!config.adminUsernames.includes(ctx.from.username)) return;
if (config.pausedReason == null) return ctx.reply("Already running"); if (config.pausedReason == null) return ctx.reply("Already running");
await setConfig({ pausedReason: null }); await setConfig({ pausedReason: null });
info(`Bot resumed by ${ctx.from.first_name}`); logger().info(`Bot resumed by ${ctx.from.first_name}`);
return ctx.reply("Resumed"); return ctx.reply("Resumed");
}); });

View File

@ -1,10 +1,9 @@
import { CommandContext } from "grammy"; import { CommandContext } from "grammy";
import { StatelessQuestion } from "grammy_stateless_question"; import { StatelessQuestion } from "grammy_stateless_question";
import { debug } from "std/log/mod.ts";
import { getConfig } from "../app/config.ts"; import { getConfig } from "../app/config.ts";
import { generationQueue } from "../app/generationQueue.ts"; import { generationQueue } from "../app/generationQueue.ts";
import { formatUserChat } from "../utils/formatUserChat.ts"; import { formatUserChat } from "../utils/formatUserChat.ts";
import { ErisContext } from "./mod.ts"; import { ErisContext, logger } from "./mod.ts";
import { getPngInfo, parsePngInfo, PngInfo } from "./parsePngInfo.ts"; import { getPngInfo, parsePngInfo, PngInfo } from "./parsePngInfo.ts";
export const txt2imgQuestion = new StatelessQuestion<ErisContext>( export const txt2imgQuestion = new StatelessQuestion<ErisContext>(
@ -92,5 +91,5 @@ async function txt2img(ctx: ErisContext, match: string, includeRepliedTo: boolea
replyMessage: replyMessage, replyMessage: replyMessage,
}, { retryCount: 3, retryDelayMs: 10_000 }); }, { retryCount: 3, retryDelayMs: 10_000 });
debug(`Generation (txt2img) enqueued for ${formatUserChat(ctx.message)}`); logger().debug(`Generation (txt2img) enqueued for ${formatUserChat(ctx.message)}`);
} }

View File

@ -10,45 +10,46 @@
"lineWidth": 100 "lineWidth": 100
}, },
"imports": { "imports": {
"@date-fns/utc": "https://cdn.skypack.dev/@date-fns/utc@1.1.0?dts",
"async": "https://deno.land/x/async@v2.0.2/mod.ts",
"date-fns": "https://cdn.skypack.dev/date-fns@2.30.0?dts",
"file_type": "https://esm.sh/file-type@18.5.0",
"grammy_autoquote": "https://lib.deno.dev/x/grammy_autoquote@1/mod.ts",
"grammy_files": "https://lib.deno.dev/x/grammy_files@1/mod.ts",
"grammy_parse_mode": "https://lib.deno.dev/x/grammy_parse_mode@1/mod.ts",
"grammy_runner": "https://lib.deno.dev/x/grammy_runner@2/mod.ts",
"grammy_stateless_question": "https://lib.deno.dev/x/grammy_stateless_question_alpha@3/mod.ts",
"grammy_types": "https://lib.deno.dev/x/grammy_types@3/mod.ts",
"grammy": "https://lib.deno.dev/x/grammy@1/mod.ts",
"indexed_kv": "https://deno.land/x/indexed_kv@v0.5.0/mod.ts",
"kvfs": "https://deno.land/x/kvfs@v0.1.0/mod.ts",
"kvmq": "https://deno.land/x/kvmq@v0.3.0/mod.ts",
"openapi_fetch": "https://esm.sh/openapi-fetch@0.7.6",
"png_chunk_text": "https://esm.sh/png-chunk-text@1.0.0",
"png_chunks_extract": "https://esm.sh/png-chunks-extract@1.0.0",
"reroute": "https://deno.land/x/reroute@v0.1.0/mod.ts",
"serve_spa": "https://deno.land/x/serve_spa@v0.2.0/mod.ts",
"std/async/": "https://deno.land/std@0.201.0/async/",
"std/collections/": "https://deno.land/std@0.202.0/collections/",
"std/dotenv/": "https://deno.land/std@0.201.0/dotenv/", "std/dotenv/": "https://deno.land/std@0.201.0/dotenv/",
"std/encoding/": "https://deno.land/std@0.202.0/encoding/",
"std/fmt/": "https://deno.land/std@0.202.0/fmt/",
"std/log/": "https://deno.land/std@0.201.0/log/", "std/log/": "https://deno.land/std@0.201.0/log/",
"std/async/": "https://deno.land/std@0.201.0/async/",
"std/fmt/": "https://deno.land/std@0.202.0/fmt/",
"std/collections/": "https://deno.land/std@0.202.0/collections/",
"std/encoding/": "https://deno.land/std@0.202.0/encoding/",
"std/path/": "https://deno.land/std@0.204.0/path/", "std/path/": "https://deno.land/std@0.204.0/path/",
"t_rest/server": "https://esm.sh/ty-rest@0.4.0/server?dev",
"ulid": "https://deno.land/x/ulid@v0.3.0/mod.ts",
"@twind/core": "https://esm.sh/@twind/core@1.1.3?dev", "async": "https://deno.land/x/async@v2.0.2/mod.ts",
"@twind/preset-tailwind": "https://esm.sh/@twind/preset-tailwind@1.1.4?dev", "ulid": "https://deno.land/x/ulid@v0.3.0/mod.ts",
"react-dom/client": "https://esm.sh/react-dom@18.2.0/client?dev", "indexed_kv": "https://deno.land/x/indexed_kv@v0.5.0/mod.ts",
"react-flip-move": "https://esm.sh/react-flip-move@3.0.5?dev", "kvmq": "https://deno.land/x/kvmq@v0.3.0/mod.ts",
"react-intl": "https://esm.sh/react-intl@6.4.7?external=react&alias=@types/react:react&dev", "kvfs": "https://deno.land/x/kvfs@v0.1.0/mod.ts",
"react-router-dom": "https://esm.sh/react-router-dom@6.16.0?dev", "serve_spa": "https://deno.land/x/serve_spa@v0.2.0/mod.ts",
"reroute": "https://deno.land/x/reroute@v0.1.0/mod.ts",
"grammy": "https://lib.deno.dev/x/grammy@1/mod.ts",
"grammy_runner": "https://lib.deno.dev/x/grammy_runner@2/mod.ts",
"grammy_types": "https://lib.deno.dev/x/grammy_types@3/mod.ts",
"grammy_autoquote": "https://lib.deno.dev/x/grammy_autoquote@1/mod.ts",
"grammy_parse_mode": "https://lib.deno.dev/x/grammy_parse_mode@1/mod.ts",
"grammy_stateless_question": "https://lib.deno.dev/x/grammy_stateless_question_alpha@3/mod.ts",
"grammy_files": "https://lib.deno.dev/x/grammy_files@1/mod.ts",
"date-fns": "https://cdn.skypack.dev/date-fns@2.30.0?dts",
"@date-fns/utc": "https://cdn.skypack.dev/@date-fns/utc@1.1.0?dts",
"file_type": "https://esm.sh/file-type@18.5.0",
"png_chunks_extract": "https://esm.sh/png-chunks-extract@1.0.0",
"png_chunk_text": "https://esm.sh/png-chunk-text@1.0.0",
"openapi_fetch": "https://esm.sh/openapi-fetch@0.7.6",
"t_rest/server": "https://esm.sh/ty-rest@0.4.0/server?dev",
"t_rest/client": "https://esm.sh/ty-rest@0.4.0/client?dev",
"react": "https://esm.sh/react@18.2.0?dev", "react": "https://esm.sh/react@18.2.0?dev",
"react-dom/client": "https://esm.sh/react-dom@18.2.0/client?dev",
"react-router-dom": "https://esm.sh/react-router-dom@6.16.0?dev",
"react-intl": "https://esm.sh/react-intl@6.4.7?external=react&alias=@types/react:react&dev",
"swr": "https://esm.sh/swr@2.2.4?dev", "swr": "https://esm.sh/swr@2.2.4?dev",
"swr/mutation": "https://esm.sh/swr@2.2.4/mutation?dev", "swr/mutation": "https://esm.sh/swr@2.2.4/mutation?dev",
"t_rest/client": "https://esm.sh/ty-rest@0.4.0/client?dev", "use-local-storage": "https://esm.sh/use-local-storage@3.0.0?dev",
"use-local-storage": "https://esm.sh/use-local-storage@3.0.0?dev" "@twind/core": "https://esm.sh/@twind/core@1.1.3?dev",
"@twind/preset-tailwind": "https://esm.sh/@twind/preset-tailwind@1.1.4?dev",
"react-flip-move": "https://esm.sh/react-flip-move@3.0.5?dev"
} }
} }

View File

@ -1,20 +1,18 @@
/// <reference lib="deno.unstable" /> /// <reference lib="deno.unstable" />
import "std/dotenv/load.ts"; import "std/dotenv/load.ts";
import { ConsoleHandler } from "std/log/handlers.ts"; import { ConsoleHandler } from "std/log/handlers.ts";
import { LevelName, setup } from "std/log/mod.ts"; import { setup } from "std/log/mod.ts";
import { serveUi } from "./api/mod.ts"; import { serveUi } from "./api/mod.ts";
import { runAllTasks } from "./app/mod.ts"; import { runAllTasks } from "./app/mod.ts";
import { runBot } from "./bot/mod.ts"; import { runBot } from "./bot/mod.ts";
const logLevel = Deno.env.get("LOG_LEVEL")?.toUpperCase() as LevelName ?? "INFO";
// setup logging // setup logging
setup({ setup({
handlers: { handlers: {
console: new ConsoleHandler(logLevel), console: new ConsoleHandler("INFO"),
}, },
loggers: { loggers: {
default: { level: logLevel, handlers: ["console"] }, default: { level: "INFO", handlers: ["console"] },
}, },
}); });