mjdapi
Version:
Midjourney client using Discord.
667 lines (666 loc) • 25.4 kB
JavaScript
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _DiscordMessage_client;
//TYPES IMPORTS
import * as dntShim from "../_dnt.shims.js";
// CODE imports
import { ButtonStyle, logger, path, pc } from "../deps.js";
import { downloadFileCached, REROLL } from "./utils.js";
const progressionQueue = -1;
const progressionStoped = -1;
export function extractPrompt(content, id) {
if (!content) {
return undefined;
}
const result = {
// id?: string;
// mode?: "fast" | "relaxed";
// completion?: number; // 0..1
// completion: -1,
source: "",
prompt: "",
name: "",
};
let extra = content;
// remove useless staff
extra = extra.split(' [image here](https://')[0];
/**
* speed first
*/
if (extra.endsWith(" (fast)")) {
result.mode = "fast";
extra = extra.substring(0, extra.length - 7);
}
else if (extra.endsWith(" (relaxed)")) {
result.mode = "relaxed";
extra = extra.substring(0, extra.length - 10);
}
else if (extra.endsWith(" (fast, stealth)")) {
result.mode = "fast, stealth";
extra = extra.substring(0, extra.length - 16);
}
else if (extra.endsWith(" (relaxed, stealth)")) {
result.mode = "relaxed, stealth";
extra = extra.substring(0, extra.length - 19);
}
/**
* progression
*/
if (extra.endsWith(" (paused)")) {
extra = extra.substring(0, extra.length - 9);
result.completion = progressionQueue;
}
else if (extra.endsWith(" (Stopped)")) {
extra = extra.substring(0, extra.length - 10);
result.completion = progressionStoped;
}
else if (extra.endsWith(" (Open on website for full quality)")) {
extra = extra.substring(0, extra.length - 35);
result.completion = 1;
}
else if (extra.endsWith(" (Waiting to start)")) {
extra = extra.substring(0, extra.length - 19);
result.completion = progressionQueue;
}
else {
const extractPercent = extra.match(/ \(([0-9]+)%\)$/);
if (extractPercent) {
extra = extra.substring(0, extra.length - extractPercent[0].length);
result.completion = parseInt(extractPercent[1]) / 100;
}
}
/**
* prefix
*/
if (extra.startsWith("Making variations for image #")) {
const prefix = extra.match(/^Making variations for image #(\d) with prompt /);
if (prefix) {
result.name = prefix[1];
extra = extra.substring(prefix[0].length);
}
}
const extractId = extra.match(/ <@(\d+)>$/);
if (extractId) {
extra = extra.substring(0, extra.length - extractId[0].length);
result.id = extractId[1];
}
// can be present befor the <@id>
if (extra.endsWith(" Upscaled by")) {
extra = extra.substring(0, extra.length - 12);
if (result.completion === undefined) {
result.completion = 1; // Upscaled are only display at completion
}
}
else if (extra.endsWith(" Variations by")) {
extra = extra.substring(0, extra.length - 14);
if (result.completion === undefined) {
result.completion = 1; // Variations are only display at completion
}
}
else if (extra.endsWith(" Upscaled (Beta) by")) {
extra = extra.substring(0, extra.length - 19);
if (result.completion === undefined) {
result.completion = 1; // Variations are only display at completion
}
}
else if (extra.endsWith(" Upscaled (Max) by")) {
extra = extra.substring(0, extra.length - 18);
if (result.completion === undefined) {
result.completion = 1; // Variations are only display at completion
}
}
else if (extra.endsWith(" Upscaled (Light) by")) {
extra = extra.substring(0, extra.length - 20);
if (result.completion === undefined) {
result.completion = 1; // Variations are only display at completion
}
}
else if (extra.endsWith(" Remix by")) {
extra = extra.substring(0, extra.length - 9);
if (result.completion === undefined) {
result.completion = 1;
}
}
else if (extra.endsWith(" Remaster by")) {
extra = extra.substring(0, extra.length - 12);
if (result.completion === undefined) {
result.completion = 1;
}
}
else if (extra.endsWith("** -")) {
if (result.completion === undefined) {
result.completion = 1; // imagen on blend
}
}
{
const tailImage = extra.match(/ Image #(\d)$/);
if (tailImage) {
extra = extra.substring(0, extra.length - tailImage[0].length);
result.source = tailImage[0].trim();
result.completion = 1; // upscall have no progress
}
}
if (extra.endsWith("** -")) {
extra = extra.substring(0, extra.length - 2);
}
if (extra.startsWith("**") && extra.endsWith("**") && extra.length > 4) {
result.prompt = extra.substring(2, extra.length - 2);
return result;
}
// if (id === "936929561302675456") {
// logger.warn(`Failed to extract prompt data from: ${pc.yellow(content)}`);
// logger.warn(`Extra data:"${pc.yellow(extra)}"`);
// }
}
export class DiscordMessage {
constructor(client, source) {
/**
* ID of the message
*/
Object.defineProperty(this, "id", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* ID of the channel the message was sent in
*/
Object.defineProperty(this, "channel_id", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* The author of this message (only a valid user in the case where the message is generated by a user or bot user)
*
* If the message is generated by a webhook, the author object corresponds to the webhook's id,
* username, and avatar. You can tell if a message is generated by a webhook by checking for the `webhook_id` property
*
* See https://discord.com/developers/docs/resources/user#user-object
*/
Object.defineProperty(this, "author", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Contents of the message
*
* The `MESSAGE_CONTENT` privileged gateway intent will become required after **August 31, 2022** for verified applications to receive a non-empty value from this field
*
* In the Discord Developers Portal, you need to enable the toggle of this intent of your application in **Bot > Privileged Gateway Intents**
*
* See https://support-dev.discord.com/hc/articles/4404772028055
*/
Object.defineProperty(this, "content", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* When this message was sent
*/
Object.defineProperty(this, "timestamp", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* When this message was edited (or null if never)
*/
Object.defineProperty(this, "edited_timestamp", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Whether this was a TTS message
*/
Object.defineProperty(this, "tts", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Whether this message mentions everyone
*/
Object.defineProperty(this, "mention_everyone", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Users specifically mentioned in the message
*
* The `member` field is only present in `MESSAGE_CREATE` and `MESSAGE_UPDATE` events
* from text-based guild channels
*
* See https://discord.com/developers/docs/resources/user#user-object
* See https://discord.com/developers/docs/resources/guild#guild-member-object
*/
Object.defineProperty(this, "mentions", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
}); // extention from APIUser
/**
* Roles specifically mentioned in this message
*
* See https://discord.com/developers/docs/topics/permissions#role-object
*/
Object.defineProperty(this, "mention_roles", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Any attached files
*
* See https://discord.com/developers/docs/resources/channel#attachment-object
*
* The `MESSAGE_CONTENT` privileged gateway intent will become required after **August 31, 2022** for verified applications to receive a non-empty value from this field
*
* In the Discord Developers Portal, you need to enable the toggle of this intent of your application in **Bot > Privileged Gateway Intents**
*
* See https://support-dev.discord.com/hc/articles/4404772028055
*/
Object.defineProperty(this, "attachments", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Any embedded content
*
* See https://discord.com/developers/docs/resources/channel#embed-object
*
* The `MESSAGE_CONTENT` privileged gateway intent will become required after **August 31, 2022** for verified applications to receive a non-empty value from this field
*
* In the Discord Developers Portal, you need to enable the toggle of this intent of your application in **Bot > Privileged Gateway Intents**
*
* See https://support-dev.discord.com/hc/articles/4404772028055
*/
Object.defineProperty(this, "embeds", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Whether this message is pinned
*/
Object.defineProperty(this, "pinned", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Type of message
*
* See https://discord.com/developers/docs/resources/channel#message-object-message-types
*/
Object.defineProperty(this, "type", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Message flags combined as a bitfield
*
* See https://discord.com/developers/docs/resources/channel#message-object-message-flags
*
* See https://en.wikipedia.org/wiki/Bit_field
*/
Object.defineProperty(this, "flags", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
}); //MessageFlags;
/**
* Sent if the message is a response to an Interaction
*/
Object.defineProperty(this, "interaction", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Sent if a thread was started from this message
*/
Object.defineProperty(this, "thread", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Sent if the message contains components like buttons, action rows, or other interactive components
*
* The `MESSAGE_CONTENT` privileged gateway intent will become required after **August 31, 2022** for verified applications to receive a non-empty value from this field
*
* In the Discord Developers Portal, you need to enable the toggle of this intent of your application in **Bot > Privileged Gateway Intents**
*
* See https://support-dev.discord.com/hc/articles/4404772028055
*/
Object.defineProperty(this, "components", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* The message associated with the `message_reference`
*
* This field is only returned for messages with a `type` of `19` (REPLY).
*
* If the message is a reply but the `referenced_message` field is not present,
* the backend did not attempt to fetch the message that was being replied to,
* so its state is unknown.
*
* If the field exists but is `null`, the referenced message was deleted
*
* See https://discord.com/developers/docs/resources/channel#message-object
*/
Object.defineProperty(this, "referenced_message", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "prompt", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* parent client
*/
_DiscordMessage_client.set(this, void 0);
Object.assign(this, source);
__classPrivateFieldSet(this, _DiscordMessage_client, client, "f");
// handle error prompt
if (source.embeds[0]?.footer?.text && !source.content) {
source.content = `**${source.embeds[0].footer.text.replace(/\/\S+ /, "")}** - <@${source?.interaction?.user?.id}> (Stopped) (fast)`;
}
this.prompt = extractPrompt(source.content, source.author?.id);
this.content = source.content;
if (source.referenced_message) {
this.referenced_message = new DiscordMessage(client, source.referenced_message);
}
// labels: '1️⃣2️⃣3️⃣4️⃣🔄'
// const labels = this.components.map(a => a.label).join('');
// const custom_ids = this.components.map((a) => a.custom_id).join("");
if (source.interaction) {
const name = source.interaction.name;
if (name === "describe") {
if (source.embeds && source.embeds[0]) {
const embeds = source.embeds[0];
if (embeds.image) {
const description = embeds.description || "";
this.prompt = {
source: description,
name: embeds.image.url.replace(/.+\//, ""),
prompt: description,
completion: 1,
};
}
}
else {
// embeds not available yet.
this.prompt = {
source: "",
name: "",
prompt: "",
completion: -1,
};
}
}
else if (name === "imagine") {
// empty
}
else if (name === "blend") {
// empty
}
else {
logger.info("interaction Name: ", name, this.prompt);
// console.log("interaction source.embeds: ", source.embeds);
}
}
//if (custom_ids.includes("MJ::Job::PicReader::")) {
}
get componentsNames() {
const out = [];
if (!this.components) {
return out;
}
for (const component of this.components) {
const line = component.components
.map((a) => a.label)
.filter((a) => a);
out.push(...line);
}
return out;
}
get parentInteraction() {
if (this.interaction && this.interaction.name) {
return this.interaction.name;
}
if (this.components && this.components.length) {
const sig1 = this.componentsNames.join("");
// [0].components.map((a) => (a as { label: string }).label).join("");
if (sig1 === "U1V1" || sig1.includes("U1U2")) {
// U3U4
if (this.referenced_message &&
this.referenced_message.parentInteraction === "imagine") {
return "variations";
}
if (this.prompt) {
// "<https://s.mj.run/abcd> <https://s.mj.run/abcd> --ar 2:3 --v 5.1"
let prompt = this.prompt.prompt;
prompt = prompt.replace(/ --s [0-9]+$/, "");
prompt = prompt.replace(/ --(v|niji) [0-9.]+$/, "");
prompt = prompt.replace(/ --ar [:0-9]+$/, "");
const urls = prompt.split(" ");
const u2 = urls
.map((a) => !a.match(/<https:\/\/s\.mj\.run\/[\w\d]+>/))
.filter((a) => a);
if (u2.length === 0 && urls.length > 1) {
return "blend";
}
}
return "imagine";
}
// Make VariationsDetailed Upscale RedoBeta Upscale RedoRemasterWeb
if (sig1.includes("Make VariationsWeb") ||
sig1.includes("Make VariationsLight Upscale") ||
sig1.includes("Make VariationsDetailed Upscale")) {
return "upscale";
}
// if (sig1 === 'Make VariationsRemasterWeb') {
// return "upscale";
// }
if (this.prompt && this.prompt.source.startsWith("Image #")) {
return "upscale";
}
if (sig1.includes("Cancel Job")) {
console.error("parsing Cancel Job:", this.content);
return "";
//return "imagine";
}
console.error(`FIXME: can not Identify signature ${pc.green(sig1)} in message: ${pc.green(this.id)}`);
}
return "";
}
getComponents(label, label2) {
if (!this.components) {
throw Error("no components In this message.");
}
const availableLabels = [];
for (const src of this.components) {
for (const c of src.components) {
if (!("custom_id" in c))
continue;
if ("label" in c && c.label && c.label) {
if (c.label === label)
return c;
if (label2 && c.label === label2) {
return c;
}
availableLabels.push(c.label);
}
else if ("emoji" in c && c.emoji && c.emoji.name) {
if (c.emoji.name === label) {
return c;
}
if (label2 && c.emoji.name === label2) {
return c;
}
availableLabels.push(`${c.emoji.name}`);
}
}
}
throw Error(`Failed to find componant named "${label}" within ${availableLabels
.map((a) => `"${a}"`)
.join(", ")}`);
}
/**
* return if the the Message is upscalable, if an id is provide, will return true only if the requested action had not already been started.
*/
canReroll() {
try {
return this.getComponents(REROLL);
}
catch (_) {
return null;
}
}
/**
* return if the the Message is upscalable, if an id is provide, will return true only if the requested action had not already been started.
*/
canUpscale(id) {
const selector = id ? `U${id}` : "U1";
try {
const c = this.getComponents(selector);
if (id) {
if (!c.disabled && c.style !== ButtonStyle.Primary) {
// 1 is primary button means that it had already been click
return c;
}
return null;
}
return c;
}
catch (_) {
if (id && (id > 4 || id < 0)) {
logger.warn(`You asked for a image id out of bound [1,2,3,4]`);
}
return null;
}
}
/**
* return if the the Message can be varaint, if an id is provide, will return true only if the requested action had not already been started.
*/
canVariant(id) {
const selector = id ? `V${id}` : "V1";
try {
const c = this.getComponents(selector, "Make Variations");
if (id) {
if (!c.disabled && c.style !== ButtonStyle.Primary) {
return c;
}
return null; // 1 is primary button means that it had already been click
}
return c;
}
catch (_) {
if (id && (id > 4 || id < 0)) {
logger.warn(`You asked for a image id out of bound [1,2,3,4]`);
}
return null;
}
}
reroll(progress) {
const comp = this.getComponents(REROLL);
logger.info(`${comp.custom_id} Reroll will be generated`);
return __classPrivateFieldGet(this, _DiscordMessage_client, "f").callCustomComponents(this.id, comp, progress);
}
upscale(id, progress) {
const comp = this.getComponents(`U${id}`);
logger.info(`${comp.custom_id} Upscale will be generated`);
return __classPrivateFieldGet(this, _DiscordMessage_client, "f").callCustomComponents(this.id, comp, progress);
}
variant(id, progress) {
const comp = this.getComponents(`V${id}`, "Make Variations");
logger.info(`${comp.custom_id} Variant will be generated`);
return __classPrivateFieldGet(this, _DiscordMessage_client, "f").callCustomComponents(this.id, comp, progress);
}
async refresh() {
const m2 = await __classPrivateFieldGet(this, _DiscordMessage_client, "f").getMessageById(this.id);
if (m2) {
Object.assign(this, m2);
}
return this;
}
// async waitForattachements(timeout = 10): Promise<void> {
// for (let i = 0; i < timeout; i++) {
// if (!this.attachments || !this.attachments.length) {
// await wait(600);
// await this.refresh();
// console.log("Failed to get Attachement.");
// } else {
// break;
// }
// }
// }
async download(attachementId, dest) {
// await this.waitForattachements();
const att = (this.attachments || [])[attachementId];
if (!att) {
return null;
}
try {
const stats = await dntShim.Deno.stat(dest);
if (stats.isDirectory) {
const destFile = path.join(dest, att.filename);
logger.info(`downloading ${att.url} to ${destFile}`);
return await downloadFileCached(att.url, destFile);
}
throw Error(`download abort, ${dest} is an existing file`);
}
catch (_) {
if (path.basename(dest).includes(".")) {
return await downloadFileCached(att.url, dest);
}
else {
await dntShim.Deno.mkdir(dest, { recursive: true });
return this.download(attachementId, dest);
}
}
}
}
_DiscordMessage_client = new WeakMap();