UNPKG

relax-mj

Version:

Node.js client for the unofficial MidJourney API.

403 lines (398 loc) 11.1 kB
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); } } // limit the number of concurrent interactions protected async safeIteractions(payload: any) { return this.apiQueue.addTask( () => new Promise<number>((resolve) => { this.interactions(payload, (res) => { resolve(res); }); }) ); } protected async interactions( payload: any, callback?: (result: number) => void ) { try { const headers = { "Content-Type": "application/json", Authorization: this.config.SalaiToken, }; const agent = this.Agent; const response = await fetch( `${this.config.DiscordBaseUrl}/api/v9/interactions`, { method: "POST", body: JSON.stringify(payload), headers: headers, agent, } ); callback && callback(response.status); //discord api rate limit await sleep(950); if (response.status >= 400) { console.error("api.error.config", { config: this.config }); } return response.status; } catch (error) { console.error(error); callback && callback(500); } } async ImagineApi(prompt: string, nonce: string = nextNonce()) { const guild_id = this.config.ServerId; const payload = { type: 2, application_id: "936929561302675456", guild_id, channel_id: this.config.ChannelId, session_id: this.config.SessionId, data: { version: "1166847114203123795", id: "938956540159881230", name: "imagine", type: 1, options: [ { type: 3, name: "prompt", value: prompt, }, ], application_command: { id: "938956540159881230", application_id: "936929561302675456", version: "1166847114203123795", default_permission: true, default_member_permissions: null, type: 1, nsfw: false, name: "imagine", description: "Create images with Midjourney", dm_permission: true, options: [ { type: 3, name: "prompt", description: "The prompt to imagine", required: true, }, ], }, attachments: [], }, nonce, }; return this.safeIteractions(payload); } async VariationApi( index: number, messageId: string, messageHash: string, nonce?: string ) { const payload = { type: 3, guild_id: this.config.ServerId, channel_id: this.config.ChannelId, message_flags: 0, message_id: messageId, application_id: "936929561302675456", session_id: this.config.SessionId, data: { component_type: 2, custom_id: `MJ::JOB::variation::${index}::${messageHash}`, }, nonce, }; return this.safeIteractions(payload); } async UpscaleApi( index: number, messageId: string, messageHash: string, nonce?: string ) { const guild_id = this.config.ServerId; const payload = { type: 3, guild_id, channel_id: this.config.ChannelId, message_flags: 0, message_id: messageId, application_id: "936929561302675456", session_id: this.config.SessionId, data: { component_type: 2, custom_id: `MJ::JOB::upsample::${index}::${messageHash}`, }, nonce, }; return this.safeIteractions(payload); } async ClickBtnApi(messageId: string, customId: string, nonce?: string) { const guild_id = this.config.ServerId; const payload = { type: 3, nonce, guild_id, channel_id: this.config.ChannelId, message_flags: 0, message_id: messageId, application_id: "936929561302675456", session_id: this.config.SessionId, data: { component_type: 2, custom_id: customId, }, }; return this.safeIteractions(payload); } async InfoApi(nonce?: string) { const guild_id = this.config.ServerId; const payload = { type: 2, application_id: "936929561302675456", guild_id, channel_id: this.config.ChannelId, session_id: this.config.SessionId, data: { version: "987795925764280356", id: "972289487818334209", name: "info", type: 1, options: [], application_command: { id: "972289487818334209", application_id: "936929561302675456", version: "987795925764280356", default_member_permissions: null, type: 1, nsfw: false, name: "info", description: "View information about your profile.", dm_permission: true, contexts: null, }, attachments: [], }, nonce, }; return this.safeIteractions(payload); } async FastApi(nonce?: string) { const guild_id = this.config.ServerId; const payload = { type: 2, application_id: "936929561302675456", guild_id, channel_id: this.config.ChannelId, session_id: this.config.SessionId, data: { version: "987795926183731231", id: "972289487818334212", name: "fast", type: 1, options: [], application_command: { id: "972289487818334212", application_id: "936929561302675456", version: "987795926183731231", default_member_permissions: null, type: 1, nsfw: false, name: "fast", description: "Switch to fast mode", dm_permission: true, contexts: null, }, attachments: [], }, nonce, }; return this.safeIteractions(payload); } async RelaxApi(nonce?: string) { const guild_id = this.config.ServerId; const channel_id = this.config.ChannelId; const payload = { type: 2, application_id: "936929561302675456", guild_id, channel_id, session_id: this.config.SessionId, data: { version: "987795926183731232", id: "972289487818334213", name: "relax", type: 1, options: [], application_command: { id: "972289487818334213", application_id: "936929561302675456", version: "987795926183731232", default_member_permissions: null, type: 1, nsfw: false, name: "relax", description: "Switch to relax mode", dm_permission: true, contexts: null, }, attachments: [], }, nonce, }; return this.safeIteractions(payload); } /** * * @param fileUrl http or local file path * @returns */ 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()}` ); } 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); } }