Skip to content

Commit

Permalink
Merge pull request #112 from erictik/describe
Browse files Browse the repository at this point in the history
add describe api
  • Loading branch information
zcpua authored Jun 13, 2023
2 parents 256b506 + 5afd29d commit e1c9ab0
Show file tree
Hide file tree
Showing 13 changed files with 297 additions and 38 deletions.
12 changes: 2 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,19 +96,11 @@ Then, run the example with the following command:
npx tsx example/imagine-ws.ts
```

```bash
npx tsx example/upscale-ws.ts
```

```bash
npx tsx example/variation-ws.ts
```


## route-map
- [x] websocket get message
- [x] call back error
- [ ] add `/info` `/fast` and `/relax`
- [x] add `/info` `/fast` and `/relax`
- [ ] `/describe`



Expand Down
28 changes: 28 additions & 0 deletions example/describe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import "dotenv/config";
import { Midjourney } from "../src";
/**
*
* a simple example of using the describe api
* ```
* npx tsx example/describe.ts
* ```
*/
async function main() {
const client = new Midjourney({
ServerId: <string>process.env.SERVER_ID,
ChannelId: <string>process.env.CHANNEL_ID,
SalaiToken: <string>process.env.SALAI_TOKEN,
Debug: true,
Ws: true,
});
await client.init();
const msg = await client.Describe(
"https://img.ohdat.io/midjourney-image/1b74cab8-70c9-474e-bfbb-093e9a3cfd5c/0_1.png"
);
console.log({ msg });
}
main().catch((err) => {
console.log("finished");
console.error(err);
process.exit(1);
});
35 changes: 31 additions & 4 deletions example/imagine-ws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,40 @@ async function main() {
Ws: true,
});
await client.init();
const msg = await client.Imagine(
"the queen of the underworld, race: vampire, appearance: delicate features with detailed portrayal, super exquisite facial features, silver long hair reaching ankles, silver pupils, fair skin with a hint of melancholy in the eyes, beautiful and noble, clothing: wearing a blood-red rose on the hair, skirt with layers of lace, sitting in a (pose), captured in ultra-high resolution, film-like realism, 8k for the best visual quality, super clear and finely drawn. --ar 9:16 --v 5",
const Imagine = await client.Imagine(
"A little white elephant",
(uri: string, progress: string) => {
console.log("loading", uri, "progress", progress);
console.log("Imagine.loading", uri, "progress", progress);
}
);
console.log({ msg });
console.log({ Imagine });
if (!Imagine) {
return;
}
const Variation = await client.Variation(
Imagine.content,
2,
<string>Imagine.id,
<string>Imagine.hash,
(uri: string, progress: string) => {
console.log("Variation.loading", uri, "progress", progress);
}
);

console.log({ Variation });
if (!Variation) {
return;
}
const Upscale = await client.Upscale(
Variation.content,
2,
<string>Variation.id,
<string>Variation.hash,
(uri: string, progress: string) => {
console.log("Upscale.loading", uri, "progress", progress);
}
);
console.log({ Upscale });
}
main()
.then(() => {
Expand Down
2 changes: 1 addition & 1 deletion example/imagine-info.ts → example/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Midjourney } from "../src";
*
* a simple example of using the info api
* ```
* npx tsx example/imagine-info.ts
* npx tsx example/info.ts
* ```
*/
async function main() {
Expand Down
21 changes: 0 additions & 21 deletions example/message.ts

This file was deleted.

19 changes: 19 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
},
"homepage": "https://github.com/erictik/midjourney-api#readme",
"devDependencies": {
"@types/mime": "^3.0.1",
"@types/node": "^18.16.0",
"@types/node-fetch": "^2.6.4",
"@types/ws": "^8.5.4",
Expand All @@ -47,6 +48,7 @@
"@huggingface/inference": "^2.5.0",
"https-proxy-agent": "^7.0.0",
"isomorphic-ws": "^5.0.0",
"mime": "^3.0.0",
"node-fetch": "^2.6.11",
"p-queue": "^6.6.2",
"snowyflake": "^2.0.0",
Expand Down
16 changes: 16 additions & 0 deletions src/interfaces/discord.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export type UploadParam = {
filename: string;
file_size: number;
id: number | string;
};

export type UploadSlot = {
id: number;
upload_filename: string;
upload_url: string;
};
export type DiscordImage = {
id: number | string;
filename: string;
upload_filename: string;
};
1 change: 1 addition & 0 deletions src/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./message";
export * from "./config";
export * from "./discord";
143 changes: 142 additions & 1 deletion src/midjourne.api.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { MJConfig } from "./interfaces";
import { DiscordImage, MJConfig, UploadParam, UploadSlot } from "./interfaces";
import { CreateQueue } from "./queue";
import { nextNonce, sleep } from "./utls";
import fetch from "node-fetch";
import { HttpsProxyAgent } from "https-proxy-agent";
import * as fs from "fs";
import path from "path";
import * as mime from "mime";

export class MidjourneyApi {
private ApiQueue = CreateQueue(1);
agent?: HttpsProxyAgent<string>;
upId = Date.now() % 10; // upload id
constructor(public config: MJConfig) {
if (this.config.ProxyUrl && this.config.ProxyUrl !== "") {
this.agent = new HttpsProxyAgent(this.config.ProxyUrl);
Expand Down Expand Up @@ -261,4 +265,141 @@ export class MidjourneyApi {
};
return this.safeIteractions(payload);
}

async UploadImage(fileUrl: string) {
let fileData;
let mimeType;
let filename;
let file_size;

if (fileUrl.startsWith("http")) {
const response = await fetch(fileUrl);
fileData = await response.arrayBuffer();
mimeType = response.headers.get("content-type");
filename = path.basename(fileUrl) || "image.png";
file_size = fileData.byteLength;
} else {
fileData = await fs.promises.readFile(fileUrl);
mimeType = mime.getType(fileUrl);
filename = path.basename(fileUrl);
file_size = (await fs.promises.stat(fileUrl)).size;
}
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 response: DiscordImage = {
id: UploadSlot.id,
filename: path.basename(UploadSlot.upload_filename),
upload_filename: UploadSlot.upload_filename,
};
return response;
}

/**
* prepare an attachement to upload an image.
*/
private async attachments(
...files: UploadParam[]
): Promise<{ attachments: UploadSlot[] }> {
const headers = {
Authorization: this.config.SalaiToken,
"content-type": "application/json",
};
const url = new URL(
`${this.config.DiscordBaseUrl}/api/v9/channels/${this.config.ChannelId}/attachments`
);
const body = { files };
const response = await fetch(url.toString(), {
headers,
method: "POST",
body: JSON.stringify(body),
});
if (response.status === 200) {
return (await response.json()) as { attachments: UploadSlot[] };
}
throw new Error(
`Attachments return ${response.status} ${
response.statusText
} ${await response.text()}`
);
}

/**
* Upload an image to an upload slot provided by the attachments function.
* @param slot use uploadUrl to put the image
* @returns
*/
private async uploadImage(
slot: UploadSlot,
data: ArrayBuffer,
contentType: string
): Promise<void> {
const body = new Uint8Array(data);
const headers = { "content-type": contentType };
const response = await fetch(slot.upload_url, {
method: "PUT",
headers,
body,
});
if (!response.ok) {
throw new Error(
`uploadImage return ${response.status} ${
response.statusText
} ${await response.text()}`
);
}
}

async DescribeApi(data: DiscordImage, nonce?: string) {
const payload = {
type: 2,
application_id: "936929561302675456",
guild_id: this.config.ServerId,
channel_id: this.config.ChannelId,
session_id: this.config.SessionId,
data: {
version: "1092492867185950853",
id: "1092492867185950852",
name: "describe",
type: 1,
options: [{ type: 11, name: "image", value: data.id }],
application_command: {
id: "1092492867185950852",
application_id: "936929561302675456",
version: "1092492867185950853",
default_member_permissions: null,
type: 1,
nsfw: false,
name: "describe",
description: "Writes a prompt based on your image.",
dm_permission: true,
contexts: null,
options: [
{
type: 11,
name: "image",
description: "The image to describe",
required: true,
},
],
},
attachments: [
{
id: <string>data.id,
filename: data.filename,
uploaded_filename: data.upload_filename,
},
],
},
nonce,
};
return this.safeIteractions(payload);
}
}
13 changes: 13 additions & 0 deletions src/midjourney.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,19 @@ export class Midjourney extends MidjourneyMessage {
}
return null;
}
async Describe(imgUri: string) {
const nonce = nextNonce();
const DcImage = await this.MJApi.UploadImage(imgUri);
this.log(`Describe`, DcImage, "nonce", nonce);
const httpStatus = await this.MJApi.DescribeApi(DcImage, nonce);
if (httpStatus !== 204) {
throw new Error(`DescribeApi failed with status ${httpStatus}`);
}
if (this.wsClient) {
return this.wsClient.waitDescribe(nonce);
}
return null;
}

async Variation(
content: string,
Expand Down
Loading

0 comments on commit e1c9ab0

Please sign in to comment.