├── .github ├── FUNDING.yml └── img │ ├── authorization.jpg │ └── demo.gif ├── .gitignore ├── LICENSE.md ├── README.md ├── composer.json ├── demo.php └── src └── Midjourney.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: ferranfg 2 | -------------------------------------------------------------------------------- /.github/img/authorization.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ferranfg/midjourney-discord-api-php/65ec41a7cced09be0ae1e2264ec9f85c19087bf7/.github/img/authorization.jpg -------------------------------------------------------------------------------- /.github/img/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ferranfg/midjourney-discord-api-php/65ec41a7cced09be0ae1e2264ec9f85c19087bf7/.github/img/demo.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.phpunit.cache 2 | /vendor 3 | composer.phar 4 | composer.lock 5 | .DS_Store 6 | Thumbs.db 7 | /phpunit.xml 8 | /.idea 9 | /.fleet 10 | /.vscode 11 | .phpunit.result.cache -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Ferran Figueredo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Midjourney PHP Library for Discord API Image Generation 2 | 3 | This PHP library provides a simple interface for generating images using the Midjourney Bot through the Discord API. 4 | 5 | ![Midjourney PHP Library for Discord API](/.github/img/demo.gif) 6 | 7 | ![](https://img.shields.io/packagist/dt/ferranfg/midjourney-php) 8 | ![](https://img.shields.io/packagist/v/ferranfg/midjourney-php) 9 | ![](https://img.shields.io/packagist/l/ferranfg/midjourney-php) 10 | 11 | ## Installation 12 | 13 | You can install this library using Composer. Run the following command in your project directory: 14 | 15 | `composer require ferranfg/midjourney-php` 16 | 17 | ## Usage 18 | 19 | ### Basic usage 20 | 21 | To generate an image using the Midjourney Bot, you first need to create an instance of the `Midjourney` class: 22 | 23 | ```php 24 | use Ferranfg\MidjourneyPhp\Midjourney; 25 | 26 | $midjourney = new Midjourney($discord_channel_id, $discord_user_token); 27 | 28 | $message = $midjourney->generate('An astronaut riding a horse'); 29 | 30 | return $message->upscaled_photo_url; 31 | ``` 32 | 33 | ### Constructor 34 | 35 | - `$discord_channel_id` - Replaces this value with the Channel ID where the Midjourney Bot is installed. You can get the Channel ID right-clicking on the channel and **Copy Channel ID**. 36 | 37 | Remember that you can invite the Midjourney Bot to your own server to organize your work https://docs.midjourney.com/docs/invite-the-bot 38 | 39 | - `$discord_user_token` - Automatic user accounts (self-bots) are not allowed by Discord and can result in an account termination if found, so use it at your own risk. 40 | 41 | To get your user token, visit [https://discord.com/channels/@me](https://discord.com/channels/@me) and open the **Network** tab inside the **Developers Tools**. Find between your XHR requests the `Authorization` header. 42 | 43 | ![Discord User Token](/.github/img/authorization.jpg) 44 | 45 | ### Methods 46 | 47 | #### `$midjourney->imagine($prompt)` 48 | 49 | This method generates an image using a prompt and returns an object that represents the message containing the generated image. The `$prompt` parameter is a string that will be used to generate the image. 50 | 51 | ```php 52 | $imagine_object = $midjourney->imagine('An astronaut riding a horse'); 53 | ``` 54 | 55 | #### `$midjourney->getImagine($prompt)` 56 | 57 | This method returns a previously generated object that represents the message containing the image generated by the given prompt. The `$prompt` parameter is the string used to generate the image. 58 | 59 | ```php 60 | $imagine_object = $midjourney->getImagine('An astronaut riding a horse'); 61 | ``` 62 | 63 | #### `$midjourney->upscale($imagine_object, $upscale_index)` 64 | 65 | This method upscales an image contained in the given object and returns the URL of the upscaled image. The `$imagine_object` parameter is the object returned from the `imagine` / `getImagine` methods. The `$upscale_index` parameter is an integer between 0 and 3 that represents the option provided by the MJ bot we want to upscale. 66 | 67 | ```php 68 | $upscaled_image_url = $midjourney->upscale($imagine_object, 2); 69 | ``` 70 | 71 | #### `$midjourney->getUpscale($imagine_object, $upscale_index)` 72 | 73 | This method returns the URL of a previously upscaled image generated by the given message and with the given option of upscaling. The `$imagine_object` parameter is the object returned from the `imagine` / `getImagine` methods. The `$upscale_index` parameter is an integer between 0 and 3 that represents the option provided to the MJ bot we upscaled. 74 | 75 | ```php 76 | $upscaled_image_url = $midjourney->getUpscale($imagine_object, 2); 77 | ``` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ferranfg/midjourney-php", 3 | "description": "Generate images using Midjourney Bot through the Discord API.", 4 | "keywords": ["midjourney", "midjourney-bot", "midjourney-api-bot"], 5 | "license": "MIT", 6 | "support": { 7 | "issues": "https://github.com/ferranfg/midjourney-discord-api-php/issues", 8 | "source": "https://github.com/ferranfg/midjourney-discord-api-php" 9 | }, 10 | "autoload": { 11 | "psr-4": { 12 | "Ferranfg\\MidjourneyPhp\\": "src/" 13 | } 14 | }, 15 | "authors": [ 16 | { 17 | "name": "ferran figueredo", 18 | "email": "hola@ferranfigueredo.com" 19 | } 20 | ], 21 | "require": { 22 | "php": "^8.1", 23 | "guzzlehttp/guzzle": "^7.5" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /demo.php: -------------------------------------------------------------------------------- 1 | generate('A cat riding a horse --v 5'); 14 | 15 | echo $message->upscaled_photo_url; -------------------------------------------------------------------------------- /src/Midjourney.php: -------------------------------------------------------------------------------- 1 | self::API_URL, 37 | 'headers' => [ 38 | 'Authorization' => self::$oauth_token 39 | ] 40 | ]); 41 | 42 | $request = self::$client->get('channels/' . self::$channel_id); 43 | $response = json_decode((string) $request->getBody()); 44 | 45 | self::$guild_id = $response->guild_id; 46 | 47 | $request = self::$client->get('users/@me'); 48 | $response = json_decode((string) $request->getBody()); 49 | 50 | self::$user_id = $response->id; 51 | } 52 | 53 | private static function firstWhere($array, $key, $value = null) 54 | { 55 | foreach ($array as $item) 56 | { 57 | if ( 58 | (is_callable($key) and $key($item)) or 59 | (is_string($key) and str_starts_with($item->{$key}, $value)) 60 | ) 61 | { 62 | return $item; 63 | } 64 | } 65 | 66 | return null; 67 | } 68 | 69 | public function imagine(string $prompt) 70 | { 71 | $params = [ 72 | 'type' => 2, 73 | 'application_id' => self::APPLICATION_ID, 74 | 'guild_id' => self::$guild_id, 75 | 'channel_id' => self::$channel_id, 76 | 'session_id' => self::SESSION_ID, 77 | 'data' => [ 78 | 'version' => self::DATA_VERSION, 79 | 'id' => self::DATA_ID, 80 | 'name' => 'imagine', 81 | 'type' => 1, 82 | 'options' => [[ 83 | 'type' => 3, 84 | 'name' => 'prompt', 85 | 'value' => $prompt 86 | ]], 87 | 'application_command' => [ 88 | 'id' => self::DATA_ID, 89 | 'application_id' => self::APPLICATION_ID, 90 | 'version' => self::DATA_VERSION, 91 | 'default_member_permissions' => null, 92 | 'type' => 1, 93 | 'nsfw' => false, 94 | 'name' => 'imagine', 95 | 'description' => 'Create images with Midjourney', 96 | 'dm_permission' => true, 97 | 'options' => [[ 98 | 'type' => 3, 99 | 'name' => 'prompt', 100 | 'description' => 'The prompt to imagine', 101 | 'required' => true 102 | ]] 103 | ], 104 | 'attachments' => [] 105 | ] 106 | ]; 107 | 108 | self::$client->post('interactions', [ 109 | 'json' => $params 110 | ]); 111 | 112 | sleep(8); 113 | 114 | $imagine_message = null; 115 | 116 | while (is_null($imagine_message)) 117 | { 118 | $imagine_message = $this->getImagine($prompt); 119 | 120 | if (is_null($imagine_message)) sleep(8); 121 | } 122 | 123 | return $imagine_message; 124 | } 125 | 126 | public function getImagine(string $prompt) 127 | { 128 | $response = self::$client->get('channels/' . self::$channel_id . '/messages'); 129 | $response = json_decode((string) $response->getBody()); 130 | 131 | $raw_message = self::firstWhere($response, function ($item) use ($prompt) 132 | { 133 | return ( 134 | str_starts_with($item->content, "**{$prompt}** - <@" . self::$user_id . '>') and 135 | ! str_contains($item->content, '%') and 136 | str_ends_with($item->content, '(fast)') 137 | ); 138 | }); 139 | 140 | if (is_null($raw_message)) return null; 141 | 142 | return (object) [ 143 | 'id' => $raw_message->id, 144 | 'prompt' => $prompt, 145 | 'raw_message' => $raw_message 146 | ]; 147 | } 148 | 149 | public function upscale($message, int $upscale_index = 0) 150 | { 151 | if ( ! property_exists($message, 'raw_message')) 152 | { 153 | throw new Exception('Upscale requires a message object obtained from the imagine/getImagine methods.'); 154 | } 155 | 156 | if ($upscale_index < 0 or $upscale_index > 3) 157 | { 158 | throw new Exception('Upscale index must be between 0 and 3.'); 159 | } 160 | 161 | $upscale_hash = null; 162 | $raw_message = $message->raw_message; 163 | 164 | if (property_exists($raw_message, 'components') and is_array($raw_message->components)) 165 | { 166 | $upscales = $raw_message->components[0]->components; 167 | 168 | $upscale_hash = $upscales[$upscale_index]->custom_id; 169 | } 170 | 171 | $params = [ 172 | 'type' => 3, 173 | 'guild_id' => self::$guild_id, 174 | 'channel_id' => self::$channel_id, 175 | 'message_flags' => 0, 176 | 'message_id' => $message->id, 177 | 'application_id' => self::APPLICATION_ID, 178 | 'session_id' => self::SESSION_ID, 179 | 'data' => [ 180 | 'component_type' => 2, 181 | 'custom_id' => $upscale_hash 182 | ] 183 | ]; 184 | 185 | self::$client->post('interactions', [ 186 | 'json' => $params 187 | ]); 188 | 189 | $upscaled_photo_url = null; 190 | 191 | while (is_null($upscaled_photo_url)) 192 | { 193 | $upscaled_photo_url = $this->getUpscale($message, $upscale_index); 194 | 195 | if (is_null($upscaled_photo_url)) sleep(3); 196 | } 197 | 198 | return $upscaled_photo_url; 199 | } 200 | 201 | public function getUpscale($message, $upscale_index = 0) 202 | { 203 | if ( ! property_exists($message, 'raw_message')) 204 | { 205 | throw new Exception('Upscale requires a message object obtained from the imagine/getImagine methods.'); 206 | } 207 | 208 | if ($upscale_index < 0 or $upscale_index > 3) 209 | { 210 | throw new Exception('Upscale index must be between 0 and 3.'); 211 | } 212 | 213 | $prompt = $message->prompt; 214 | 215 | $response = self::$client->get('channels/' . self::$channel_id . '/messages'); 216 | $response = json_decode((string) $response->getBody()); 217 | 218 | $message_index = $upscale_index + 1; 219 | $message = self::firstWhere($response, 'content', "**{$prompt}** - Image #{$message_index} <@" . self::$user_id . '>'); 220 | 221 | if (is_null($message)) 222 | { 223 | $message = self::firstWhere($response, 'content', "**{$prompt}** - Upscaled by <@" . self::$user_id . '> (fast)'); 224 | } 225 | 226 | if (is_null($message)) return null; 227 | 228 | if (property_exists($message, 'attachments') and is_array($message->attachments)) 229 | { 230 | $attachment = $message->attachments[0]; 231 | 232 | return $attachment->url; 233 | } 234 | 235 | return null; 236 | } 237 | 238 | public function generate($prompt, $upscale_index = 0) 239 | { 240 | $imagine = $this->imagine($prompt); 241 | 242 | $upscaled_photo_url = $this->upscale($imagine, $upscale_index); 243 | 244 | return (object) [ 245 | 'imagine_message_id' => $imagine->id, 246 | 'upscaled_photo_url' => $upscaled_photo_url 247 | ]; 248 | } 249 | } 250 | --------------------------------------------------------------------------------