midjourney
Version:
Node.js client for the unofficial MidJourney API.
405 lines (392 loc) • 10.7 kB
text/typescript
import {
DefaultMJConfig,
LoadingHandler,
MJConfig,
MJConfigParam,
} from "./interfaces";
import { MidjourneyApi } from "./midjourney.api";
import { MidjourneyMessage } from "./discord.message";
import {
toRemixCustom,
custom2Type,
nextNonce,
random,
base64ToBlob,
} from "./utils";
import { WsMessage } from "./discord.ws";
import { faceSwap } from "./face.swap";
export class Midjourney extends MidjourneyMessage {
public config: MJConfig;
private wsClient?: WsMessage;
public MJApi: MidjourneyApi;
constructor(defaults: MJConfigParam) {
const { SalaiToken } = defaults;
if (!SalaiToken) {
throw new Error("SalaiToken are required");
}
super(defaults);
this.config = {
...DefaultMJConfig,
...defaults,
};
this.MJApi = new MidjourneyApi(this.config);
}
async Connect() {
if (!this.config.Ws) {
return this;
}
await this.MJApi.allCommand();
if (this.wsClient) return this;
this.wsClient = new WsMessage(this.config, this.MJApi);
await this.wsClient.onceReady();
return this;
}
async init() {
await this.Connect();
const settings = await this.Settings();
if (settings) {
// this.log(`settings:`, settings.content);
const remix = settings.options.find((o) => o.label === "Remix mode");
if (remix?.style == 3) {
this.config.Remix = true;
this.log(`Remix mode enabled`);
}
}
return this;
}
async Imagine(prompt: string, loading?: LoadingHandler) {
prompt = prompt.trim();
if (!this.config.Ws) {
const seed = random(1000000000, 9999999999);
prompt = `[${seed}] ${prompt}`;
} else {
await this.getWsClient();
}
const nonce = nextNonce();
this.log(`Imagine`, prompt, "nonce", nonce);
const httpStatus = await this.MJApi.ImagineApi(prompt, nonce);
if (httpStatus !== 204) {
throw new Error(`ImagineApi failed with status ${httpStatus}`);
}
if (this.wsClient) {
return await this.wsClient.waitImageMessage({ nonce, loading, prompt });
} else {
this.log(`await generate image`);
const msg = await this.WaitMessage(prompt, loading);
this.log(`image generated`, prompt, msg?.uri);
return msg;
}
}
// check ws enabled && connect
private async getWsClient() {
if (!this.config.Ws) {
throw new Error(`ws not enabled`);
}
if (!this.wsClient) {
await this.Connect();
}
if (!this.wsClient) {
throw new Error(`ws not connected`);
}
return this.wsClient;
}
async Settings() {
const wsClient = await this.getWsClient();
const nonce = nextNonce();
const httpStatus = await this.MJApi.SettingsApi(nonce);
if (httpStatus !== 204) {
throw new Error(`ImagineApi failed with status ${httpStatus}`);
}
return wsClient.waitSettings();
}
async Reset() {
const settings = await this.Settings();
if (!settings) {
throw new Error(`Settings not found`);
}
const reset = settings.options.find((o) => o.label === "Reset Settings");
if (!reset) {
throw new Error(`Reset Settings not found`);
}
const httpstatus = await this.MJApi.CustomApi({
msgId: settings.id,
customId: reset.custom,
flags: settings.flags,
});
if (httpstatus !== 204) {
throw new Error(`Reset failed with status ${httpstatus}`);
}
}
async Info() {
const wsClient = await this.getWsClient();
const nonce = nextNonce();
const httpStatus = await this.MJApi.InfoApi(nonce);
if (httpStatus !== 204) {
throw new Error(`InfoApi failed with status ${httpStatus}`);
}
return wsClient.waitInfo();
}
async Fast() {
const nonce = nextNonce();
const httpStatus = await this.MJApi.FastApi(nonce);
if (httpStatus !== 204) {
throw new Error(`FastApi failed with status ${httpStatus}`);
}
return null;
}
async Relax() {
const nonce = nextNonce();
const httpStatus = await this.MJApi.RelaxApi(nonce);
if (httpStatus !== 204) {
throw new Error(`RelaxApi failed with status ${httpStatus}`);
}
return null;
}
async SwitchRemix() {
const wsClient = await this.getWsClient();
const nonce = nextNonce();
const httpStatus = await this.MJApi.SwitchRemixApi(nonce);
if (httpStatus !== 204) {
throw new Error(`RelaxApi failed with status ${httpStatus}`);
}
return wsClient.waitContent("prefer-remix");
}
async Describe(imgUri: string) {
const wsClient = await this.getWsClient();
const nonce = nextNonce();
const DcImage = await this.MJApi.UploadImageByUri(imgUri);
this.log(`Describe`, DcImage);
const httpStatus = await this.MJApi.DescribeApi(DcImage, nonce);
if (httpStatus !== 204) {
throw new Error(`DescribeApi failed with status ${httpStatus}`);
}
return wsClient.waitDescribe(nonce);
}
async DescribeByBlob(blob: Blob) {
const wsClient = await this.getWsClient();
const nonce = nextNonce();
const DcImage = await this.MJApi.UploadImageByBole(blob);
this.log(`Describe`, DcImage);
const httpStatus = await this.MJApi.DescribeApi(DcImage, nonce);
if (httpStatus !== 204) {
throw new Error(`DescribeApi failed with status ${httpStatus}`);
}
return wsClient.waitDescribe(nonce);
}
async Shorten(prompt: string) {
const wsClient = await this.getWsClient();
const nonce = nextNonce();
const httpStatus = await this.MJApi.ShortenApi(prompt, nonce);
if (httpStatus !== 204) {
throw new Error(`ShortenApi failed with status ${httpStatus}`);
}
return wsClient.waitShorten(nonce);
}
async Variation({
index,
msgId,
hash,
content,
flags,
loading,
}: {
index: 1 | 2 | 3 | 4;
msgId: string;
hash: string;
content?: string;
flags: number;
loading?: LoadingHandler;
}) {
return await this.Custom({
customId: `MJ::JOB::variation::${index}::${hash}`,
msgId,
content,
flags,
loading,
});
}
async Upscale({
index,
msgId,
hash,
content,
flags,
loading,
}: {
index: 1 | 2 | 3 | 4;
msgId: string;
hash: string;
content?: string;
flags: number;
loading?: LoadingHandler;
}) {
return await this.Custom({
customId: `MJ::JOB::upsample::${index}::${hash}`,
msgId,
content,
flags,
loading,
});
}
async Custom({
msgId,
customId,
content,
flags,
loading,
}: {
msgId: string;
customId: string;
content?: string;
flags: number;
loading?: LoadingHandler;
}) {
if (this.config.Ws) {
await this.getWsClient();
}
const nonce = nextNonce();
const httpStatus = await this.MJApi.CustomApi({
msgId,
customId,
flags,
nonce,
});
if (httpStatus !== 204) {
throw new Error(`CustomApi failed with status ${httpStatus}`);
}
if (this.wsClient) {
return await this.wsClient.waitImageMessage({
nonce,
loading,
messageId: msgId,
prompt: content,
onmodal: async (nonde, id) => {
if (content === undefined || content === "") {
return "";
}
const newNonce = nextNonce();
switch (custom2Type(customId)) {
case "customZoom":
const httpStatus = await this.MJApi.CustomZoomImagineApi({
msgId: id,
customId,
prompt: content,
nonce: newNonce,
});
if (httpStatus !== 204) {
throw new Error(
`CustomZoomImagineApi failed with status ${httpStatus}`
);
}
return newNonce;
case "variation":
if (this.config.Remix !== true) {
return "";
}
customId = toRemixCustom(customId);
const remixHttpStatus = await this.MJApi.RemixApi({
msgId: id,
customId,
prompt: content,
nonce: newNonce,
});
if (remixHttpStatus !== 204) {
throw new Error(
`RemixApi failed with status ${remixHttpStatus}`
);
}
return newNonce;
default:
return "";
throw new Error(`unknown customId ${customId}`);
}
},
});
}
if (content === undefined || content === "") {
throw new Error(`content is required`);
}
return await this.WaitMessage(content, loading);
}
async ZoomOut({
level,
msgId,
hash,
content,
flags,
loading,
}: {
level: "high" | "low" | "2x" | "1.5x";
msgId: string;
hash: string;
content?: string;
flags: number;
loading?: LoadingHandler;
}) {
let customId: string;
switch (level) {
case "high":
customId = `MJ::JOB::high_variation::1::${hash}::SOLO`;
break;
case "low":
customId = `MJ::JOB::low_variation::1::${hash}::SOLO`;
break;
case "2x":
customId = `MJ::Outpaint::50::1::${hash}::SOLO`;
break;
case "1.5x":
customId = `MJ::Outpaint::75::1::${hash}::SOLO`;
break;
}
return this.Custom({
msgId,
customId,
content,
flags,
loading,
});
}
async Reroll({
msgId,
hash,
content,
flags,
loading,
}: {
msgId: string;
hash: string;
content?: string;
flags: number;
loading?: LoadingHandler;
}) {
return await this.Custom({
customId: `MJ::JOB::reroll::0::${hash}::SOLO`,
msgId,
content,
flags,
loading,
});
}
async FaceSwap(target: string, source: string) {
const wsClient = await this.getWsClient();
const app = new faceSwap(this.config.HuggingFaceToken);
const Target = await (await this.config.fetch(target)).blob();
const Source = await (await this.config.fetch(source)).blob();
const res = await app.changeFace(Target, Source);
this.log(res[0]);
const blob = await base64ToBlob(res[0] as string);
const DcImage = await this.MJApi.UploadImageByBole(blob);
const nonce = nextNonce();
const httpStatus = await this.MJApi.DescribeApi(DcImage, nonce);
if (httpStatus !== 204) {
throw new Error(`DescribeApi failed with status ${httpStatus}`);
}
return wsClient.waitDescribe(nonce);
}
Close() {
if (this.wsClient) {
this.wsClient.close();
this.wsClient = undefined;
}
}
}