use import map instead of deps.ts
This commit is contained in:
parent
aa380c63fb
commit
ff5320bcf4
|
@ -1,4 +1,4 @@
|
||||||
import { KVFS } from "../deps.ts";
|
import { KvFs } from "kvfs";
|
||||||
|
|
||||||
export const db = await Deno.openKv("./app.db");
|
export const db = await Deno.openKv("./app.db");
|
||||||
export const fs = new KVFS.KvFs(db);
|
export const fs = new KvFs(db);
|
||||||
|
|
|
@ -1,25 +1,23 @@
|
||||||
|
import { promiseState } from "async";
|
||||||
|
import { Chat, Message, User } from "grammy_types";
|
||||||
|
import { JobData, Queue, Worker } from "kvmq";
|
||||||
|
import createOpenApiClient from "openapi_fetch";
|
||||||
|
import { delay } from "std/async";
|
||||||
|
import { decode, encode } from "std/encoding/base64";
|
||||||
|
import { getLogger } from "std/log";
|
||||||
|
import { ulid } from "ulid";
|
||||||
import { bot } from "../bot/mod.ts";
|
import { bot } from "../bot/mod.ts";
|
||||||
|
import { SdError } from "../sd/SdError.ts";
|
||||||
import { PngInfo } from "../sd/parsePngInfo.ts";
|
import { PngInfo } from "../sd/parsePngInfo.ts";
|
||||||
import * as SdApi from "../sd/sdApi.ts";
|
import * as SdApi from "../sd/sdApi.ts";
|
||||||
|
import { formatOrdinal } from "../utils/formatOrdinal.ts";
|
||||||
import { formatUserChat } from "../utils/formatUserChat.ts";
|
import { formatUserChat } from "../utils/formatUserChat.ts";
|
||||||
import { getConfig, SdInstanceData } from "./config.ts";
|
import { getConfig, SdInstanceData } from "./config.ts";
|
||||||
import { db, fs } from "./db.ts";
|
import { db, fs } from "./db.ts";
|
||||||
import { SdGenerationInfo } from "./generationStore.ts";
|
import { SdGenerationInfo } from "./generationStore.ts";
|
||||||
import {
|
|
||||||
Async,
|
|
||||||
AsyncX,
|
|
||||||
Base64,
|
|
||||||
createOpenApiClient,
|
|
||||||
GrammyTypes,
|
|
||||||
KVMQ,
|
|
||||||
Log,
|
|
||||||
ULID,
|
|
||||||
} from "../deps.ts";
|
|
||||||
import { formatOrdinal } from "../utils/formatOrdinal.ts";
|
|
||||||
import { SdError } from "../sd/SdError.ts";
|
|
||||||
import { uploadQueue } from "./uploadQueue.ts";
|
import { uploadQueue } from "./uploadQueue.ts";
|
||||||
|
|
||||||
const logger = () => Log.getLogger();
|
const logger = () => getLogger();
|
||||||
|
|
||||||
interface GenerationJob {
|
interface GenerationJob {
|
||||||
task:
|
task:
|
||||||
|
@ -32,17 +30,17 @@ interface GenerationJob {
|
||||||
params: Partial<PngInfo>;
|
params: Partial<PngInfo>;
|
||||||
fileId: string;
|
fileId: string;
|
||||||
};
|
};
|
||||||
from: GrammyTypes.User;
|
from: User;
|
||||||
chat: GrammyTypes.Chat;
|
chat: Chat;
|
||||||
requestMessage: GrammyTypes.Message;
|
requestMessage: Message;
|
||||||
replyMessage: GrammyTypes.Message;
|
replyMessage: Message;
|
||||||
sdInstanceId?: string;
|
sdInstanceId?: string;
|
||||||
progress?: number;
|
progress?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const generationQueue = new KVMQ.Queue<GenerationJob>(db, "jobQueue");
|
export const generationQueue = new Queue<GenerationJob>(db, "jobQueue");
|
||||||
|
|
||||||
export const activeGenerationWorkers = new Map<string, KVMQ.Worker<GenerationJob>>();
|
export const activeGenerationWorkers = new Map<string, Worker<GenerationJob>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes queue workers for each SD instance when they become online.
|
* Initializes queue workers for each SD instance when they become online.
|
||||||
|
@ -104,7 +102,7 @@ export async function processGenerationQueue() {
|
||||||
activeGenerationWorkers.set(sdInstance.id, newWorker);
|
activeGenerationWorkers.set(sdInstance.id, newWorker);
|
||||||
logger().info(`Started worker ${sdInstance.id}`);
|
logger().info(`Started worker ${sdInstance.id}`);
|
||||||
}
|
}
|
||||||
await Async.delay(60_000);
|
await delay(60_000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +111,7 @@ export async function processGenerationQueue() {
|
||||||
*/
|
*/
|
||||||
async function processGenerationJob(
|
async function processGenerationJob(
|
||||||
state: GenerationJob,
|
state: GenerationJob,
|
||||||
updateJob: (job: Partial<KVMQ.JobData<GenerationJob>>) => Promise<void>,
|
updateJob: (job: Partial<JobData<GenerationJob>>) => Promise<void>,
|
||||||
sdInstance: SdInstanceData,
|
sdInstance: SdInstanceData,
|
||||||
) {
|
) {
|
||||||
const startDate = new Date();
|
const startDate = new Date();
|
||||||
|
@ -185,7 +183,7 @@ async function processGenerationJob(
|
||||||
? state.task.params.negative_prompt
|
? state.task.params.negative_prompt
|
||||||
: config.defaultParams?.negative_prompt,
|
: config.defaultParams?.negative_prompt,
|
||||||
init_images: [
|
init_images: [
|
||||||
Base64.encode(
|
encode(
|
||||||
await fetch(
|
await fetch(
|
||||||
`https://api.telegram.org/file/bot${bot.token}/${await bot.api.getFile(
|
`https://api.telegram.org/file/bot${bot.token}/${await bot.api.getFile(
|
||||||
state.task.fileId,
|
state.task.fileId,
|
||||||
|
@ -229,8 +227,8 @@ async function processGenerationJob(
|
||||||
{ maxAttempts: 1 },
|
{ maxAttempts: 1 },
|
||||||
).catch(() => undefined);
|
).catch(() => undefined);
|
||||||
|
|
||||||
await Promise.race([Async.delay(3000), responsePromise]).catch(() => undefined);
|
await Promise.race([delay(3000), responsePromise]).catch(() => undefined);
|
||||||
} while (await AsyncX.promiseState(responsePromise) === "pending");
|
} while (await promiseState(responsePromise) === "pending");
|
||||||
|
|
||||||
// check response
|
// check response
|
||||||
const response = await responsePromise;
|
const response = await responsePromise;
|
||||||
|
@ -247,8 +245,8 @@ async function processGenerationJob(
|
||||||
// save images to db
|
// save images to db
|
||||||
const imageKeys: Deno.KvKey[] = [];
|
const imageKeys: Deno.KvKey[] = [];
|
||||||
for (const imageBase64 of response.data.images) {
|
for (const imageBase64 of response.data.images) {
|
||||||
const imageBuffer = Base64.decode(imageBase64);
|
const imageBuffer = decode(imageBase64);
|
||||||
const imageKey = ["images", "upload", ULID.ulid()];
|
const imageKey = ["images", "upload", ulid()];
|
||||||
await fs.set(imageKey, imageBuffer, { expireIn: 30 * 60 * 1000 });
|
await fs.set(imageKey, imageBuffer, { expireIn: 30 * 60 * 1000 });
|
||||||
imageKeys.push(imageKey);
|
imageKeys.push(imageKey);
|
||||||
}
|
}
|
||||||
|
@ -301,6 +299,6 @@ export async function updateGenerationQueue() {
|
||||||
{ maxAttempts: 1 },
|
{ maxAttempts: 1 },
|
||||||
).catch(() => undefined);
|
).catch(() => undefined);
|
||||||
}
|
}
|
||||||
await Async.delay(3000);
|
await delay(3000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { GrammyTypes, IKV } from "../deps.ts";
|
import { Chat, User } from "grammy_types";
|
||||||
|
import { Store } from "indexed_kv";
|
||||||
import { db } from "./db.ts";
|
import { db } from "./db.ts";
|
||||||
|
|
||||||
export interface GenerationSchema {
|
export interface GenerationSchema {
|
||||||
from: GrammyTypes.User;
|
from: User;
|
||||||
chat: GrammyTypes.Chat;
|
chat: Chat;
|
||||||
sdInstanceId?: string;
|
sdInstanceId?: string;
|
||||||
info?: SdGenerationInfo;
|
info?: SdGenerationInfo;
|
||||||
startDate?: Date;
|
startDate?: Date;
|
||||||
|
@ -49,7 +50,7 @@ type GenerationIndices = {
|
||||||
chatId: number;
|
chatId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generationStore = new IKV.Store<GenerationSchema, GenerationIndices>(
|
export const generationStore = new Store<GenerationSchema, GenerationIndices>(
|
||||||
db,
|
db,
|
||||||
"generations",
|
"generations",
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
|
import { fileTypeFromBuffer } from "file_type";
|
||||||
|
import { InputFile, InputMediaBuilder } from "grammy";
|
||||||
|
import { bold, fmt } from "grammy_parse_mode";
|
||||||
|
import { Chat, Message, User } from "grammy_types";
|
||||||
|
import { Queue } from "kvmq";
|
||||||
|
import { format } from "std/fmt/duration";
|
||||||
|
import { getLogger } from "std/log";
|
||||||
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 { FileType, FmtDuration, Grammy, GrammyParseMode, GrammyTypes, KVMQ, Log } from "../deps.ts";
|
|
||||||
|
|
||||||
const logger = () => Log.getLogger();
|
const logger = () => getLogger();
|
||||||
|
|
||||||
interface UploadJob {
|
interface UploadJob {
|
||||||
from: GrammyTypes.User;
|
from: User;
|
||||||
chat: GrammyTypes.Chat;
|
chat: Chat;
|
||||||
requestMessage: GrammyTypes.Message;
|
requestMessage: Message;
|
||||||
replyMessage: GrammyTypes.Message;
|
replyMessage: Message;
|
||||||
sdInstanceId: string;
|
sdInstanceId: string;
|
||||||
startDate: Date;
|
startDate: Date;
|
||||||
endDate: Date;
|
endDate: Date;
|
||||||
|
@ -18,7 +24,7 @@ interface UploadJob {
|
||||||
info: SdGenerationInfo;
|
info: SdGenerationInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const uploadQueue = new KVMQ.Queue<UploadJob>(db, "uploadQueue");
|
export const uploadQueue = new Queue<UploadJob>(db, "uploadQueue");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes queue worker for uploading images to Telegram.
|
* Initializes queue worker for uploading images to Telegram.
|
||||||
|
@ -37,7 +43,6 @@ export async function processUploadQueue() {
|
||||||
// const detailedReply = Object.keys(job.value.params).filter((key) => key !== "prompt").length > 0;
|
// const detailedReply = Object.keys(job.value.params).filter((key) => key !== "prompt").length > 0;
|
||||||
const detailedReply = true;
|
const detailedReply = true;
|
||||||
const jobDurationMs = Math.trunc((Date.now() - state.startDate.getTime()) / 1000) * 1000;
|
const jobDurationMs = Math.trunc((Date.now() - state.startDate.getTime()) / 1000) * 1000;
|
||||||
const { bold, fmt } = GrammyParseMode;
|
|
||||||
const caption = fmt([
|
const caption = fmt([
|
||||||
`${state.info.prompt}\n`,
|
`${state.info.prompt}\n`,
|
||||||
...detailedReply
|
...detailedReply
|
||||||
|
@ -51,7 +56,7 @@ export async function processUploadQueue() {
|
||||||
fmt`${bold("Seed:")} ${state.info.seed}, `,
|
fmt`${bold("Seed:")} ${state.info.seed}, `,
|
||||||
fmt`${bold("Size")}: ${state.info.width}x${state.info.height}, `,
|
fmt`${bold("Size")}: ${state.info.width}x${state.info.height}, `,
|
||||||
fmt`${bold("Worker")}: ${state.sdInstanceId}, `,
|
fmt`${bold("Worker")}: ${state.sdInstanceId}, `,
|
||||||
fmt`${bold("Time taken")}: ${FmtDuration.format(jobDurationMs, { ignoreZero: true })}`,
|
fmt`${bold("Time taken")}: ${format(jobDurationMs, { ignoreZero: true })}`,
|
||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
]);
|
]);
|
||||||
|
@ -61,10 +66,10 @@ export async function processUploadQueue() {
|
||||||
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 FileType.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");
|
||||||
return Grammy.InputMediaBuilder.photo(
|
return InputMediaBuilder.photo(
|
||||||
new Grammy.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
|
||||||
idx === 0 && caption.text.length <= 1024
|
idx === 0 && caption.text.length <= 1024
|
||||||
? { caption: caption.text, caption_entities: caption.entities }
|
? { caption: caption.text, caption_entities: caption.entities }
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { generationQueue } from "../app/generationQueue.ts";
|
import { generationQueue } from "../app/generationQueue.ts";
|
||||||
import { Context } from "./mod.ts";
|
import { ErisContext } from "./mod.ts";
|
||||||
|
|
||||||
export async function cancelCommand(ctx: Context) {
|
export async function cancelCommand(ctx: ErisContext) {
|
||||||
const jobs = await generationQueue.getAllJobs();
|
const jobs = await generationQueue.getAllJobs();
|
||||||
const userJobs = jobs
|
const userJobs = jobs
|
||||||
.filter((job) => job.lockUntil < new Date())
|
.filter((job) => job.lockUntil < new Date())
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import { Collections, Grammy, GrammyStatelessQ } from "../deps.ts";
|
import { CommandContext } from "grammy";
|
||||||
import { formatUserChat } from "../utils/formatUserChat.ts";
|
import { StatelessQuestion } from "grammy_stateless_question";
|
||||||
import { parsePngInfo, PngInfo } from "../sd/parsePngInfo.ts";
|
import { maxBy } from "std/collections";
|
||||||
import { Context, logger } from "./mod.ts";
|
|
||||||
import { generationQueue } from "../app/generationQueue.ts";
|
|
||||||
import { getConfig } from "../app/config.ts";
|
import { getConfig } from "../app/config.ts";
|
||||||
|
import { generationQueue } from "../app/generationQueue.ts";
|
||||||
|
import { parsePngInfo, PngInfo } from "../sd/parsePngInfo.ts";
|
||||||
|
import { formatUserChat } from "../utils/formatUserChat.ts";
|
||||||
|
import { ErisContext, logger } from "./mod.ts";
|
||||||
|
|
||||||
export const img2imgQuestion = new GrammyStatelessQ.StatelessQuestion<Context>(
|
export const img2imgQuestion = new StatelessQuestion<ErisContext>(
|
||||||
"img2img",
|
"img2img",
|
||||||
async (ctx, state) => {
|
async (ctx, state) => {
|
||||||
// todo: also save original image size in state
|
// todo: also save original image size in state
|
||||||
|
@ -13,12 +15,12 @@ export const img2imgQuestion = new GrammyStatelessQ.StatelessQuestion<Context>(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export async function img2imgCommand(ctx: Grammy.CommandContext<Context>) {
|
export async function img2imgCommand(ctx: CommandContext<ErisContext>) {
|
||||||
await img2img(ctx, ctx.match, true);
|
await img2img(ctx, ctx.match, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function img2img(
|
async function img2img(
|
||||||
ctx: Context,
|
ctx: ErisContext,
|
||||||
match: string | undefined,
|
match: string | undefined,
|
||||||
includeRepliedTo: boolean,
|
includeRepliedTo: boolean,
|
||||||
fileId?: string,
|
fileId?: string,
|
||||||
|
@ -57,7 +59,7 @@ async function img2img(
|
||||||
|
|
||||||
if (includeRepliedTo && repliedToMsg?.photo) {
|
if (includeRepliedTo && repliedToMsg?.photo) {
|
||||||
const photos = repliedToMsg.photo;
|
const photos = repliedToMsg.photo;
|
||||||
const biggestPhoto = Collections.maxBy(photos, (p) => p.width * p.height);
|
const biggestPhoto = maxBy(photos, (p) => p.width * p.height);
|
||||||
if (!biggestPhoto) throw new Error("Message was a photo but had no photos?");
|
if (!biggestPhoto) throw new Error("Message was a photo but had no photos?");
|
||||||
fileId = biggestPhoto.file_id;
|
fileId = biggestPhoto.file_id;
|
||||||
params.width = biggestPhoto.width;
|
params.width = biggestPhoto.width;
|
||||||
|
@ -66,7 +68,7 @@ async function img2img(
|
||||||
|
|
||||||
if (ctx.message.photo) {
|
if (ctx.message.photo) {
|
||||||
const photos = ctx.message.photo;
|
const photos = ctx.message.photo;
|
||||||
const biggestPhoto = Collections.maxBy(photos, (p) => p.width * p.height);
|
const biggestPhoto = maxBy(photos, (p) => p.width * p.height);
|
||||||
if (!biggestPhoto) throw new Error("Message was a photo but had no photos?");
|
if (!biggestPhoto) throw new Error("Message was a photo but had no photos?");
|
||||||
fileId = biggestPhoto.file_id;
|
fileId = biggestPhoto.file_id;
|
||||||
params.width = biggestPhoto.width;
|
params.width = biggestPhoto.width;
|
||||||
|
|
47
bot/mod.ts
47
bot/mod.ts
|
@ -1,52 +1,53 @@
|
||||||
import { Grammy, GrammyAutoQuote, GrammyFiles, GrammyParseMode, Log } from "../deps.ts";
|
import { Api, Bot, Context, RawApi, session, SessionFlavor } from "grammy";
|
||||||
|
import { autoQuote } from "grammy_autoquote";
|
||||||
|
import { FileFlavor, hydrateFiles } from "grammy_files";
|
||||||
|
import { hydrateReply, ParseModeFlavor } from "grammy_parse_mode";
|
||||||
|
import { getLogger } from "std/log";
|
||||||
|
import { getConfig, setConfig } from "../app/config.ts";
|
||||||
import { formatUserChat } from "../utils/formatUserChat.ts";
|
import { formatUserChat } from "../utils/formatUserChat.ts";
|
||||||
|
import { cancelCommand } from "./cancelCommand.ts";
|
||||||
|
import { img2imgCommand, img2imgQuestion } from "./img2imgCommand.ts";
|
||||||
|
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";
|
||||||
import { pnginfoCommand, pnginfoQuestion } from "./pnginfoCommand.ts";
|
|
||||||
import { img2imgCommand, img2imgQuestion } from "./img2imgCommand.ts";
|
|
||||||
import { cancelCommand } from "./cancelCommand.ts";
|
|
||||||
import { getConfig, setConfig } from "../app/config.ts";
|
|
||||||
|
|
||||||
export const logger = () => Log.getLogger();
|
export const logger = () => getLogger();
|
||||||
|
|
||||||
interface SessionData {
|
interface SessionData {
|
||||||
chat: ChatData;
|
chat: ErisChatData;
|
||||||
user: UserData;
|
user: ErisUserData;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChatData {
|
interface ErisChatData {
|
||||||
language?: string;
|
language?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserData {
|
interface ErisUserData {
|
||||||
params?: Record<string, string>;
|
params?: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Context =
|
export type ErisContext =
|
||||||
& GrammyFiles.FileFlavor<GrammyParseMode.ParseModeFlavor<Grammy.Context>>
|
& FileFlavor<ParseModeFlavor<Context>>
|
||||||
& Grammy.SessionFlavor<SessionData>;
|
& SessionFlavor<SessionData>;
|
||||||
|
|
||||||
type WithRetryApi<T extends Grammy.RawApi> = {
|
type WithRetryApi<T extends RawApi> = {
|
||||||
[M in keyof T]: T[M] extends (args: infer P, ...rest: infer A) => infer R
|
[M in keyof T]: T[M] extends (args: infer P, ...rest: infer A) => infer R
|
||||||
? (args: P extends object ? P & { maxAttempts?: number; maxWait?: number } : P, ...rest: A) => R
|
? (args: P extends object ? P & { maxAttempts?: number; maxWait?: number } : P, ...rest: A) => R
|
||||||
: T[M];
|
: T[M];
|
||||||
};
|
};
|
||||||
|
|
||||||
type Api = Grammy.Api<WithRetryApi<Grammy.RawApi>>;
|
type ErisApi = Api<WithRetryApi<RawApi>>;
|
||||||
|
|
||||||
export const bot = new Grammy.Bot<Context, Api>(
|
export const bot = new Bot<ErisContext, ErisApi>(
|
||||||
Deno.env.get("TG_BOT_TOKEN")!,
|
Deno.env.get("TG_BOT_TOKEN")!,
|
||||||
{
|
{
|
||||||
client: { timeoutSeconds: 20 },
|
client: { timeoutSeconds: 20 },
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
bot.use(GrammyAutoQuote.autoQuote);
|
bot.use(autoQuote);
|
||||||
bot.use(GrammyParseMode.hydrateReply);
|
bot.use(hydrateReply);
|
||||||
bot.use(Grammy.session<
|
bot.use(session<SessionData, ErisContext>({
|
||||||
SessionData,
|
|
||||||
Grammy.Context & Grammy.SessionFlavor<SessionData>
|
|
||||||
>({
|
|
||||||
type: "multi",
|
type: "multi",
|
||||||
chat: {
|
chat: {
|
||||||
initial: () => ({}),
|
initial: () => ({}),
|
||||||
|
@ -57,7 +58,7 @@ bot.use(Grammy.session<
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
bot.api.config.use(GrammyFiles.hydrateFiles(bot.token));
|
bot.api.config.use(hydrateFiles(bot.token));
|
||||||
|
|
||||||
// Automatically retry bot requests if we get a "too many requests" or telegram internal error
|
// Automatically retry bot requests if we get a "too many requests" or telegram internal error
|
||||||
bot.api.config.use(async (prev, method, payload, signal) => {
|
bot.api.config.use(async (prev, method, payload, signal) => {
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
import { Grammy, GrammyParseMode, GrammyStatelessQ } from "../deps.ts";
|
import { CommandContext } from "grammy";
|
||||||
|
import { bold, fmt } from "grammy_parse_mode";
|
||||||
|
import { StatelessQuestion } from "grammy_stateless_question";
|
||||||
import { getPngInfo, parsePngInfo } from "../sd/parsePngInfo.ts";
|
import { getPngInfo, parsePngInfo } from "../sd/parsePngInfo.ts";
|
||||||
import { Context } from "./mod.ts";
|
import { ErisContext } from "./mod.ts";
|
||||||
|
|
||||||
export const pnginfoQuestion = new GrammyStatelessQ.StatelessQuestion<Context>(
|
export const pnginfoQuestion = new StatelessQuestion<ErisContext>(
|
||||||
"pnginfo",
|
"pnginfo",
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
await pnginfo(ctx, false);
|
await pnginfo(ctx, false);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export async function pnginfoCommand(ctx: Grammy.CommandContext<Context>) {
|
export async function pnginfoCommand(ctx: CommandContext<ErisContext>) {
|
||||||
await pnginfo(ctx, true);
|
await pnginfo(ctx, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pnginfo(ctx: Context, includeRepliedTo: boolean): Promise<void> {
|
async function pnginfo(ctx: ErisContext, includeRepliedTo: boolean): Promise<void> {
|
||||||
const document = ctx.message?.document ||
|
const document = ctx.message?.document ||
|
||||||
(includeRepliedTo ? ctx.message?.reply_to_message?.document : undefined);
|
(includeRepliedTo ? ctx.message?.reply_to_message?.document : undefined);
|
||||||
|
|
||||||
|
@ -30,8 +32,6 @@ async function pnginfo(ctx: Context, includeRepliedTo: boolean): Promise<void> {
|
||||||
const buffer = await fetch(file.getUrl()).then((resp) => resp.arrayBuffer());
|
const buffer = await fetch(file.getUrl()).then((resp) => resp.arrayBuffer());
|
||||||
const params = parsePngInfo(getPngInfo(new Uint8Array(buffer)) ?? "");
|
const params = parsePngInfo(getPngInfo(new Uint8Array(buffer)) ?? "");
|
||||||
|
|
||||||
const { bold, fmt } = GrammyParseMode;
|
|
||||||
|
|
||||||
const paramsText = fmt([
|
const paramsText = fmt([
|
||||||
`${params.prompt}\n`,
|
`${params.prompt}\n`,
|
||||||
params.negative_prompt ? fmt`${bold("Negative prompt:")} ${params.negative_prompt}\n` : "",
|
params.negative_prompt ? fmt`${bold("Negative prompt:")} ${params.negative_prompt}\n` : "",
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { Grammy, GrammyParseMode } from "../deps.ts";
|
import { CommandContext } from "grammy";
|
||||||
import { Context } from "./mod.ts";
|
import { bold, fmt } from "grammy_parse_mode";
|
||||||
import { getFlagEmoji } from "../utils/getFlagEmoji.ts";
|
|
||||||
import { activeGenerationWorkers, generationQueue } from "../app/generationQueue.ts";
|
|
||||||
import { getConfig } from "../app/config.ts";
|
import { getConfig } from "../app/config.ts";
|
||||||
|
import { activeGenerationWorkers, generationQueue } from "../app/generationQueue.ts";
|
||||||
|
import { getFlagEmoji } from "../utils/getFlagEmoji.ts";
|
||||||
|
import { ErisContext } from "./mod.ts";
|
||||||
|
|
||||||
export async function queueCommand(ctx: Grammy.CommandContext<Context>) {
|
export async function queueCommand(ctx: CommandContext<ErisContext>) {
|
||||||
let formattedMessage = await getMessageText();
|
let formattedMessage = await getMessageText();
|
||||||
const queueMessage = await ctx.replyFmt(formattedMessage, { disable_notification: true });
|
const queueMessage = await ctx.replyFmt(formattedMessage, { disable_notification: true });
|
||||||
handleFutureUpdates().catch(() => undefined);
|
handleFutureUpdates().catch(() => undefined);
|
||||||
|
@ -18,7 +19,6 @@ export async function queueCommand(ctx: Grammy.CommandContext<Context>) {
|
||||||
.filter((job) => job.lockUntil <= new Date())
|
.filter((job) => job.lockUntil <= new Date())
|
||||||
.map((job, index) => ({ ...job, index: index + 1 }));
|
.map((job, index) => ({ ...job, index: index + 1 }));
|
||||||
const jobs = [...processingJobs, ...waitingJobs];
|
const jobs = [...processingJobs, ...waitingJobs];
|
||||||
const { bold, fmt } = GrammyParseMode;
|
|
||||||
|
|
||||||
return fmt([
|
return fmt([
|
||||||
"Current queue:\n",
|
"Current queue:\n",
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { Grammy, GrammyStatelessQ } from "../deps.ts";
|
import { CommandContext } from "grammy";
|
||||||
import { formatUserChat } from "../utils/formatUserChat.ts";
|
import { StatelessQuestion } from "grammy_stateless_question";
|
||||||
import { getPngInfo, parsePngInfo, PngInfo } from "../sd/parsePngInfo.ts";
|
|
||||||
import { Context, logger } from "./mod.ts";
|
|
||||||
import { generationQueue } from "../app/generationQueue.ts";
|
|
||||||
import { getConfig } from "../app/config.ts";
|
import { getConfig } from "../app/config.ts";
|
||||||
|
import { generationQueue } from "../app/generationQueue.ts";
|
||||||
|
import { getPngInfo, parsePngInfo, PngInfo } from "../sd/parsePngInfo.ts";
|
||||||
|
import { formatUserChat } from "../utils/formatUserChat.ts";
|
||||||
|
import { ErisContext, logger } from "./mod.ts";
|
||||||
|
|
||||||
export const txt2imgQuestion = new GrammyStatelessQ.StatelessQuestion<Context>(
|
export const txt2imgQuestion = new StatelessQuestion<ErisContext>(
|
||||||
"txt2img",
|
"txt2img",
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
if (!ctx.message.text) return;
|
if (!ctx.message.text) return;
|
||||||
|
@ -13,11 +14,11 @@ export const txt2imgQuestion = new GrammyStatelessQ.StatelessQuestion<Context>(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export async function txt2imgCommand(ctx: Grammy.CommandContext<Context>) {
|
export async function txt2imgCommand(ctx: CommandContext<ErisContext>) {
|
||||||
await txt2img(ctx, ctx.match, true);
|
await txt2img(ctx, ctx.match, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function txt2img(ctx: Context, match: string, includeRepliedTo: boolean): Promise<void> {
|
async function txt2img(ctx: ErisContext, match: string, includeRepliedTo: boolean): Promise<void> {
|
||||||
if (!ctx.message?.from?.id) {
|
if (!ctx.message?.from?.id) {
|
||||||
await ctx.reply("I don't know who you are");
|
await ctx.reply("I don't know who you are");
|
||||||
return;
|
return;
|
||||||
|
|
22
deno.jsonc
22
deno.jsonc
|
@ -4,5 +4,27 @@
|
||||||
},
|
},
|
||||||
"fmt": {
|
"fmt": {
|
||||||
"lineWidth": 100
|
"lineWidth": 100
|
||||||
|
},
|
||||||
|
"imports": {
|
||||||
|
"std/log": "https://deno.land/std@0.201.0/log/mod.ts",
|
||||||
|
"std/async": "https://deno.land/std@0.201.0/async/mod.ts",
|
||||||
|
"std/fmt/duration": "https://deno.land/std@0.202.0/fmt/duration.ts",
|
||||||
|
"std/collections": "https://deno.land/std@0.202.0/collections/mod.ts",
|
||||||
|
"std/encoding/base64": "https://deno.land/std@0.202.0/encoding/base64.ts",
|
||||||
|
"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.4.0/mod.ts",
|
||||||
|
"kvmq": "https://deno.land/x/kvmq@v0.2.0/mod.ts",
|
||||||
|
"kvfs": "https://deno.land/x/kvfs@v0.1.0/mod.ts",
|
||||||
|
"grammy": "https://lib.deno.dev/x/grammy@1/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",
|
||||||
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
20
deps.ts
20
deps.ts
|
@ -1,20 +0,0 @@
|
||||||
export * as Log from "https://deno.land/std@0.201.0/log/mod.ts";
|
|
||||||
export * as Async from "https://deno.land/std@0.201.0/async/mod.ts";
|
|
||||||
export * as FmtDuration from "https://deno.land/std@0.202.0/fmt/duration.ts";
|
|
||||||
export * as Collections from "https://deno.land/std@0.202.0/collections/mod.ts";
|
|
||||||
export * as Base64 from "https://deno.land/std@0.202.0/encoding/base64.ts";
|
|
||||||
export * as AsyncX from "https://deno.land/x/async@v2.0.2/mod.ts";
|
|
||||||
export * as ULID from "https://deno.land/x/ulid@v0.3.0/mod.ts";
|
|
||||||
export * as IKV from "https://deno.land/x/indexed_kv@v0.4.0/mod.ts";
|
|
||||||
export * as KVMQ from "https://deno.land/x/kvmq@v0.2.0/mod.ts";
|
|
||||||
export * as KVFS from "https://deno.land/x/kvfs@v0.1.0/mod.ts";
|
|
||||||
export * as Grammy from "https://deno.land/x/grammy@v1.18.3/mod.ts";
|
|
||||||
export * as GrammyTypes from "https://deno.land/x/grammy_types@v3.2.2/mod.ts";
|
|
||||||
export * as GrammyAutoQuote from "https://deno.land/x/grammy_autoquote@v1.1.2/mod.ts";
|
|
||||||
export * as GrammyParseMode from "https://deno.land/x/grammy_parse_mode@1.8.1/mod.ts";
|
|
||||||
export * as GrammyStatelessQ from "https://deno.land/x/grammy_stateless_question_alpha@v3.0.4/mod.ts";
|
|
||||||
export * as GrammyFiles from "https://deno.land/x/grammy_files@v1.0.4/mod.ts";
|
|
||||||
export * as FileType from "https://esm.sh/file-type@18.5.0";
|
|
||||||
export { default as pngChunksExtract } from "https://esm.sh/png-chunks-extract@1.0.0";
|
|
||||||
export { decode as pngChunkTextDecode } from "https://esm.sh/png-chunk-text@1.0.0";
|
|
||||||
export { default as createOpenApiClient } from "https://esm.sh/openapi-fetch@0.7.6";
|
|
8
main.ts
8
main.ts
|
@ -1,11 +1,11 @@
|
||||||
import "https://deno.land/std@0.201.0/dotenv/load.ts";
|
import "https://deno.land/std@0.201.0/dotenv/load.ts";
|
||||||
import { Log } from "./deps.ts";
|
import { handlers, setup } from "std/log";
|
||||||
import { bot } from "./bot/mod.ts";
|
|
||||||
import { runAllTasks } from "./app/mod.ts";
|
import { runAllTasks } from "./app/mod.ts";
|
||||||
|
import { bot } from "./bot/mod.ts";
|
||||||
|
|
||||||
Log.setup({
|
setup({
|
||||||
handlers: {
|
handlers: {
|
||||||
console: new Log.handlers.ConsoleHandler("DEBUG"),
|
console: new handlers.ConsoleHandler("DEBUG"),
|
||||||
},
|
},
|
||||||
loggers: {
|
loggers: {
|
||||||
default: { level: "DEBUG", handlers: ["console"] },
|
default: { level: "DEBUG", handlers: ["console"] },
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { pngChunksExtract, pngChunkTextDecode } from "../deps.ts";
|
import { decode } from "png_chunk_text";
|
||||||
|
import extractChunks from "png_chunks_extract";
|
||||||
|
|
||||||
export function getPngInfo(pngData: Uint8Array): string | undefined {
|
export function getPngInfo(pngData: Uint8Array): string | undefined {
|
||||||
return pngChunksExtract(pngData)
|
return extractChunks(pngData)
|
||||||
.filter((chunk) => chunk.name === "tEXt")
|
.filter((chunk) => chunk.name === "tEXt")
|
||||||
.map((chunk) => pngChunkTextDecode(chunk.data))
|
.map((chunk) => decode(chunk.data))
|
||||||
.find((textChunk) => textChunk.keyword === "parameters")
|
.find((textChunk) => textChunk.keyword === "parameters")
|
||||||
?.text;
|
?.text;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { GrammyTypes } from "../deps.ts";
|
import { Chat, User } from "grammy_types";
|
||||||
|
|
||||||
export function formatUserChat(
|
export function formatUserChat(
|
||||||
ctx: { from?: GrammyTypes.User; chat?: GrammyTypes.Chat; sdInstanceId?: string },
|
ctx: { from?: User; chat?: Chat; sdInstanceId?: string },
|
||||||
) {
|
) {
|
||||||
const msg: string[] = [];
|
const msg: string[] = [];
|
||||||
if (ctx.from) {
|
if (ctx.from) {
|
||||||
|
|
Loading…
Reference in New Issue