diff --git a/example/variation-ws.ts b/example/variation-ws.ts index 4ebac4c..9e473f1 100644 --- a/example/variation-ws.ts +++ b/example/variation-ws.ts @@ -16,7 +16,8 @@ async function main() { Ws: true, //enable ws is required for remix mode }); await client.init(); //init auto enable remix mode - const prompt = "the queen of the underworld, race"; + const prompt = + "48 year old woman with auburn hair plays video games on a tablet in her bedroom and is a chemist. Engaged. Happy. Evening. Silver blue walls in room. In the style of anime. does not exceed 10 MB."; const Imagine = await client.Imagine( prompt, (uri: string, progress: string) => { @@ -29,42 +30,55 @@ async function main() { return; } const Variation = await client.Variation({ - index: 2, + index: 1, msgId: Imagine.id, hash: Imagine.hash, flags: Imagine.flags, content: prompt, loading: (uri: string, progress: string) => { - console.log("Variation2.loading", uri, "progress", progress); + console.log("Variation1.loading", uri, "progress", progress); }, }); console.log("Variation", Variation); - // await client - // .Variation({ - // index: 2, - // msgId: Imagine.id, - // hash: Imagine.hash, - // flags: Imagine.flags, - // loading: (uri: string, progress: string) => { - // console.log("Variation2.loading", uri, "progress", progress); - // }, - // }) - // .then((msg2) => { - // console.log({ msg2 }); - // }); - // client - // .Variation({ - // index: 3, - // msgId: Imagine.id, - // hash: Imagine.hash, - // flags: Imagine.flags, - // loading: (uri: string, progress: string) => { - // console.log("Variation3.loading", uri, "progress", progress); - // }, - // }) - // .then((msg3) => { - // console.log({ msg3 }); - // }); + client + .Variation({ + index: 2, + msgId: Imagine.id, + hash: Imagine.hash, + flags: Imagine.flags, + loading: (uri: string, progress: string) => { + console.log("Variation2.loading", uri, "progress", progress); + }, + }) + .then((msg2) => { + console.log({ msg2 }); + }); + client + .Variation({ + index: 3, + msgId: Imagine.id, + hash: Imagine.hash, + flags: Imagine.flags, + loading: (uri: string, progress: string) => { + console.log("Variation3.loading", uri, "progress", progress); + }, + }) + .then((msg3) => { + console.log({ msg3 }); + }); + client + .Variation({ + index: 4, + msgId: Imagine.id, + hash: Imagine.hash, + flags: Imagine.flags, + loading: (uri: string, progress: string) => { + console.log("Variation4.loading", uri, "progress", progress); + }, + }) + .then((msg4) => { + console.log({ msg4 }); + }); } main().catch((err) => { console.error(err); diff --git a/package.json b/package.json index e5fe287..8997014 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ }, "homepage": "https://github.com/erictik/midjourney-client#readme", "devDependencies": { + "@types/async": "^3.2.20", "@types/node": "^18.16.0", "@types/ws": "^8.5.4", "dotenv": "^16.0.3", @@ -44,10 +45,10 @@ }, "dependencies": { "@huggingface/inference": "^2.5.0", + "async": "^3.2.4", "isomorphic-ws": "^5.0.0", - "p-queue": "^6.6.2", "snowyflake": "^2.0.0", "tslib": "^2.5.0", "ws": "^8.13.0" } -} \ No newline at end of file +} diff --git a/src/discord.message.ts b/src/discord.message.ts index 0062b26..b0395fa 100644 --- a/src/discord.message.ts +++ b/src/discord.message.ts @@ -5,11 +5,10 @@ import { MJConfig, MJConfigParam, } from "./interfaces"; -import { CreateQueue } from "./queue"; import { formatOptions, sleep } from "./utls"; +import async from "async"; export class MidjourneyMessage { - private magApiQueue = CreateQueue(1); public config: MJConfig; constructor(defaults: MJConfigParam) { const { SalaiToken } = defaults; @@ -21,6 +20,38 @@ export class MidjourneyMessage { ...defaults, }; } + private safeRetrieveMessages = (request = 50) => { + return new Promise((resolve, reject) => { + this.queue.push( + { + request, + callback: (any: any) => { + resolve(any); + }, + }, + (error: any, result: any) => { + if (error) { + reject(error); + } else { + resolve(result); + } + } + ); + }); + }; + private processRequest = async ({ + request, + callback, + }: { + request: any; + callback: (any: any) => void; + }) => { + const httpStatus = await this.RetrieveMessages(request); + callback(httpStatus); + await sleep(this.config.ApiInterval); + }; + private queue = async.queue(this.processRequest, 1); + protected log(...args: any[]) { this.config.Debug && console.log(...args, new Date().toISOString()); } @@ -109,10 +140,6 @@ export class MidjourneyMessage { return null; } - // limit the number of concurrent interactions - protected async safeRetrieveMessages(limit = 50) { - return this.magApiQueue.addTask(() => this.RetrieveMessages(limit)); - } async RetrieveMessages(limit = this.config.Limit) { const headers = { "Content-Type": "application/json", diff --git a/src/interfaces/config.ts b/src/interfaces/config.ts index 134b6d9..232b4df 100644 --- a/src/interfaces/config.ts +++ b/src/interfaces/config.ts @@ -7,7 +7,7 @@ export const NijiBot = "1022952195194359889"; export interface MJConfig { ChannelId: string; SalaiToken: string; - BotId: typeof MJBot | typeof NijiBot; + BotId: typeof MJBot | typeof NijiBot; Debug: boolean; Limit: number; MaxWait: number; @@ -19,29 +19,32 @@ export interface MJConfig { DiscordBaseUrl: string; WsBaseUrl: string; fetch: FetchFn; + ApiInterval: number; WebSocket: WebSocketCl; } export interface MJConfigParam { - SalaiToken: string; - ChannelId?: string; - BotId?: typeof MJBot | typeof NijiBot; - Debug?: boolean; - Limit?: number; + SalaiToken: string; //DISCORD_TOKEN + ChannelId?: string; //DISCORD_CHANNEL_ID + ServerId?: string; //DISCORD_SERVER_ID + BotId?: typeof MJBot | typeof NijiBot; //DISCORD_BOT_ID MJBot OR NijiBot + Debug?: boolean; // print log + ApiInterval?: number; //ApiInterval request api interval + Limit?: number; //Limit of get message list MaxWait?: number; - Ws?: boolean; - HuggingFaceToken?: string; - ServerId?: string; + Ws?: boolean; //Ws:true use websocket get discord message (ephemeral message) + HuggingFaceToken?: string; //HuggingFaceToken for verify human SessionId?: string; DiscordBaseUrl?: string; WsBaseUrl?: string; - fetch?: FetchFn; - WebSocket?: WebSocketCl; + fetch?: FetchFn; //Node.js<18 need node.fetch Or proxy + WebSocket?: WebSocketCl; //isomorphic-ws Or proxy } export const DefaultMJConfig: MJConfig = { BotId: MJBot, ChannelId: "1077800642086703114", SalaiToken: "", + ApiInterval: 350, SessionId: "8bb7f5b79c7a49f7d0824ab4b8773a81", Debug: false, Limit: 50, diff --git a/src/midjourne.api.ts b/src/midjourne.api.ts index 2616332..a6344bb 100644 --- a/src/midjourne.api.ts +++ b/src/midjourne.api.ts @@ -9,31 +9,49 @@ import { UploadParam, UploadSlot, } from "./interfaces"; -import { CreateQueue } from "./queue"; + import { nextNonce, sleep } from "./utls"; -import path from "path"; import { Command } from "./command"; +import async from "async"; +import path from "path"; + export class MidjourneyApi extends Command { - private apiQueue = CreateQueue(1); UpId = Date.now() % 10; // upload id constructor(public config: MJConfig) { super(config); } - // limit the number of concurrent interactions - protected async safeIteractions(payload: any) { - return this.apiQueue.addTask( - () => - new Promise((resolve) => { - this.interactions(payload, (res) => { - resolve(res); - }); - }) - ); - } - protected async interactions( - payload: any, - callback?: (result: number) => void - ) { + private safeIteractions = (request: any) => { + return new Promise((resolve, reject) => { + this.queue.push( + { + request, + callback: (any: any) => { + resolve(any); + }, + }, + (error: any, result: any) => { + if (error) { + reject(error); + } else { + resolve(result); + } + } + ); + }); + }; + private processRequest = async ({ + request, + callback, + }: { + request: any; + callback: (any: any) => void; + }) => { + const httpStatus = await this.interactions(request); + callback(httpStatus); + await sleep(this.config.ApiInterval); + }; + private queue = async.queue(this.processRequest, 1); + private interactions = async (payload: any) => { try { const headers = { "Content-Type": "application/json", @@ -53,19 +71,18 @@ export class MidjourneyApi extends Command { config: this.config, }); } - callback && callback(response.status); - //discord api rate limit - await sleep(950); return response.status; } catch (error) { console.error(error); - callback && callback(500); + return 500; } - } + }; + async ImagineApi(prompt: string, nonce: string = nextNonce()) { const payload = await this.imaginePayload(prompt, nonce); return this.safeIteractions(payload); } + async SwitchRemixApi(nonce: string = nextNonce()) { const payload = await this.PreferPayload(nonce); return this.safeIteractions(payload); @@ -75,6 +92,7 @@ export class MidjourneyApi extends Command { const payload = await this.shortenPayload(prompt, nonce); return this.safeIteractions(payload); } + async VariationApi({ index, msgId, @@ -95,6 +113,7 @@ export class MidjourneyApi extends Command { nonce, }); } + async UpscaleApi({ index, msgId, @@ -115,6 +134,7 @@ export class MidjourneyApi extends Command { nonce, }); } + async RerollApi({ msgId, hash, @@ -163,6 +183,7 @@ export class MidjourneyApi extends Command { }; return this.safeIteractions(payload); } + //FIXME: get SubmitCustomId from discord api async ModalSubmitApi({ nonce, @@ -204,6 +225,7 @@ export class MidjourneyApi extends Command { console.log("submitCustomId", JSON.stringify(payload)); return this.safeIteractions(payload); } + async RemixApi({ nonce, msgId, @@ -223,6 +245,7 @@ export class MidjourneyApi extends Command { submitCustomId: RemixModalSubmitID, }); } + async ShortenImagineApi({ nonce, msgId, @@ -291,18 +314,22 @@ export class MidjourneyApi extends Command { const payload = await this.infoPayload(nonce); return this.safeIteractions(payload); } + async SettingsApi(nonce?: string) { const payload = await this.settingsPayload(nonce); return this.safeIteractions(payload); } + async FastApi(nonce?: string) { const payload = await this.fastPayload(nonce); return this.safeIteractions(payload); } + async RelaxApi(nonce?: string) { const payload = await this.relaxPayload(nonce); return this.safeIteractions(payload); } + /** * * @param fileUrl http file path @@ -331,28 +358,7 @@ export class MidjourneyApi extends Command { }; return resp; } - async UploadImageByFile(file: File) { - const fileData = await file.arrayBuffer(); - const mimeType = file.type; - const filename = file.name || "image.png"; - const file_size = fileData.byteLength; - if (!mimeType) { - throw new Error("Unknown mime type"); - } - const { attachments } = await this.attachments({ - filename, - file_size, - id: this.UpId++, - }); - const UploadSlot = attachments[0]; - await this.uploadImage(UploadSlot, fileData, mimeType); - const resp: DiscordImage = { - id: UploadSlot.id, - filename: path.basename(UploadSlot.upload_filename), - upload_filename: UploadSlot.upload_filename, - }; - return resp; - } + async UploadImageByBole(blob: Blob, filename = "image.png") { const fileData = await blob.arrayBuffer(); const mimeType = blob.type; @@ -403,6 +409,7 @@ export class MidjourneyApi extends Command { } ${await response.text()}`; throw new Error(error); } + private async uploadImage( slot: UploadSlot, data: ArrayBuffer, @@ -423,6 +430,7 @@ export class MidjourneyApi extends Command { ); } } + async DescribeApi(image: DiscordImage, nonce?: string) { const payload = await this.describePayload(image, nonce); return this.safeIteractions(payload); diff --git a/src/queue.ts b/src/queue.ts deleted file mode 100644 index a513373..0000000 --- a/src/queue.ts +++ /dev/null @@ -1,54 +0,0 @@ -import PQueue from "p-queue"; - -class ConcurrentQueue { - private queue: (() => Promise)[] = []; - private limit: any; - - constructor(concurrency: number) { - this.limit = new PQueue({ concurrency }); - } - - public getWaiting(): number { - return this.queue.length; - } - - public async addTask(task: () => Promise): Promise { - return await this.limit.add(async () => { - const result = await task(); - return result; - }); - } - - public async getResults(): Promise { - return Promise.allSettled( - this.queue.map((task) => { - return task().catch((err) => err); - }) - ); - } -} -export function CreateQueue(concurrency: number) { - return new ConcurrentQueue(5); -} - -// // Usage example: -// const queue = new ConcurrentQueue(5); - -// for (let i = 0; i < 10; i++) { -// queue.addTask(() => -// new Promise((resolve, reject) => { -// setTimeout(() => { -// console.log('Task done:', i); -// resolve(i * 2); -// }, Math.random() * 1000); -// }) -// ); -// } - -// console.log('Tasks waiting:', queue.getWaiting()); - -// setTimeout(() => { -// queue.getResults().then((results) => { -// console.log('Results:', results); -// }); -// }, 5000); diff --git a/test/test2.ts b/test/test2.ts new file mode 100644 index 0000000..9188c19 --- /dev/null +++ b/test/test2.ts @@ -0,0 +1,63 @@ +import async from "async"; +/** + * + * a simple example of how to use the imagine command + * ``` + * npx tsx test/test2.ts + * ``` + */ + +const processRequest = async ({ + request, + callback, +}: { + request: any; + callback: (any) => void; +}) => { + // 在这里执行实际的HTTP请求 + // 可以使用任何HTTP库,比如axios或node-fetch + // console.log("Request processed:", request, new Date()); + + // 这里只是一个示例,使用setTimeout模拟一个异步请求 + await new Promise((resolve) => setTimeout(resolve, 1000)); + callback(request + " processed"); + await new Promise((resolve) => setTimeout(resolve, 2000)); + return "sleep processed"; +}; +const queue = async.queue(processRequest, 1); +const addRequest = (request: any) => { + console.log("Request queued:", request, new Date()); + return new Promise((resolve, reject) => { + queue.push( + { + request, + callback: (any) => { + resolve(any); + }, + }, + (error: any, result: any) => { + if (error) { + reject(error); + } else { + resolve(result); + } + } + ); + }); +}; + +const safeRequest = async (request: any) => { + const dd = await addRequest(request); + return dd + " safeRequest" + new Date(); +}; +async function test2() { + safeRequest("1").then((result) => console.log(result)); + safeRequest("2").then((result) => console.log(result)); + safeRequest("3").then((result) => console.log(result)); + safeRequest("4").then((result) => console.log(result)); + safeRequest("5").then((result) => console.log(result)); + safeRequest("6").then((result) => console.log(result)); + const dd = await safeRequest("32"); + console.log(dd); +} +test2(); diff --git a/yarn.lock b/yarn.lock index 746941a..d3ba752 100644 --- a/yarn.lock +++ b/yarn.lock @@ -186,6 +186,11 @@ resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== +"@types/async@^3.2.20": + version "3.2.20" + resolved "https://registry.yarnpkg.com/@types/async/-/async-3.2.20.tgz#53517caaa68c94f99da1c4e986cf7f2954981515" + integrity sha512-6jSBQQugzyX1aWto0CbvOnmxrU9tMoXfA9gc4IrLEtvr3dTwSg5GLGoWiZnGLI6UG/kqpB3JOQKQrqnhUWGKQA== + "@types/node@*", "@types/node@^18.16.0": version "18.16.0" resolved "https://registry.npmjs.org/@types/node/-/node-18.16.0.tgz" @@ -213,6 +218,11 @@ arg@^4.1.0: resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== +async@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" + integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" @@ -261,11 +271,6 @@ esbuild@~0.17.6: "@esbuild/win32-ia32" "0.17.18" "@esbuild/win32-x64" "0.17.18" -eventemitter3@^4.0.4: - version "4.0.7" - resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - fsevents@~2.3.2: version "2.3.2" resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" @@ -286,26 +291,6 @@ make-error@^1.1.1: resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz" - integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== - -p-queue@^6.6.2: - version "6.6.2" - resolved "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz" - integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== - dependencies: - eventemitter3 "^4.0.4" - p-timeout "^3.2.0" - -p-timeout@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz" - integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== - dependencies: - p-finally "^1.0.0" - prettier@^2.8.8: version "2.8.8" resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz"