Compare commits
3 Commits
07fc0c71f2
...
5a241ff13c
Author | SHA1 | Date |
---|---|---|
pinks | 5a241ff13c | |
pinks | 2a3674b0c4 | |
pinks | 843f6d9103 |
|
@ -14,12 +14,16 @@ 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
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
import { deepMerge } from "std/collections/deep_merge.ts";
|
import { deepMerge } from "std/collections/deep_merge.ts";
|
||||||
import { getLogger } from "std/log/mod.ts";
|
import { info } 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 },
|
||||||
|
@ -38,7 +36,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" } };
|
||||||
}
|
}
|
||||||
logger().info(`User ${chat.username} updated default params: ${JSON.stringify(body.data)}`);
|
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 } };
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
import { createEndpoint, createMethodFilter, createPathFilter } from "t_rest/server";
|
|
||||||
import { activeGenerationWorkers } from "../app/generationQueue.ts";
|
|
||||||
import { getConfig } from "../app/config.ts";
|
|
||||||
import * as SdApi from "../app/sdApi.ts";
|
|
||||||
import createOpenApiFetch from "openapi_fetch";
|
|
||||||
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";
|
import { subMinutes } from "date-fns";
|
||||||
|
import { Model } from "indexed_kv";
|
||||||
const logger = () => getLogger();
|
import createOpenApiFetch from "openapi_fetch";
|
||||||
|
import { info } from "std/log/mod.ts";
|
||||||
|
import { createEndpoint, createMethodFilter, createPathFilter } from "t_rest/server";
|
||||||
|
import { getConfig } from "../app/config.ts";
|
||||||
|
import { activeGenerationWorkers } from "../app/generationQueue.ts";
|
||||||
|
import { generationStore } from "../app/generationStore.ts";
|
||||||
|
import * as SdApi from "../app/sdApi.ts";
|
||||||
|
import { WorkerInstance, workerInstanceStore } from "../app/workerInstanceStore.ts";
|
||||||
|
import { bot } from "../bot/mod.ts";
|
||||||
|
import { getAuthHeader } from "../utils/getAuthHeader.ts";
|
||||||
|
import { sessions } from "./sessionsRoute.ts";
|
||||||
|
|
||||||
export type WorkerData = Omit<WorkerInstance, "sdUrl" | "sdAuth"> & {
|
export type WorkerData = Omit<WorkerInstance, "sdUrl" | "sdAuth"> & {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -124,7 +122,7 @@ export const workersRoute = createPathFilter({
|
||||||
sdUrl: body.data.sdUrl,
|
sdUrl: body.data.sdUrl,
|
||||||
sdAuth: body.data.sdAuth,
|
sdAuth: body.data.sdAuth,
|
||||||
});
|
});
|
||||||
logger().info(`User ${chat.username} created worker ${workerInstance.id}`);
|
info(`User ${chat.username} created worker ${workerInstance.id}`);
|
||||||
const worker = await getWorkerData(workerInstance);
|
const worker = await getWorkerData(workerInstance);
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
|
@ -202,7 +200,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;
|
||||||
}
|
}
|
||||||
logger().info(
|
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();
|
||||||
|
@ -238,7 +236,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" } };
|
||||||
}
|
}
|
||||||
logger().info(`User ${chat.username} deleted worker ${params.workerId}`);
|
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 } };
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
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 { getLogger } from "std/log/mod.ts";
|
import { info } 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: {
|
||||||
|
@ -36,7 +34,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));
|
||||||
|
|
||||||
logger().info(`Calculating daily stats for ${year}-${month}-${day}`);
|
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 })
|
||||||
|
|
|
@ -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 { getLogger } from "std/log/mod.ts";
|
import { debug, error, info } 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,8 +19,6 @@ 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:
|
||||||
| {
|
| {
|
||||||
|
@ -73,7 +71,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);
|
||||||
logger().debug(`Worker ${workerInstance.value.key} is down: ${error}`);
|
debug(`Worker ${workerInstance.value.key} is down: ${error}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!activeWorkerStatus?.data) {
|
if (!activeWorkerStatus?.data) {
|
||||||
|
@ -86,7 +84,7 @@ export async function processGenerationQueue() {
|
||||||
});
|
});
|
||||||
|
|
||||||
newWorker.addEventListener("error", (e) => {
|
newWorker.addEventListener("error", (e) => {
|
||||||
logger().error(
|
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(
|
||||||
|
@ -103,7 +101,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);
|
||||||
logger().info(`Stopped worker ${workerInstance.value.key}`);
|
info(`Stopped worker ${workerInstance.value.key}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
newWorker.addEventListener("complete", () => {
|
newWorker.addEventListener("complete", () => {
|
||||||
|
@ -113,7 +111,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);
|
||||||
logger().info(`Started worker ${workerInstance.value.key}`);
|
info(`Started worker ${workerInstance.value.key}`);
|
||||||
}
|
}
|
||||||
await delay(60_000);
|
await delay(60_000);
|
||||||
}
|
}
|
||||||
|
@ -139,7 +137,7 @@ async function processGenerationJob(
|
||||||
});
|
});
|
||||||
state.workerInstanceKey = workerInstance.value.key;
|
state.workerInstanceKey = workerInstance.value.key;
|
||||||
state.progress = 0;
|
state.progress = 0;
|
||||||
logger().debug(`Generation started for ${formatUserChat(state)}`);
|
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
|
||||||
|
@ -253,7 +251,7 @@ async function processGenerationJob(
|
||||||
).catch(() => undefined);
|
).catch(() => undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.race([delay(1000), responsePromise]).catch(() => undefined);
|
await Promise.race([delay(2_000), responsePromise]).catch(() => undefined);
|
||||||
} while (await promiseState(responsePromise) === "pending");
|
} while (await promiseState(responsePromise) === "pending");
|
||||||
|
|
||||||
// check response
|
// check response
|
||||||
|
@ -298,7 +296,7 @@ async function processGenerationJob(
|
||||||
{ maxAttempts: 1 },
|
{ maxAttempts: 1 },
|
||||||
).catch(() => undefined);
|
).catch(() => undefined);
|
||||||
|
|
||||||
logger().debug(`Generation finished for ${formatUserChat(state)}`);
|
debug(`Generation finished for ${formatUserChat(state)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -4,15 +4,13 @@ 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 { getLogger } from "std/log/mod.ts";
|
import { debug, error } 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;
|
||||||
|
@ -63,12 +61,16 @@ 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
|
||||||
|
@ -121,13 +123,19 @@ 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) => {
|
||||||
logger().error(`Upload failed for ${formatUserChat(e.detail.job.state)}: ${e.detail.error}`);
|
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` +
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import { minutesToMilliseconds } from "date-fns";
|
import { minutesToMilliseconds } from "date-fns";
|
||||||
import { Store } from "indexed_kv";
|
import { Store } from "indexed_kv";
|
||||||
import { getLogger } from "std/log/mod.ts";
|
import { info } 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: {
|
||||||
|
@ -63,7 +61,7 @@ export const getUserStats = kvMemoize(
|
||||||
let pixelStepCount = 0;
|
let pixelStepCount = 0;
|
||||||
const tagCountMap: Record<string, number> = {};
|
const tagCountMap: Record<string, number> = {};
|
||||||
|
|
||||||
logger().info(`Calculating user stats for ${userId}`);
|
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 })
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
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, logger } from "./mod.ts";
|
import { ErisContext } 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) {
|
||||||
|
@ -46,10 +47,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);
|
||||||
logger().info(`Broadcasted to ${formatUserChat({ from: gen.value.from })}`);
|
info(`Broadcasted to ${formatUserChat({ from: gen.value.from })}`);
|
||||||
sentCount++;
|
sentCount++;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger().error(`Broadcasting to ${formatUserChat({ from: gen.value.from })} failed: ${err}`);
|
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();
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
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, logger } from "./mod.ts";
|
import { ErisContext } 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> };
|
||||||
|
@ -126,5 +127,5 @@ async function img2img(
|
||||||
replyMessage: replyMessage,
|
replyMessage: replyMessage,
|
||||||
}, { retryCount: 3, repeatDelayMs: 10_000 });
|
}, { retryCount: 3, repeatDelayMs: 10_000 });
|
||||||
|
|
||||||
logger().debug(`Generation (img2img) enqueued for ${formatUserChat(ctx.message)}`);
|
debug(`Generation (img2img) enqueued for ${formatUserChat(ctx.message)}`);
|
||||||
}
|
}
|
||||||
|
|
14
bot/mod.ts
14
bot/mod.ts
|
@ -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 { getLogger } from "std/log/mod.ts";
|
import { error, info, warning } 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,8 +13,6 @@ 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;
|
||||||
|
@ -77,7 +75,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;
|
||||||
logger().warning(
|
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));
|
||||||
|
@ -85,7 +83,7 @@ bot.api.config.use(async (prev, method, payload, signal) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
bot.catch((err) => {
|
bot.catch((err) => {
|
||||||
logger().error(
|
error(
|
||||||
`Handling update from ${formatUserChat(err.ctx)} failed: ${err.name} ${err.message}`,
|
`Handling update from ${formatUserChat(err.ctx)} failed: ${err.name} ${err.message}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -131,7 +129,7 @@ bot.command("start", async (ctx) => {
|
||||||
}
|
}
|
||||||
session.userId = ctx.from?.id;
|
session.userId = ctx.from?.id;
|
||||||
sessions.set(id, session);
|
sessions.set(id, session);
|
||||||
logger().info(`User ${formatUserChat(ctx)} logged in`);
|
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,
|
||||||
|
@ -169,7 +167,7 @@ bot.command("pause", async (ctx) => {
|
||||||
await setConfig({
|
await setConfig({
|
||||||
pausedReason: ctx.match || "No reason given",
|
pausedReason: ctx.match || "No reason given",
|
||||||
});
|
});
|
||||||
logger().warning(`Bot paused by ${ctx.from.first_name} because ${config.pausedReason}`);
|
warning(`Bot paused by ${ctx.from.first_name} because ${config.pausedReason}`);
|
||||||
return ctx.reply("Paused");
|
return ctx.reply("Paused");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -179,7 +177,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 });
|
||||||
logger().info(`Bot resumed by ${ctx.from.first_name}`);
|
info(`Bot resumed by ${ctx.from.first_name}`);
|
||||||
return ctx.reply("Resumed");
|
return ctx.reply("Resumed");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
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, logger } from "./mod.ts";
|
import { ErisContext } 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>(
|
||||||
|
@ -91,5 +92,5 @@ async function txt2img(ctx: ErisContext, match: string, includeRepliedTo: boolea
|
||||||
replyMessage: replyMessage,
|
replyMessage: replyMessage,
|
||||||
}, { retryCount: 3, retryDelayMs: 10_000 });
|
}, { retryCount: 3, retryDelayMs: 10_000 });
|
||||||
|
|
||||||
logger().debug(`Generation (txt2img) enqueued for ${formatUserChat(ctx.message)}`);
|
debug(`Generation (txt2img) enqueued for ${formatUserChat(ctx.message)}`);
|
||||||
}
|
}
|
||||||
|
|
67
deno.json
67
deno.json
|
@ -10,46 +10,45 @@
|
||||||
"lineWidth": 100
|
"lineWidth": 100
|
||||||
},
|
},
|
||||||
"imports": {
|
"imports": {
|
||||||
"std/dotenv/": "https://deno.land/std@0.201.0/dotenv/",
|
|
||||||
"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/",
|
|
||||||
|
|
||||||
"async": "https://deno.land/x/async@v2.0.2/mod.ts",
|
|
||||||
"ulid": "https://deno.land/x/ulid@v0.3.0/mod.ts",
|
|
||||||
"indexed_kv": "https://deno.land/x/indexed_kv@v0.5.0/mod.ts",
|
|
||||||
"kvmq": "https://deno.land/x/kvmq@v0.3.0/mod.ts",
|
|
||||||
"kvfs": "https://deno.land/x/kvfs@v0.1.0/mod.ts",
|
|
||||||
"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",
|
"@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",
|
"file_type": "https://esm.sh/file-type@18.5.0",
|
||||||
"png_chunks_extract": "https://esm.sh/png-chunks-extract@1.0.0",
|
"grammy_autoquote": "https://lib.deno.dev/x/grammy_autoquote@1/mod.ts",
|
||||||
"png_chunk_text": "https://esm.sh/png-chunk-text@1.0.0",
|
"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",
|
"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/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/path/": "https://deno.land/std@0.204.0/path/",
|
||||||
"t_rest/server": "https://esm.sh/ty-rest@0.4.0/server?dev",
|
"t_rest/server": "https://esm.sh/ty-rest@0.4.0/server?dev",
|
||||||
|
"ulid": "https://deno.land/x/ulid@v0.3.0/mod.ts",
|
||||||
|
|
||||||
"t_rest/client": "https://esm.sh/ty-rest@0.4.0/client?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/mutation": "https://esm.sh/swr@2.2.4/mutation?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/core": "https://esm.sh/@twind/core@1.1.3?dev",
|
||||||
"@twind/preset-tailwind": "https://esm.sh/@twind/preset-tailwind@1.1.4?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"
|
"react-dom/client": "https://esm.sh/react-dom@18.2.0/client?dev",
|
||||||
|
"react-flip-move": "https://esm.sh/react-flip-move@3.0.5?dev",
|
||||||
|
"react-intl": "https://esm.sh/react-intl@6.4.7?external=react&alias=@types/react:react&dev",
|
||||||
|
"react-router-dom": "https://esm.sh/react-router-dom@6.16.0?dev",
|
||||||
|
"react": "https://esm.sh/react@18.2.0?dev",
|
||||||
|
"swr": "https://esm.sh/swr@2.2.4?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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
8
main.ts
8
main.ts
|
@ -1,18 +1,20 @@
|
||||||
/// <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 { setup } from "std/log/mod.ts";
|
import { LevelName, 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("INFO"),
|
console: new ConsoleHandler(logLevel),
|
||||||
},
|
},
|
||||||
loggers: {
|
loggers: {
|
||||||
default: { level: "INFO", handlers: ["console"] },
|
default: { level: logLevel, handlers: ["console"] },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue