Skip to content

Commit a57d8bc

Browse files
committed
feat: support upscale
1 parent 6c6a6f9 commit a57d8bc

File tree

8 files changed

+180
-26
lines changed

8 files changed

+180
-26
lines changed

README.md

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
Fetch api for midjourney on discord
55

66
## Usage
7+
### imagine
78
```typescript
89
import { Midjourney } from 'midjourney-fetch'
910

@@ -13,9 +14,33 @@ const midjourney = new Midjourney({
1314
token: 'your token',
1415
})
1516

16-
const images = await midjourney.imagine('your prompt')
17+
const data = await midjourney.imagine('your prompt')
1718

18-
console.log(images[0].url)
19+
// generated image url
20+
console.log(data.attachments[0].url)
21+
```
22+
23+
### upscale
24+
```typescript
25+
import { Midjourney } from 'midjourney-fetch'
26+
27+
const midjourney = new Midjourney({
28+
channelId: 'your channelId',
29+
serverId: 'your serverId',
30+
token: 'your token',
31+
})
32+
33+
const image = await midjourney.imagine('your prompt')
34+
35+
const data = await midjourney.upscale('your prompt', {
36+
messageId: image.id,
37+
index: 1,
38+
// custom_id could be found at image.component, for example: MJ::JOB::upsample::1::0c266431-26c6-47fa-bfee-2e1e11c7a66f
39+
customId: 'component custom_id'
40+
})
41+
42+
// generated image url
43+
console.log(data.attachments[0].url)
1944
```
2045

2146
## How to get Ids and Token

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "midjourney-fetch",
3-
"version": "0.1.4",
3+
"version": "1.0.0-beta.0",
44
"description": "",
55
"type": "module",
66
"main": "./dist/index.js",
@@ -55,5 +55,8 @@
5555
"eslint --fix --quiet",
5656
"prettier --write"
5757
]
58+
},
59+
"dependencies": {
60+
"@sapphire/snowflake": "^3.5.1"
5861
}
5962
}

pnpm-lock.yaml

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,11 @@ export const configs = {
22
timeout: 5 * 60 * 1000, // 5 min
33
interval: 15 * 1000, // every 15 second
44
};
5+
6+
export const defaultSessionId = 'ab318945494d4aa96c97ce6fce934b97';
7+
8+
export const midjourneyBotConfigs = {
9+
applicationId: '936929561302675456',
10+
version: '1077969938624553050',
11+
id: '938956540159881230',
12+
};

src/interface.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ export interface MessageAttachment {
2020
id: string;
2121
}
2222

23+
export interface MessageComponent {
24+
custom_id: string; // starts with MJ::JOB::upsample or MJ::JOB::variation
25+
label?: 'U1' | 'U2' | 'U3' | 'U4' | 'V1' | 'V2' | 'V3' | 'V4';
26+
emoji?: { name: string };
27+
style: number; // 1 - used; 2 - free
28+
type: number;
29+
}
30+
2331
export interface MessageItem {
2432
application_id: string;
2533
attachments: MessageAttachment[];
@@ -30,4 +38,27 @@ export interface MessageItem {
3038
channel_id: string;
3139
content: string;
3240
id: string;
41+
type: number; // 19 - upscale; 0 - imagine
42+
components: Array<{
43+
components: MessageComponent[];
44+
type: number;
45+
}>;
46+
}
47+
48+
export type MessageType = 'imagine' | 'upscale';
49+
50+
export type MessageTypeProps =
51+
| {
52+
type: Extract<MessageType, 'upscale'>;
53+
index: number;
54+
}
55+
| {
56+
type?: Extract<MessageType, 'imagine'>;
57+
};
58+
59+
export interface UpscaleProps {
60+
messageId: string;
61+
index: number;
62+
hash?: string;
63+
customId?: string;
3364
}

src/midjourney.ts

Lines changed: 85 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import { configs } from './config';
1+
import { DiscordSnowflake } from '@sapphire/snowflake';
2+
import { configs, defaultSessionId, midjourneyBotConfigs } from './config';
23
import type {
3-
MessageAttachment,
44
MessageItem,
5+
MessageTypeProps,
56
MidjourneyProps,
7+
UpscaleProps,
68
} from './interface';
79
import { findMessageByPrompt, isInProgress } from './utils';
810

@@ -43,16 +45,16 @@ export class Midjourney {
4345
}
4446
}
4547

