├── .gitignore ├── sender_params.json ├── LICENSE ├── sender_notebook.ipynb ├── sender.py ├── README.md └── receiver.py /.gitignore: -------------------------------------------------------------------------------- 1 | sender_params_my.json 2 | -------------------------------------------------------------------------------- /sender_params.json: -------------------------------------------------------------------------------- 1 | {"channelid": "", "authorization": "", "application_id": "", "guild_id": "", "session_id": "", "version": "", "id": "", "flags": "--v 5"} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 George-iam 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 | -------------------------------------------------------------------------------- /sender_notebook.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from sender import Sender\n", 10 | "sender = Sender(params = '/Users/georgeb/Midjourney_api/sender_params.json')" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 2, 16 | "metadata": {}, 17 | "outputs": [ 18 | { 19 | "name": "stdout", 20 | "output_type": "stream", 21 | "text": [ 22 | "prompt [your prompt here] successfully sent!\n" 23 | ] 24 | } 25 | ], 26 | "source": [ 27 | "prompt = 'your prompt here'\n", 28 | "sender.send(prompt)" 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": null, 34 | "metadata": {}, 35 | "outputs": [], 36 | "source": [] 37 | } 38 | ], 39 | "metadata": { 40 | "kernelspec": { 41 | "display_name": "gpu_env", 42 | "language": "python", 43 | "name": "python3" 44 | }, 45 | "language_info": { 46 | "codemirror_mode": { 47 | "name": "ipython", 48 | "version": 3 49 | }, 50 | "file_extension": ".py", 51 | "mimetype": "text/x-python", 52 | "name": "python", 53 | "nbconvert_exporter": "python", 54 | "pygments_lexer": "ipython3", 55 | "version": "3.10.8" 56 | }, 57 | "orig_nbformat": 4, 58 | "vscode": { 59 | "interpreter": { 60 | "hash": "ff125abbc48a33c1abc72b8ef58b0e69bd6cc695c3c2232d0d59e8aaf86641b6" 61 | } 62 | } 63 | }, 64 | "nbformat": 4, 65 | "nbformat_minor": 2 66 | } 67 | -------------------------------------------------------------------------------- /sender.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import time 4 | import re 5 | import argparse 6 | import sys 7 | 8 | class Sender: 9 | 10 | def __init__(self, 11 | params): 12 | 13 | self.params = params 14 | self.sender_initializer() 15 | 16 | def sender_initializer(self): 17 | 18 | with open(self.params, "r") as json_file: 19 | params = json.load(json_file) 20 | 21 | self.channelid=params['channelid'] 22 | self.authorization=params['authorization'] 23 | self.application_id = params['application_id'] 24 | self.guild_id = params['guild_id'] 25 | self.session_id = params['session_id'] 26 | self.version = params['version'] 27 | self.id = params['id'] 28 | self.flags = params['flags'] 29 | 30 | 31 | def send(self, prompt): 32 | header = { 33 | 'authorization': self.authorization 34 | } 35 | 36 | prompt = prompt.replace('_', ' ') 37 | prompt = " ".join(prompt.split()) 38 | prompt = re.sub(r'[^a-zA-Z0-9\s]+', '', prompt) 39 | prompt = prompt.lower() 40 | 41 | payload = {'type': 2, 42 | 'application_id': self.application_id, 43 | 'guild_id': self.guild_id, 44 | 'channel_id': self.channelid, 45 | 'session_id': self.session_id, 46 | 'data': { 47 | 'version': self.version, 48 | 'id': self.id, 49 | 'name': 'imagine', 50 | 'type': 1, 51 | 'options': [{'type': 3, 'name': 'prompt', 'value': str(prompt) + ' ' + self.flags}], 52 | 'attachments': []} 53 | } 54 | 55 | r = requests.post('https://discord.com/api/v9/interactions', json = payload , headers = header) 56 | while r.status_code != 204: 57 | r = requests.post('https://discord.com/api/v9/interactions', json = payload , headers = header) 58 | 59 | print('prompt [{}] successfully sent!'.format(prompt)) 60 | 61 | def parse_args(args): 62 | 63 | parser = argparse.ArgumentParser() 64 | parser.add_argument('--params', help='Path to discord authorization and channel parameters', required=True) 65 | parser.add_argument('--prompt', help='prompt to generate', required=True) 66 | 67 | return parser.parse_args(args) 68 | 69 | 70 | if __name__ == "__main__": 71 | 72 | args = sys.argv[1:] 73 | args = parse_args(args) 74 | params = args.params 75 | prompt = args.prompt 76 | 77 | sender = Sender(params) 78 | sender.send(prompt) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Midjourney_api 2 | unofficial Midjourney API 3 | 4 | This is custom Midjourney API. Using it you could generate images by code. Working on Discord API. 5 | 6 | !! Don't forget Modjourney TOS doesn't allow any automation, so this project is research only purpose !! 7 | 8 | Contains: 9 | - Sender: for sending prompts to Midjourney 10 | - Receiver: works in terminal, download all the completed images to local folder 11 | 12 | Installation: 13 | 1. Create Discord account and create your server(instruction here: https://discord.com/blog/starting-your-first-discord-server) 14 | 2. Create Midjourney account and invite Midjourney Bot to your server (instruction here: https://docs.midjourney.com/docs/invite-the-bot) 15 | 3. Make sure generation works from your server 16 | 4. Log in to Discord in Chrome browser, open your server's text channel, click on three points upper right corner, then More Tools and then Developer Tools. 17 | Select Network tab, you'll see all the network activity of your page. 18 | 5. Now type any prompt to generate in your text channel, and after you press Enter to send message with prompt, you'll see in Network Activity new line named "interaction". 19 | Press on it and choose Payload tab and you'll see payload_json - that's what we need! 20 | Copy channelid, application_id, guild_id, session_id, version and id values, we'll need it a little bit later. 21 | Then move from Payload tab to Headers tab and find "authorization" field, copy it's value too. 22 | 6. Clone this repo 23 | 7. Open "sender_params.json" file and put all the values from paragraph 5 to it. Also fill in 'flags' field to specify special flags to your prompts 24 | 8. Now you are ready to run files: 25 | - To start receiver script open terminal and type: 26 | python /path/to/cloned/dir/receiver.py --params /path/to/cloned/dir/sender_params.json --local_path '/path/to/folder/for/downloading/images' 27 | This script will show you all the generating progress and download images as soon as it will be ready 28 | - To send prompts for generation open another terminal and type: 29 | python //path/to/cloned/dir/sender.py --params /path/to/cloned/dir/sender_params.json --prompt 'your prompt here' 30 | 9. Enjoy :) 31 | 32 | Take care of controling number of parralel requests - for normal and fastest work it should be not bigger than 3(in Basic and Standard plan, and 12 in Pro plan). 33 | 34 | 35 | Project comments: 36 | 37 | This is the first simple API version, now I'm working on next one with: 38 | - local queue controller 39 | - ability to work with any number of Midjourney accounts in parralel to get much better and scalable performance 40 | - Upsampling script to send upsample request 41 | - And lots of other things. 42 | 43 | 44 | Contacts: 45 | 46 | For proposals and cooperation: 47 | 48 | email: normalabnormalai@gmail.com 49 | 50 | Discord: georgeb#0907 51 | -------------------------------------------------------------------------------- /receiver.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import numpy as np 4 | import time 5 | import pandas as pd 6 | import os 7 | import re 8 | from datetime import datetime 9 | import glob 10 | import argparse 11 | import sys 12 | 13 | class Receiver: 14 | 15 | def __init__(self, 16 | params, 17 | local_path): 18 | 19 | self.params = params 20 | self.local_path = local_path 21 | 22 | self.sender_initializer() 23 | 24 | self.df = pd.DataFrame(columns = ['prompt', 'url', 'filename', 'is_downloaded']) 25 | 26 | 27 | def sender_initializer(self): 28 | 29 | with open(self.params, "r") as json_file: 30 | params = json.load(json_file) 31 | 32 | self.channelid=params['channelid'] 33 | self.authorization=params['authorization'] 34 | self.headers = {'authorization' : self.authorization} 35 | 36 | def retrieve_messages(self): 37 | r = requests.get( 38 | f'https://discord.com/api/v10/channels/{self.channelid}/messages?limit={100}', headers=self.headers) 39 | jsonn = json.loads(r.text) 40 | return jsonn 41 | 42 | 43 | def collecting_results(self): 44 | message_list = self.retrieve_messages() 45 | self.awaiting_list = pd.DataFrame(columns = ['prompt', 'status']) 46 | for message in message_list: 47 | 48 | if (message['author']['username'] == 'Midjourney Bot') and ('**' in message['content']): 49 | 50 | if len(message['attachments']) > 0: 51 | 52 | if (message['attachments'][0]['filename'][-4:] == '.png') or ('(Open on website for full quality)' in message['content']): 53 | id = message['id'] 54 | prompt = message['content'].split('**')[1].split(' --')[0] 55 | url = message['attachments'][0]['url'] 56 | filename = message['attachments'][0]['filename'] 57 | if id not in self.df.index: 58 | self.df.loc[id] = [prompt, url, filename, 0] 59 | 60 | else: 61 | id = message['id'] 62 | prompt = message['content'].split('**')[1].split(' --')[0] 63 | if ('(fast)' in message['content']) or ('(relaxed)' in message['content']): 64 | try: 65 | status = re.findall("(\w*%)", message['content'])[0] 66 | except: 67 | status = 'unknown status' 68 | self.awaiting_list.loc[id] = [prompt, status] 69 | 70 | else: 71 | id = message['id'] 72 | prompt = message['content'].split('**')[1].split(' --')[0] 73 | if '(Waiting to start)' in message['content']: 74 | status = 'Waiting to start' 75 | self.awaiting_list.loc[id] = [prompt, status] 76 | 77 | 78 | def outputer(self): 79 | if len(self.awaiting_list) > 0: 80 | print(datetime.now().strftime("%H:%M:%S")) 81 | print('prompts in progress:') 82 | print(self.awaiting_list) 83 | print('=========================================') 84 | 85 | waiting_for_download = [self.df.loc[i].prompt for i in self.df.index if self.df.loc[i].is_downloaded == 0] 86 | if len(waiting_for_download) > 0: 87 | print(datetime.now().strftime("%H:%M:%S")) 88 | print('waiting for download prompts: ', waiting_for_download) 89 | print('=========================================') 90 | 91 | def downloading_results(self): 92 | processed_prompts = [] 93 | for i in self.df.index: 94 | if self.df.loc[i].is_downloaded == 0: 95 | response = requests.get(self.df.loc[i].url) 96 | with open(os.path.join(self.local_path, self.df.loc[i].filename), "wb") as req: 97 | req.write(response.content) 98 | self.df.loc[i, 'is_downloaded'] = 1 99 | processed_prompts.append(self.df.loc[i].prompt) 100 | if len(processed_prompts) > 0: 101 | print(datetime.now().strftime("%H:%M:%S")) 102 | print('processed prompts: ', processed_prompts) 103 | print('=========================================') 104 | 105 | def main(self): 106 | while True: 107 | self.collecting_results() 108 | self.outputer() 109 | self.downloading_results() 110 | time.sleep(5) 111 | 112 | def parse_args(args): 113 | 114 | parser = argparse.ArgumentParser() 115 | parser.add_argument('--params', help='Path to discord authorization and channel parameters', required=True) 116 | parser.add_argument('--local_path', help='Path to output images', required=True) 117 | 118 | return parser.parse_args(args) 119 | 120 | 121 | if __name__ == "__main__": 122 | 123 | args = sys.argv[1:] 124 | args = parse_args(args) 125 | params = args.params 126 | local_path = args.local_path #'/Users/georgeb/discord_api/images/' 127 | 128 | print('=========== listening started ===========') 129 | receiver = Receiver(params, local_path) 130 | receiver.main() --------------------------------------------------------------------------------