46-
async interactions(prompt: string) {
48+
async createImage(prompt: string) {
4749
const payload = {
4850
type: 2,
49-
application_id: '936929561302675456',
51+
application_id: midjourneyBotConfigs.applicationId,
5052
guild_id: this.serverId,
5153
channel_id: this.channelId,
52-
session_id: 'ab318945494d4aa96c97ce6fce934b97',
54+
session_id: defaultSessionId,
5355
data: {
54-
version: '1077969938624553050',
55-
id: '938956540159881230',
56+
version: midjourneyBotConfigs.version,
57+
id: midjourneyBotConfigs.id,
5658
name: 'imagine',
5759
type: 1,
5860
options: [
@@ -63,9 +65,9 @@ export class Midjourney {
6365
},
6466
],
6567
application_command: {
66-
id: '938956540159881230',
67-
application_id: '936929561302675456',
68-
version: '1077969938624553050',
68+
id: midjourneyBotConfigs.id,
69+
application_id: midjourneyBotConfigs.applicationId,
70+
version: midjourneyBotConfigs.version,
6971
default_permission: true,
7072
default_member_permissions: null,
7173
type: 1,
@@ -85,6 +87,7 @@ export class Midjourney {
8587
},
8688
attachments: [],
8789
},
90+
nonce: DiscordSnowflake.generate().toString(),
8891
};
8992

9093
const res = await fetch(`https://discord.com/api/v9/interactions`, {
@@ -100,17 +103,55 @@ export class Midjourney {
100103
try {
101104
const data = await res.json();
102105
if (this.debugger) {
103-
this.log('Interactions failed', JSON.stringify(data));
106+
this.log('Create image failed', JSON.stringify(data));
104107
}
105108
message = data?.message;
106109
} catch (e) {
107110
// catch JSON error
108111
}
109-
throw new Error(message || `Interactions failed with ${res.status}`);
112+
throw new Error(message || `Create image failed with ${res.status}`);
110113
}
111114
}
112115

113-
async getMessage(prompt: string) {
116+
async createUpscale({ messageId, index, hash, customId }: UpscaleProps) {
117+
const payload = {
118+
type: 3,
119+
nonce: DiscordSnowflake.generate().toString(),
120+
guild_id: this.serverId,
121+
channel_id: this.channelId,
122+
message_flags: 0,
123+
message_id: messageId,
124+
application_id: midjourneyBotConfigs.applicationId,
125+
session_id: defaultSessionId,
126+
data: {
127+
component_type: 2,
128+
custom_id: customId || `MJ::JOB::upsample::${index}::${hash}`,
129+
},
130+
};
131+
const res = await fetch(`https://discord.com/api/v9/interactions`, {
132+
method: 'POST',
133+
body: JSON.stringify(payload),
134+
headers: {
135+
'Content-Type': 'application/json',
136+
Authorization: this.token,
137+
},
138+
});
139+
if (res.status >= 400) {
140+
let message = '';
141+
try {
142+
const data = await res.json();
143+
if (this.debugger) {
144+
this.log('Create upscale failed', JSON.stringify(data));
145+
}
146+
message = data?.message;
147+
} catch (e) {
148+
// catch JSON error
149+
}
150+
throw new Error(message || `Create upscale failed with ${res.status}`);
151+
}
152+
}
153+
154+
async getMessage(prompt: string, options?: MessageTypeProps) {
114155
const res = await fetch(
115156
`https://discord.com/api/v10/channels/${this.channelId}/messages?limit=50`,
116157
{
@@ -120,7 +161,7 @@ export class Midjourney {
120161
}
121162
);
122163
const data: MessageItem[] = await res.json();
123-
const message = findMessageByPrompt(data, prompt);
164+
const message = findMessageByPrompt(data, prompt, options);
124165
this.log(JSON.stringify(message), '\n');
125166
return message;
126167
}
@@ -129,24 +170,49 @@ export class Midjourney {
129170
* Same with /imagine command
130171
*/
131172
async imagine(prompt: string) {
132-
await this.interactions(prompt);
173+
await this.createImage(prompt);
133174
const times = this.timeout / this.interval;
134175
let count = 0;
135-
let image: MessageAttachment | null = null;
176+
let result: MessageItem | undefined;
136177
while (count < times) {
137178
try {
138179
count += 1;
139180
await new Promise((res) => setTimeout(res, this.interval));
140-
this.log(count);
181+
this.log(count, 'imagine');
141182
const message = await this.getMessage(prompt);
142183
if (message && !isInProgress(message)) {
143-
[image] = message.attachments;
184+
result = message;
185+
break;
186+
}
187+
} catch {
188+
continue;
189+
}
190+
}
191+
return result;
192+
}
193+
194+
async upscale({ prompt, ...params }: UpscaleProps & { prompt: string }) {
195+
await this.createUpscale(params);
196+
const times = this.timeout / this.interval;
197+
let count = 0;
198+
let result: MessageItem | undefined;
199+
while (count < times) {
200+
try {
201+
count += 1;
202+
await new Promise((res) => setTimeout(res, this.interval));
203+
this.log(count, 'upscale');
204+
const message = await this.getMessage(prompt, {
205+
type: 'upscale',
206+
index: params.index,
207+
});
208+
if (message && !isInProgress(message)) {
209+
result = message;
144210
break;
145211
}
146212
} catch {
147213
continue;
148214
}
149215
}
150-
return image ? [image] : [];
216+
return result;
151217
}
152218
}

src/utils.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
1-
import { type MessageItem } from './interface';
1+
import { midjourneyBotConfigs } from './config';
2+
import type { MessageTypeProps, MessageItem } from './interface';
23

34
export const findMessageByPrompt = (
45
messages: MessageItem[],
5-
prompt: string
6+
prompt: string,
7+
options?: MessageTypeProps
68
) => {
79
// trim and merge spaces
810
const filterPrompt = prompt.split(' ').filter(Boolean).join(' ');
11+
if (options?.type === 'upscale') {
12+
return messages.find(
13+
(msg) =>
14+
msg.type === 19 &&
15+
msg.content.includes(filterPrompt) &&
16+
msg.content.includes(`Image #${options.index}`) &&
17+
msg.author.id === midjourneyBotConfigs.applicationId
18+
);
19+
}
920
return messages.find(
1021
(msg) =>
1122
msg.content.includes(filterPrompt) &&
12-
msg.author.id === '936929561302675456'
23+
msg.author.id === midjourneyBotConfigs.applicationId
1324
);
1425
};
1526

tsup.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { defineConfig } from 'tsup';
33
export default defineConfig({
44
entry: ['src/index.ts'],
55
format: ['esm', 'cjs'],
6-
target: 'node16',
6+
target: 'node18',
77
dts: true,
88
legacyOutput: true,
99
});

0 commit comments

Comments
 (0)