├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── LICENSE ├── README.md ├── chrome_zrfWeRJEmB.png ├── midjourney_automation_bot ├── midjourney_automation_script.py └── web │ ├── dist │ └── output.css │ ├── index.html │ ├── input.css │ ├── logo.png │ └── tailwind.config.js └── requirements.txt /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # [harmindersinghnijjar] 4 | patreon: # harmindersinghnijjar 5 | 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Passivebot 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 Automation Bot 2 | 3 | ![GUI](https://github.com/passivebot/midjourney-automation-bot/blob/8efd67a4e6e09b844db6da809469fbe26e90a60f/chrome_zrfWeRJEmB.png) 4 | 5 | This repository contains the Midjourney Automation Bot, a free and open-source tool designed to streamline the process of image generation using the OpenAI GPT-3 model. The bot automates interactions with Discord channels to create images based on user-defined prompts, facilitating the creation of various art forms such as illustrations, digital paintings, or sketches. It features a user-friendly web interface, robust logging, and customizable upscale options, making it an accessible tool for artists, developers, and anyone interested in automated image generation. The bot's functionality is well-documented, and it is made available under the MIT License, allowing for broad use and modification. 6 | 7 | As seen on [LinkedIn](https://www.linkedin.com/posts/harmindersinghnijjar_sikhism-sikhi-punjab-activity-7058192758297022464-CPs6?utm_source=share&utm_medium=member_desktop) and [YouTube](https://www.youtube.com/watch?v=IJ0jNhrKQ34). 8 | 9 | ## Contact 10 | 11 | For any queries or freelance opportunities, please get in touch with me via [LinkedIn](https://www.linkedin.com/in/harmindersinghnijjar/) or email at harmindernijjar1996@gmail.com 12 | 13 | ## Table of Contents 14 | 15 | - [How it Works](#how-it-works) 16 | - [Features](#features) 17 | - [Getting Started](#getting-started) 18 | - [Usage](#usage) 19 | - [Customization](#customization) 20 | - [License](#license) 21 | - [Credits](#credits) 22 | 23 | ## How it Works 24 | 25 | 1. The bot logs into Discord using user credentials provided in a secure configuration file. 26 | 2. The bot opens the specified channel on Discord. 27 | 3. The bot sends a command to generate an image based on the user's prompt. 28 | 4. The bot waits for the upscale options to appear and selects them. 29 | 5. The bot downloads the upscaled images. 30 | 31 | The prompt can include descriptors and a topic to guide the image generation process. The bot uses the OpenAI GPT-3 model to generate the image based on the prompt. The generated images are then downloaded and saved. 32 | 33 | ## Features 34 | 35 | - Logging: The bot logs its actions and any errors during the image generation process. 36 | - API Key Management: The bot allows users to set the OpenAI API key through different methods, such as a file, environment variable, or console input. 37 | - Customizable Upscale Options: The bot selects, and downloads upscaled images based on user-defined upscale options. 38 | - Web Interface: The bot provides a user-friendly web interface powered by Eel, allowing users to interact with and control the bot easily. 39 | 40 | ## Getting Started 41 | 42 | To get started with the Midjourney Automation Bot, follow these steps: 43 | 44 | 1. Clone the repository: `git clone https://github.com/passivebot/midjourney-automation-bot.git` 45 | 2. Install the required dependencies: `pip install -r requirements.txt` 46 | 3. Run the bot: `python midjourney_automation_script.py` 47 | 4. Customize the bot's behavior by modifying the values in the GUI, such as the bot command, channel URL, prompt, etc. 48 | 5. Click "Start Automation" to start generating images. 49 | 50 | ## Usage 51 | 52 | Once the bot runs, it will automatically log into Discord, open the specified channel, and send the bot command with the given prompt. The bot will then wait for the upscale options, select them, and download the upscaled images. 53 | 54 | You can interact with the bot through the web interface, which you can access by opening `index.html` in your preferred web browser. The web interface allows you to start the bot, configure the bot parameters, and monitor the bot's progress and log messages. 55 | 56 | ## Customization 57 | 58 | The bot is designed with customization in mind. Users can modify various parameters and settings according to their needs. You can set the OpenAI API key, choose the upscale options, decide the Discord channel, and much more. To make these customizations, simply adjust the values in the GUI before starting the bot. 59 | 60 | ## License 61 | 62 | The Midjourney Automation Bot is licensed under the [MIT License](https://github.com/passivebot/midjourney-automation-bot/blob/main/LICENSE). 63 | 64 | ## Credits 65 | 66 | Developed by [Harminder Singh Nijjar](https://github.com/harmindersinghnijjar) 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /chrome_zrfWeRJEmB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passivebot/midjourney-automation-bot/17a0e64f416cd4e02677469c8fb0572e18e6417d/chrome_zrfWeRJEmB.png -------------------------------------------------------------------------------- /midjourney_automation_bot/midjourney_automation_script.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import eel 3 | import logging 4 | import openai 5 | import os 6 | import playwright 7 | import random 8 | import re 9 | import requests 10 | import shutil 11 | import time 12 | import uuid 13 | from loguru import logger 14 | from playwright.async_api import async_playwright, Page 15 | from playwright.sync_api import sync_playwright 16 | 17 | # Get logger for this file 18 | logger = logging.getLogger(__name__) 19 | # Set logging level to INFO 20 | logger.setLevel(logging.INFO) 21 | 22 | # Define a custom log format without %(asctime)s 23 | log_format = logging.Formatter('[%(levelname)s] [%(pathname)s:%(lineno)d] - %(message)s - [%(process)d:%(thread)d]') 24 | 25 | file_handler = logging.FileHandler('midjourney_automation.log', mode='a') # Create file handler 26 | file_handler.setFormatter(log_format) # Set log format for file handler 27 | logger.addHandler(file_handler) # Add file handler to logger 28 | 29 | console_handler = logging.StreamHandler() # Create console handler 30 | console_handler.setFormatter(log_format) # Set log format for console handler 31 | logger.addHandler(console_handler) # Add console handler to logger 32 | 33 | # Add condition to check if the current log statement is the same as the previous log statement, if so then don't log it 34 | class NoRepeatFilter(logging.Filter): 35 | """Filter to ignore repeated log messages.""" 36 | def __init__(self, name=''): 37 | """Initialize the filter. 38 | Args: 39 | name (str): Name of the filter. 40 | """ 41 | super().__init__(name) 42 | self.last_log = None 43 | 44 | def filter(self, record): 45 | """Filter out repeated log messages. 46 | Args: 47 | record (LogRecord): Log record to be filtered. 48 | Returns: 49 | bool: True if log message is not a repeat, False otherwise. 50 | """ 51 | 52 | # Ignore the %(asctime)s field when comparing log messages 53 | current_log = record.getMessage().split(' - ', 1)[-1] 54 | if current_log == self.last_log: 55 | return False 56 | self.last_log = current_log 57 | return True 58 | 59 | # Create an instance of the NoRepeatFilter and add it to the logger 60 | no_repeat_filter = NoRepeatFilter() 61 | logger.addFilter(no_repeat_filter) 62 | 63 | def random_sleep(): 64 | """Sleep for a random amount of time between 1 and 5 seconds.""" 65 | time.sleep(random.randint(1, 5)) 66 | 67 | def get_openai_api_key_from_file(filepath): 68 | """Get OpenAI API key from a file. 69 | Args: 70 | filepath (str): Path to the file containing the API key. 71 | Returns: 72 | str: API key. 73 | """ 74 | logger.info("Midjourney Automation bot is reading the API key from file: %s", filepath) 75 | with open(filepath, 'r') as infile: 76 | api_key = infile.read().strip() 77 | logger.info("API key successfully read from file.") 78 | return api_key 79 | 80 | def get_openai_api_key_from_env(): 81 | """Get OpenAI API key from the environment variable. 82 | Returns: 83 | str: API key. 84 | """ 85 | logger.info("Midjourney Automation bot is fetching the API key from environment variable.") 86 | api_key = os.getenv('OPENAI_API_KEY') 87 | logger.info("API key successfully read from environment variable.") 88 | return api_key 89 | 90 | 91 | def get_openai_api_key_from_user(): 92 | """Get OpenAI API key from user input. 93 | Returns: 94 | str: API key. 95 | """ 96 | logger.info("Waiting for user to enter API key for Midjourney Automation bot.") 97 | api_key = input("Please enter your OpenAI API key for Midjourney Automation bot: ") 98 | logger.info("API key successfully entered by the user.") 99 | return api_key 100 | 101 | 102 | def set_openai_api_key(api_key): 103 | """Set OpenAI API key in the environment variable. 104 | Args: 105 | api_key (str): API key to set. 106 | """ 107 | logger.info("Midjourney Automation bot is setting API key in environment variable.") 108 | os.environ['OPENAI_API_KEY'] = api_key 109 | logger.info("API key successfully set in environment variable.") 110 | 111 | 112 | def get_openai_api_key(): 113 | """Get OpenAI API key based on user's preference. 114 | Returns: 115 | str: API key. 116 | """ 117 | while True: 118 | logger.info("Midjourney Automation bot is asking user for method of entering API key.") 119 | logger.info("Welcome to the Midjourney Automation bot OpenAI API key setup script.") 120 | logger.info("How would you like to enter your OpenAI API key for Midjourney Automation bot?") 121 | logger.info("1. From a file") 122 | logger.info("2. From the environment") 123 | logger.info("3. From the console") 124 | logger.info("4. Exit") 125 | choice = input("Please enter a number: ") 126 | if choice == '1': 127 | logger.info("User chose to enter API key for Midjourney Automation bot from a file.") 128 | filepath = input("Please enter the filepath to your API key file for Midjourney Automation bot: ") 129 | return get_openai_api_key_from_file(filepath) 130 | elif choice == '2': 131 | logger.info("User chose to enter API key for Midjourney Automation bot from the environment.") 132 | return get_openai_api_key_from_env() 133 | elif choice == '3': 134 | logger.info("User chose to enter API key for Midjourney Automation bot from the console.") 135 | return get_openai_api_key_from_user() 136 | elif choice == '4': 137 | logger.info("User chose to exit the Midjourney Automation bot setup.") 138 | exit() 139 | else: 140 | logger.warning("Invalid choice entered by user for Midjourney Automation bot setup. Retrying.") 141 | 142 | @eel.expose 143 | async def download_upscaled_images(page, prompt_text: str): 144 | try: 145 | messages = await page.query_selector_all(".messageListItem-ZZ7v6g") 146 | last_four_messages = messages[-4:] 147 | 148 | for message in last_four_messages: 149 | message_text = await message.evaluate_handle('(node) => node.innerText') 150 | message_text = str(message_text) 151 | 152 | if 'Vary (Strong)' in message_text and 'Web' in message_text: 153 | try: 154 | image_elements = await page.query_selector_all('.originalLink-Azwuo9') 155 | last_four_images = image_elements[-4:] 156 | 157 | for image in last_four_images: 158 | src = await image.get_attribute('href') 159 | url = src 160 | response = re.sub(r'[^a-zA-Z0-9\s]', '', prompt_text) 161 | response = response.replace(' ', '_').replace(',', '_') 162 | response = re.sub(r'[\<>:"/|?*]', '', response) 163 | response = response.replace('\n\n', '_') 164 | response = response[:50].rstrip('. ') 165 | download_response = requests.get(url, stream=True) 166 | 167 | with open(f'{str(response) + str(uuid.uuid1())}.png', 'wb') as out_file: 168 | shutil.copyfileobj(download_response.raw, out_file) 169 | del download_response 170 | 171 | except Exception as e: 172 | logger.info(f"An error occurred while downloading the images: {e}") 173 | 174 | else: 175 | await download_upscaled_images(page, prompt_text) 176 | 177 | except Exception as e: 178 | logger.info(f"An error occurred while finding the last message: {e}") 179 | 180 | @eel.expose 181 | async def generate_prompt_and_submit_command(page, prompt: str): 182 | try: 183 | prompt_text = gpt3_midjourney_prompt(prompt) 184 | random_sleep() 185 | pill_value_locator = 'span.optionPillValue-2uxsMp' 186 | await page.fill(pill_value_locator, prompt_text) 187 | random_sleep() 188 | await page.keyboard.press("Enter") 189 | logger.info(f'Successfully submitted prompt: {prompt_text}') 190 | await wait_and_select_upscale_options(page, prompt_text) 191 | except Exception as e: 192 | logger.error(f"An error occurred while submitting the prompt: {e}") 193 | raise e 194 | 195 | 196 | @eel.expose 197 | def gpt3_midjourney_prompt(prompt: str, engine='text-davinci-003', temp=0.7, top_p=1.0, tokens=400, freq_pen=0.0, pres_pen=0.0) -> str: 198 | """ 199 | Function to generate a prompt using the OpenAI GPT-3 model. 200 | 201 | Parameters: 202 | - prompt (str): The initial text to base the generation on. 203 | - engine (str): The id of the engine to use for completion. 204 | - temp (float): Controls randomness. Lower value means less random. 205 | - top_p (float): Nucleus sampling. Higher value means more random. 206 | - tokens (int): The maximum number of tokens to generate. 207 | - freq_pen (float): Alters the likelihood of choosing tokens based on their frequency. 208 | - pres_pen (float): Alters the likelihood of choosing tokens based on their presence in the prompt. 209 | 210 | Returns: 211 | - str: The generated text. 212 | """ 213 | if not prompt: 214 | logger.error("Prompt cannot be empty.") 215 | raise ValueError("Prompt cannot be empty.") 216 | 217 | prompt = prompt.encode(encoding='ASCII', errors='ignore').decode() 218 | 219 | try: 220 | response = openai.Completion.create( 221 | engine=engine, 222 | prompt=prompt, 223 | temperature=temp, 224 | max_tokens=tokens, 225 | top_p=top_p, 226 | frequency_penalty=freq_pen, 227 | presence_penalty=pres_pen 228 | ) 229 | 230 | if not response.choices: 231 | logger.error("No response from OpenAI API.") 232 | raise ValueError("No response from OpenAI API.") 233 | 234 | text = response.choices[0].text.strip() 235 | 236 | if not text: 237 | logger.error("Response text cannot be empty.") 238 | raise ValueError("Response text cannot be empty.") 239 | 240 | return text 241 | 242 | except Exception as e: 243 | logger.error(f"Error occurred: {e} while generating prompt.") 244 | raise e 245 | 246 | @eel.expose 247 | async def get_last_message(page) -> str: 248 | """ 249 | Function to get the last message from the provided page. 250 | 251 | Parameters: 252 | - page: The page from which to fetch the last message. 253 | 254 | Returns: 255 | - str: The text of the last message. 256 | """ 257 | try: 258 | messages = await page.query_selector_all(".messageListItem-ZZ7v6g") 259 | if not messages: 260 | logger.error("No messages found on the page.") 261 | raise ValueError("No messages found on the page.") 262 | 263 | last_message = messages[-1] 264 | last_message_text = await last_message.evaluate('(node) => node.innerText') 265 | 266 | if not last_message_text: 267 | logger.error("Last message text cannot be empty.") 268 | raise ValueError("Last message text cannot be empty.") 269 | 270 | last_message_text = str(last_message_text) 271 | # Commented out for now, as it's not needed. 272 | # logger.info(f"Last message: {last_message_text}") 273 | return last_message_text 274 | 275 | except Exception as e: 276 | logger.error(f"Error occurred: {e} while getting the last message.") 277 | raise e 278 | 279 | 280 | @eel.expose 281 | async def main(bot_command: str, channel_url: str, PROMPT: str): 282 | """ 283 | Main function that starts the bot and interacts with the page. 284 | 285 | Parameters: 286 | - bot_command (str): The command for the bot to execute. 287 | - channel_url (str): The URL of the channel where the bot should operate. 288 | - PROMPT (str): The prompt text. 289 | 290 | Returns: 291 | - None 292 | """ 293 | try: 294 | browser = None 295 | async with async_playwright() as p: 296 | browser = await p.chromium.launch(headless=False) 297 | page = await browser.new_page() 298 | await page.goto("https://www.discord.com/login") 299 | 300 | # Get credentials securely 301 | with open("credentials.txt", "r") as f: 302 | email = f.readline() 303 | password = f.readline() 304 | if not email or not password: 305 | logger.error("Email or password not provided in credentials.txt.") 306 | raise ValueError("Email or password not provided in credentials.txt.") 307 | 308 | await page.fill("input[name='email']", email) 309 | await asyncio.sleep(random.randint(1, 5)) 310 | await page.fill("input[name='password']", password) 311 | await asyncio.sleep(random.randint(1, 5)) 312 | await page.click("button[type='submit']") 313 | await asyncio.sleep(random.randint(5, 10)) 314 | await page.wait_for_url("https://discord.com/channels/@me", timeout=15000) 315 | logger.info("Successfully logged into Discord.") 316 | await asyncio.sleep(random.randint(1, 5)) 317 | 318 | for i in range(10): 319 | await open_discord_channel(page, channel_url, bot_command, PROMPT) 320 | logger.info(f"Iteration {i+1} completed.") 321 | except Exception as e: 322 | logger.error(f"Error occurred: {e} while executing the main function.") 323 | raise e 324 | finally: 325 | if browser: 326 | await browser.close() 327 | 328 | @eel.expose 329 | async def open_discord_channel(page, channel_url: str, bot_command: str, PROMPT: str): 330 | """ 331 | Function to open a Discord channel and send a bot command. 332 | 333 | Parameters: 334 | - page: The page object representing the current browser context. 335 | - channel_url (str): The URL of the channel to open. 336 | - bot_command (str): The bot command to send. 337 | - PROMPT (str): The prompt text. 338 | 339 | Returns: 340 | - None 341 | """ 342 | try: 343 | await page.goto(f"{channel_url}") 344 | await asyncio.sleep(random.randint(1, 5)) 345 | await page.wait_for_load_state("networkidle") 346 | logger.info("Successfully opened the appropriate channel.") 347 | 348 | logger.info("Entering the specified bot command.") 349 | await send_bot_command(page, bot_command, PROMPT) 350 | 351 | except Exception as e: 352 | logger.error(f"An error occurred while opening the channel and entering the bot command: {e}") 353 | raise e 354 | 355 | @eel.expose 356 | async def select_upscale_option(page, option_text: str): 357 | """ 358 | Function to select an upscale option based on the provided text. 359 | 360 | Parameters: 361 | - page: The page object representing the current browser context. 362 | - option_text (str): The text of the upscale option to select. 363 | 364 | Returns: 365 | - None 366 | """ 367 | try: 368 | upscale_option = page.locator(f"button:has-text('{option_text}')").locator("nth=-1") 369 | if not upscale_option: 370 | logger.error(f"No upscale option found with text: {option_text}.") 371 | raise ValueError(f"No upscale option found with text: {option_text}.") 372 | 373 | await upscale_option.click() 374 | logger.info(f"Successfully clicked {option_text} upscale option.") 375 | 376 | except Exception as e: 377 | logger.error(f"An error occurred while selecting the upscale option: {e}") 378 | raise e 379 | 380 | @eel.expose 381 | async def send_bot_command(page, command: str, PROMPT: str): 382 | """ 383 | Function to send a command to the bot in the chat bar. 384 | 385 | Parameters: 386 | - page: The page object representing the current browser context. 387 | - command (str): The command to send to the bot. 388 | - PROMPT (str): The prompt for the command. 389 | 390 | Returns: 391 | - None 392 | """ 393 | try: 394 | logger.info("Clicking on chat bar.") 395 | chat_bar = page.get_by_role('textbox', name='Message #general') 396 | await asyncio.sleep(random.randint(1, 5)) 397 | 398 | logger.info("Typing in bot command") 399 | await chat_bar.fill(command) 400 | #await chat_bar.fill(command) 401 | await asyncio.sleep(random.randint(1, 5)) 402 | 403 | logger.info("Selecting the prompt option in the suggestions menu") 404 | prompt_option_selector = "#autocomplete-0 > .base-2v-uc0" 405 | await page.wait_for_selector(prompt_option_selector, state='visible', timeout=10000) 406 | prompt_option = page.locator(prompt_option_selector) 407 | await asyncio.sleep(random.randint(1, 5)) 408 | await prompt_option.click() 409 | 410 | 411 | logger.info("Generating prompt using OpenAI's API.") 412 | await generate_prompt_and_submit_command(page, PROMPT) 413 | 414 | except Exception as e: 415 | logger.exception(f"An error occurred while sending the bot command: {e}") 416 | raise e 417 | 418 | @eel.expose 419 | def start_bot(art_type: str, bot_command: str, channel_url: str, descriptors: str, topic: str): 420 | """ 421 | Function to start the bot with the specified parameters. 422 | 423 | Parameters: 424 | - art_type (str): The type of art to generate. 425 | - bot_command (str): The command to send to the bot. 426 | - channel_url (str): The URL of the channel where the bot is located. 427 | - descriptors (str): The descriptors to include in the prompt. 428 | - topic (str): The topic of the image to generate. 429 | 430 | Returns: 431 | - None 432 | """ 433 | try: 434 | PROMPT = f"Generate a Midjourney prompt to result in an {art_type} image about {topic} include {descriptors}" 435 | logger.info(f"Prompt: {PROMPT}") 436 | 437 | asyncio.run(main(bot_command, channel_url, PROMPT)) 438 | 439 | except Exception as e: 440 | logger.error(f"An error occurred while starting the bot: {e}") 441 | raise e 442 | 443 | @eel.expose 444 | async def wait_and_select_upscale_options(page, prompt_text: str): 445 | """ 446 | Function to wait for and select upscale options. 447 | 448 | Parameters: 449 | - page: The page to operate on. 450 | - prompt_text (str): The text of the prompt. 451 | 452 | Returns: 453 | - None 454 | """ 455 | try: 456 | prompt_text = prompt_text.lower() 457 | 458 | # Repeat until upscale options are found 459 | while True: 460 | last_message = await get_last_message(page) 461 | 462 | # Check for 'U1' in the last message 463 | if 'U1' in last_message: 464 | logger.info("Found upscale options. Attempting to upscale all generated images.") 465 | try: 466 | await select_upscale_option(page, 'U1') 467 | time.sleep(random.randint(3, 5)) 468 | await select_upscale_option(page, 'U2') 469 | time.sleep(random.randint(3, 5)) 470 | await select_upscale_option(page, 'U3') 471 | time.sleep(random.randint(3, 5)) 472 | await select_upscale_option(page, 'U4') 473 | time.sleep(random.randint(3, 5)) 474 | except Exception as e: 475 | logger.error(f"An error occurred while selecting upscale options: {e}") 476 | raise e 477 | 478 | await download_upscaled_images(page, prompt_text) 479 | break # Exit the loop when upscale options have been found and selected 480 | 481 | else: 482 | logger.info("Upscale options not yet available, waiting...") 483 | time.sleep(random.randint(3, 5)) 484 | 485 | except Exception as e: 486 | logger.error(f"An error occurred while finding the last message: {e}") 487 | raise e 488 | 489 | def check_api_key(api_key: str) -> str: 490 | """ 491 | Function to check if the OpenAI API key is already set, if not, it prompts the user to set it. 492 | 493 | Parameters: 494 | - api_key (str): The OpenAI API key. 495 | 496 | Returns: 497 | - str: The OpenAI API key. 498 | """ 499 | try: 500 | if api_key: 501 | logger.info("OpenAI API key for Midjourney Automation bot is already set in the environment.") 502 | else: 503 | logger.info("OpenAI API key for Midjourney Automation bot is not set in the environment. Asking user to set it.") 504 | api_key = get_openai_api_key() 505 | 506 | # You might want to not log the full API key for security reasons. 507 | logger.info("Your OpenAI API key for Midjourney Automation bot is set.") 508 | 509 | set_openai_api_key(api_key) 510 | logger.info("Your OpenAI API key for Midjourney Automation bot has been set in the environment.") 511 | 512 | return api_key 513 | except Exception as e: 514 | logger.error(f"An error occurred while checking or setting the OpenAI API key: {e}") 515 | raise e 516 | 517 | def initialize_bot(api_key: str): 518 | """ 519 | Function to initialize the bot. 520 | 521 | Parameters: 522 | - api_key (str): The OpenAI API key. 523 | 524 | This function initializes the web interface with Eel and starts the web server. 525 | """ 526 | try: 527 | if not api_key: 528 | logger.error("API key not provided.") 529 | raise ValueError("API key not provided.") 530 | 531 | # Initialize eel with your web files folder 532 | eel.init('web') 533 | logger.info("Eel initialized with web files.") 534 | 535 | # Start the web server. 536 | eel.start('index.html', mode='chrome', cmdline_args=['--kiosk']) 537 | logger.info("Web server started.") 538 | 539 | except Exception as e: 540 | logger.error(f"An error occurred while initializing the bot: {e}") 541 | raise e 542 | 543 | if __name__ == '__main__': 544 | api_key = os.environ.get("OPENAI_API_KEY") 545 | api_key = check_api_key(api_key) 546 | initialize_bot(api_key) 547 | -------------------------------------------------------------------------------- /midjourney_automation_bot/web/dist/output.css: -------------------------------------------------------------------------------- 1 | /* 2 | ! tailwindcss v3.3.2 | MIT License | https://tailwindcss.com 3 | */ 4 | 5 | /* 6 | 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) 7 | 2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) 8 | */ 9 | 10 | *, 11 | ::before, 12 | ::after { 13 | box-sizing: border-box; 14 | /* 1 */ 15 | border-width: 0; 16 | /* 2 */ 17 | border-style: solid; 18 | /* 2 */ 19 | border-color: #e5e7eb; 20 | /* 2 */ 21 | } 22 | 23 | ::before, 24 | ::after { 25 | --tw-content: ''; 26 | } 27 | 28 | /* 29 | 1. Use a consistent sensible line-height in all browsers. 30 | 2. Prevent adjustments of font size after orientation changes in iOS. 31 | 3. Use a more readable tab size. 32 | 4. Use the user's configured `sans` font-family by default. 33 | 5. Use the user's configured `sans` font-feature-settings by default. 34 | 6. Use the user's configured `sans` font-variation-settings by default. 35 | */ 36 | 37 | html { 38 | line-height: 1.5; 39 | /* 1 */ 40 | -webkit-text-size-adjust: 100%; 41 | /* 2 */ 42 | -moz-tab-size: 4; 43 | /* 3 */ 44 | -o-tab-size: 4; 45 | tab-size: 4; 46 | /* 3 */ 47 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 48 | /* 4 */ 49 | font-feature-settings: normal; 50 | /* 5 */ 51 | font-variation-settings: normal; 52 | /* 6 */ 53 | } 54 | 55 | /* 56 | 1. Remove the margin in all browsers. 57 | 2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. 58 | */ 59 | 60 | body { 61 | margin: 0; 62 | /* 1 */ 63 | line-height: inherit; 64 | /* 2 */ 65 | } 66 | 67 | /* 68 | 1. Add the correct height in Firefox. 69 | 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) 70 | 3. Ensure horizontal rules are visible by default. 71 | */ 72 | 73 | hr { 74 | height: 0; 75 | /* 1 */ 76 | color: inherit; 77 | /* 2 */ 78 | border-top-width: 1px; 79 | /* 3 */ 80 | } 81 | 82 | /* 83 | Add the correct text decoration in Chrome, Edge, and Safari. 84 | */ 85 | 86 | abbr:where([title]) { 87 | -webkit-text-decoration: underline dotted; 88 | text-decoration: underline dotted; 89 | } 90 | 91 | /* 92 | Remove the default font size and weight for headings. 93 | */ 94 | 95 | h1, 96 | h2, 97 | h3, 98 | h4, 99 | h5, 100 | h6 { 101 | font-size: inherit; 102 | font-weight: inherit; 103 | } 104 | 105 | /* 106 | Reset links to optimize for opt-in styling instead of opt-out. 107 | */ 108 | 109 | a { 110 | color: inherit; 111 | text-decoration: inherit; 112 | } 113 | 114 | /* 115 | Add the correct font weight in Edge and Safari. 116 | */ 117 | 118 | b, 119 | strong { 120 | font-weight: bolder; 121 | } 122 | 123 | /* 124 | 1. Use the user's configured `mono` font family by default. 125 | 2. Correct the odd `em` font sizing in all browsers. 126 | */ 127 | 128 | code, 129 | kbd, 130 | samp, 131 | pre { 132 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 133 | /* 1 */ 134 | font-size: 1em; 135 | /* 2 */ 136 | } 137 | 138 | /* 139 | Add the correct font size in all browsers. 140 | */ 141 | 142 | small { 143 | font-size: 80%; 144 | } 145 | 146 | /* 147 | Prevent `sub` and `sup` elements from affecting the line height in all browsers. 148 | */ 149 | 150 | sub, 151 | sup { 152 | font-size: 75%; 153 | line-height: 0; 154 | position: relative; 155 | vertical-align: baseline; 156 | } 157 | 158 | sub { 159 | bottom: -0.25em; 160 | } 161 | 162 | sup { 163 | top: -0.5em; 164 | } 165 | 166 | /* 167 | 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) 168 | 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) 169 | 3. Remove gaps between table borders by default. 170 | */ 171 | 172 | table { 173 | text-indent: 0; 174 | /* 1 */ 175 | border-color: inherit; 176 | /* 2 */ 177 | border-collapse: collapse; 178 | /* 3 */ 179 | } 180 | 181 | /* 182 | 1. Change the font styles in all browsers. 183 | 2. Remove the margin in Firefox and Safari. 184 | 3. Remove default padding in all browsers. 185 | */ 186 | 187 | button, 188 | input, 189 | optgroup, 190 | select, 191 | textarea { 192 | font-family: inherit; 193 | /* 1 */ 194 | font-size: 100%; 195 | /* 1 */ 196 | font-weight: inherit; 197 | /* 1 */ 198 | line-height: inherit; 199 | /* 1 */ 200 | color: inherit; 201 | /* 1 */ 202 | margin: 0; 203 | /* 2 */ 204 | padding: 0; 205 | /* 3 */ 206 | } 207 | 208 | /* 209 | Remove the inheritance of text transform in Edge and Firefox. 210 | */ 211 | 212 | button, 213 | select { 214 | text-transform: none; 215 | } 216 | 217 | /* 218 | 1. Correct the inability to style clickable types in iOS and Safari. 219 | 2. Remove default button styles. 220 | */ 221 | 222 | button, 223 | [type='button'], 224 | [type='reset'], 225 | [type='submit'] { 226 | -webkit-appearance: button; 227 | /* 1 */ 228 | background-color: transparent; 229 | /* 2 */ 230 | background-image: none; 231 | /* 2 */ 232 | } 233 | 234 | /* 235 | Use the modern Firefox focus style for all focusable elements. 236 | */ 237 | 238 | :-moz-focusring { 239 | outline: auto; 240 | } 241 | 242 | /* 243 | Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) 244 | */ 245 | 246 | :-moz-ui-invalid { 247 | box-shadow: none; 248 | } 249 | 250 | /* 251 | Add the correct vertical alignment in Chrome and Firefox. 252 | */ 253 | 254 | progress { 255 | vertical-align: baseline; 256 | } 257 | 258 | /* 259 | Correct the cursor style of increment and decrement buttons in Safari. 260 | */ 261 | 262 | ::-webkit-inner-spin-button, 263 | ::-webkit-outer-spin-button { 264 | height: auto; 265 | } 266 | 267 | /* 268 | 1. Correct the odd appearance in Chrome and Safari. 269 | 2. Correct the outline style in Safari. 270 | */ 271 | 272 | [type='search'] { 273 | -webkit-appearance: textfield; 274 | /* 1 */ 275 | outline-offset: -2px; 276 | /* 2 */ 277 | } 278 | 279 | /* 280 | Remove the inner padding in Chrome and Safari on macOS. 281 | */ 282 | 283 | ::-webkit-search-decoration { 284 | -webkit-appearance: none; 285 | } 286 | 287 | /* 288 | 1. Correct the inability to style clickable types in iOS and Safari. 289 | 2. Change font properties to `inherit` in Safari. 290 | */ 291 | 292 | ::-webkit-file-upload-button { 293 | -webkit-appearance: button; 294 | /* 1 */ 295 | font: inherit; 296 | /* 2 */ 297 | } 298 | 299 | /* 300 | Add the correct display in Chrome and Safari. 301 | */ 302 | 303 | summary { 304 | display: list-item; 305 | } 306 | 307 | /* 308 | Removes the default spacing and border for appropriate elements. 309 | */ 310 | 311 | blockquote, 312 | dl, 313 | dd, 314 | h1, 315 | h2, 316 | h3, 317 | h4, 318 | h5, 319 | h6, 320 | hr, 321 | figure, 322 | p, 323 | pre { 324 | margin: 0; 325 | } 326 | 327 | fieldset { 328 | margin: 0; 329 | padding: 0; 330 | } 331 | 332 | legend { 333 | padding: 0; 334 | } 335 | 336 | ol, 337 | ul, 338 | menu { 339 | list-style: none; 340 | margin: 0; 341 | padding: 0; 342 | } 343 | 344 | /* 345 | Prevent resizing textareas horizontally by default. 346 | */ 347 | 348 | textarea { 349 | resize: vertical; 350 | } 351 | 352 | /* 353 | 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) 354 | 2. Set the default placeholder color to the user's configured gray 400 color. 355 | */ 356 | 357 | input::-moz-placeholder, textarea::-moz-placeholder { 358 | opacity: 1; 359 | /* 1 */ 360 | color: #9ca3af; 361 | /* 2 */ 362 | } 363 | 364 | input::placeholder, 365 | textarea::placeholder { 366 | opacity: 1; 367 | /* 1 */ 368 | color: #9ca3af; 369 | /* 2 */ 370 | } 371 | 372 | /* 373 | Set the default cursor for buttons. 374 | */ 375 | 376 | button, 377 | [role="button"] { 378 | cursor: pointer; 379 | } 380 | 381 | /* 382 | Make sure disabled buttons don't get the pointer cursor. 383 | */ 384 | 385 | :disabled { 386 | cursor: default; 387 | } 388 | 389 | /* 390 | 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) 391 | 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) 392 | This can trigger a poorly considered lint error in some tools but is included by design. 393 | */ 394 | 395 | img, 396 | svg, 397 | video, 398 | canvas, 399 | audio, 400 | iframe, 401 | embed, 402 | object { 403 | display: block; 404 | /* 1 */ 405 | vertical-align: middle; 406 | /* 2 */ 407 | } 408 | 409 | /* 410 | Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) 411 | */ 412 | 413 | img, 414 | video { 415 | max-width: 100%; 416 | height: auto; 417 | } 418 | 419 | /* Make elements with the HTML hidden attribute stay hidden by default */ 420 | 421 | [hidden] { 422 | display: none; 423 | } 424 | 425 | *, ::before, ::after { 426 | --tw-border-spacing-x: 0; 427 | --tw-border-spacing-y: 0; 428 | --tw-translate-x: 0; 429 | --tw-translate-y: 0; 430 | --tw-rotate: 0; 431 | --tw-skew-x: 0; 432 | --tw-skew-y: 0; 433 | --tw-scale-x: 1; 434 | --tw-scale-y: 1; 435 | --tw-pan-x: ; 436 | --tw-pan-y: ; 437 | --tw-pinch-zoom: ; 438 | --tw-scroll-snap-strictness: proximity; 439 | --tw-gradient-from-position: ; 440 | --tw-gradient-via-position: ; 441 | --tw-gradient-to-position: ; 442 | --tw-ordinal: ; 443 | --tw-slashed-zero: ; 444 | --tw-numeric-figure: ; 445 | --tw-numeric-spacing: ; 446 | --tw-numeric-fraction: ; 447 | --tw-ring-inset: ; 448 | --tw-ring-offset-width: 0px; 449 | --tw-ring-offset-color: #fff; 450 | --tw-ring-color: rgb(59 130 246 / 0.5); 451 | --tw-ring-offset-shadow: 0 0 #0000; 452 | --tw-ring-shadow: 0 0 #0000; 453 | --tw-shadow: 0 0 #0000; 454 | --tw-shadow-colored: 0 0 #0000; 455 | --tw-blur: ; 456 | --tw-brightness: ; 457 | --tw-contrast: ; 458 | --tw-grayscale: ; 459 | --tw-hue-rotate: ; 460 | --tw-invert: ; 461 | --tw-saturate: ; 462 | --tw-sepia: ; 463 | --tw-drop-shadow: ; 464 | --tw-backdrop-blur: ; 465 | --tw-backdrop-brightness: ; 466 | --tw-backdrop-contrast: ; 467 | --tw-backdrop-grayscale: ; 468 | --tw-backdrop-hue-rotate: ; 469 | --tw-backdrop-invert: ; 470 | --tw-backdrop-opacity: ; 471 | --tw-backdrop-saturate: ; 472 | --tw-backdrop-sepia: ; 473 | } 474 | 475 | ::backdrop { 476 | --tw-border-spacing-x: 0; 477 | --tw-border-spacing-y: 0; 478 | --tw-translate-x: 0; 479 | --tw-translate-y: 0; 480 | --tw-rotate: 0; 481 | --tw-skew-x: 0; 482 | --tw-skew-y: 0; 483 | --tw-scale-x: 1; 484 | --tw-scale-y: 1; 485 | --tw-pan-x: ; 486 | --tw-pan-y: ; 487 | --tw-pinch-zoom: ; 488 | --tw-scroll-snap-strictness: proximity; 489 | --tw-gradient-from-position: ; 490 | --tw-gradient-via-position: ; 491 | --tw-gradient-to-position: ; 492 | --tw-ordinal: ; 493 | --tw-slashed-zero: ; 494 | --tw-numeric-figure: ; 495 | --tw-numeric-spacing: ; 496 | --tw-numeric-fraction: ; 497 | --tw-ring-inset: ; 498 | --tw-ring-offset-width: 0px; 499 | --tw-ring-offset-color: #fff; 500 | --tw-ring-color: rgb(59 130 246 / 0.5); 501 | --tw-ring-offset-shadow: 0 0 #0000; 502 | --tw-ring-shadow: 0 0 #0000; 503 | --tw-shadow: 0 0 #0000; 504 | --tw-shadow-colored: 0 0 #0000; 505 | --tw-blur: ; 506 | --tw-brightness: ; 507 | --tw-contrast: ; 508 | --tw-grayscale: ; 509 | --tw-hue-rotate: ; 510 | --tw-invert: ; 511 | --tw-saturate: ; 512 | --tw-sepia: ; 513 | --tw-drop-shadow: ; 514 | --tw-backdrop-blur: ; 515 | --tw-backdrop-brightness: ; 516 | --tw-backdrop-contrast: ; 517 | --tw-backdrop-grayscale: ; 518 | --tw-backdrop-hue-rotate: ; 519 | --tw-backdrop-invert: ; 520 | --tw-backdrop-opacity: ; 521 | --tw-backdrop-saturate: ; 522 | --tw-backdrop-sepia: ; 523 | } 524 | 525 | .mx-auto { 526 | margin-left: auto; 527 | margin-right: auto; 528 | } 529 | 530 | .mb-10 { 531 | margin-bottom: 2.5rem; 532 | } 533 | 534 | .mb-2 { 535 | margin-bottom: 0.5rem; 536 | } 537 | 538 | .mb-5 { 539 | margin-bottom: 1.25rem; 540 | } 541 | 542 | .mr-4 { 543 | margin-right: 1rem; 544 | } 545 | 546 | .mr-8 { 547 | margin-right: 2rem; 548 | } 549 | 550 | .mt-2 { 551 | margin-top: 0.5rem; 552 | } 553 | 554 | .mt-24 { 555 | margin-top: 6rem; 556 | } 557 | 558 | .mt-8 { 559 | margin-top: 2rem; 560 | } 561 | 562 | .block { 563 | display: block; 564 | } 565 | 566 | .inline-block { 567 | display: inline-block; 568 | } 569 | 570 | .flex { 571 | display: flex; 572 | } 573 | 574 | .grid { 575 | display: grid; 576 | } 577 | 578 | .h-32 { 579 | height: 8rem; 580 | } 581 | 582 | .h-8 { 583 | height: 2rem; 584 | } 585 | 586 | .min-h-screen { 587 | min-height: 100vh; 588 | } 589 | 590 | .w-20 { 591 | width: 5rem; 592 | } 593 | 594 | .w-full { 595 | width: 100%; 596 | } 597 | 598 | .w-32 { 599 | width: 8rem; 600 | } 601 | 602 | .w-6 { 603 | width: 1.5rem; 604 | } 605 | 606 | .w-64 { 607 | width: 16rem; 608 | } 609 | 610 | .max-w-screen-xl { 611 | max-width: 1280px; 612 | } 613 | 614 | .flex-1 { 615 | flex: 1 1 0%; 616 | } 617 | 618 | .transform { 619 | transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); 620 | } 621 | 622 | .grid-cols-1 { 623 | grid-template-columns: repeat(1, minmax(0, 1fr)); 624 | } 625 | 626 | .flex-col { 627 | flex-direction: column; 628 | } 629 | 630 | .items-center { 631 | align-items: center; 632 | } 633 | 634 | .justify-between { 635 | justify-content: space-between; 636 | } 637 | 638 | .gap-8 { 639 | gap: 2rem; 640 | } 641 | 642 | .overflow-hidden { 643 | overflow: hidden; 644 | } 645 | 646 | .overflow-ellipsis { 647 | text-overflow: ellipsis; 648 | } 649 | 650 | .rounded { 651 | border-radius: 0.25rem; 652 | } 653 | 654 | .rounded-lg { 655 | border-radius: 0.5rem; 656 | } 657 | 658 | .rounded-b-lg { 659 | border-bottom-right-radius: 0.5rem; 660 | border-bottom-left-radius: 0.5rem; 661 | } 662 | 663 | .rounded-t-lg { 664 | border-top-left-radius: 0.5rem; 665 | border-top-right-radius: 0.5rem; 666 | } 667 | 668 | .border { 669 | border-width: 1px; 670 | } 671 | 672 | .border-b { 673 | border-bottom-width: 1px; 674 | } 675 | 676 | .border-b-2 { 677 | border-bottom-width: 2px; 678 | } 679 | 680 | .border-gray-300 { 681 | --tw-border-opacity: 1; 682 | border-color: rgb(209 213 219 / var(--tw-border-opacity)); 683 | } 684 | 685 | .border-gray-400 { 686 | --tw-border-opacity: 1; 687 | border-color: rgb(156 163 175 / var(--tw-border-opacity)); 688 | } 689 | 690 | .border-purple-500 { 691 | --tw-border-opacity: 1; 692 | border-color: rgb(168 85 247 / var(--tw-border-opacity)); 693 | } 694 | 695 | .border-red-300 { 696 | --tw-border-opacity: 1; 697 | border-color: rgb(252 165 165 / var(--tw-border-opacity)); 698 | } 699 | 700 | .bg-blue-200 { 701 | --tw-bg-opacity: 1; 702 | background-color: rgb(191 219 254 / var(--tw-bg-opacity)); 703 | } 704 | 705 | .bg-blue-500 { 706 | --tw-bg-opacity: 1; 707 | background-color: rgb(59 130 246 / var(--tw-bg-opacity)); 708 | } 709 | 710 | .bg-green-200 { 711 | --tw-bg-opacity: 1; 712 | background-color: rgb(187 247 208 / var(--tw-bg-opacity)); 713 | } 714 | 715 | .bg-green-500 { 716 | --tw-bg-opacity: 1; 717 | background-color: rgb(34 197 94 / var(--tw-bg-opacity)); 718 | } 719 | 720 | .bg-pink-400 { 721 | --tw-bg-opacity: 1; 722 | background-color: rgb(244 114 182 / var(--tw-bg-opacity)); 723 | } 724 | 725 | .bg-purple-400 { 726 | --tw-bg-opacity: 1; 727 | background-color: rgb(192 132 252 / var(--tw-bg-opacity)); 728 | } 729 | 730 | .bg-purple-600 { 731 | --tw-bg-opacity: 1; 732 | background-color: rgb(147 51 234 / var(--tw-bg-opacity)); 733 | } 734 | 735 | .bg-transparent { 736 | background-color: transparent; 737 | } 738 | 739 | .bg-white { 740 | --tw-bg-opacity: 1; 741 | background-color: rgb(255 255 255 / var(--tw-bg-opacity)); 742 | } 743 | 744 | .bg-gray-100 { 745 | --tw-bg-opacity: 1; 746 | background-color: rgb(243 244 246 / var(--tw-bg-opacity)); 747 | } 748 | 749 | .bg-gray-300 { 750 | --tw-bg-opacity: 1; 751 | background-color: rgb(209 213 219 / var(--tw-bg-opacity)); 752 | } 753 | 754 | .bg-gray-800 { 755 | --tw-bg-opacity: 1; 756 | background-color: rgb(31 41 55 / var(--tw-bg-opacity)); 757 | } 758 | 759 | .bg-indigo-500 { 760 | --tw-bg-opacity: 1; 761 | background-color: rgb(99 102 241 / var(--tw-bg-opacity)); 762 | } 763 | 764 | .bg-gray-600 { 765 | --tw-bg-opacity: 1; 766 | background-color: rgb(75 85 99 / var(--tw-bg-opacity)); 767 | } 768 | 769 | .bg-gray-400 { 770 | --tw-bg-opacity: 1; 771 | background-color: rgb(156 163 175 / var(--tw-bg-opacity)); 772 | } 773 | 774 | .bg-emerald-300 { 775 | --tw-bg-opacity: 1; 776 | background-color: rgb(110 231 183 / var(--tw-bg-opacity)); 777 | } 778 | 779 | .p-10 { 780 | padding: 2.5rem; 781 | } 782 | 783 | .p-3 { 784 | padding: 0.75rem; 785 | } 786 | 787 | .p-4 { 788 | padding: 1rem; 789 | } 790 | 791 | .px-8 { 792 | padding-left: 2rem; 793 | padding-right: 2rem; 794 | } 795 | 796 | .py-2 { 797 | padding-top: 0.5rem; 798 | padding-bottom: 0.5rem; 799 | } 800 | 801 | .py-3 { 802 | padding-top: 0.75rem; 803 | padding-bottom: 0.75rem; 804 | } 805 | 806 | .py-4 { 807 | padding-top: 1rem; 808 | padding-bottom: 1rem; 809 | } 810 | 811 | .py-12 { 812 | padding-top: 3rem; 813 | padding-bottom: 3rem; 814 | } 815 | 816 | .py-16 { 817 | padding-top: 4rem; 818 | padding-bottom: 4rem; 819 | } 820 | 821 | .pl-0 { 822 | padding-left: 0px; 823 | } 824 | 825 | .text-center { 826 | text-align: center; 827 | } 828 | 829 | .text-right { 830 | text-align: right; 831 | } 832 | 833 | .text-3xl { 834 | font-size: 1.875rem; 835 | line-height: 2.25rem; 836 | } 837 | 838 | .text-2xl { 839 | font-size: 1.5rem; 840 | line-height: 2rem; 841 | } 842 | 843 | .text-lg { 844 | font-size: 1.125rem; 845 | line-height: 1.75rem; 846 | } 847 | 848 | .text-sm { 849 | font-size: 0.875rem; 850 | line-height: 1.25rem; 851 | } 852 | 853 | .text-4xl { 854 | font-size: 2.25rem; 855 | line-height: 2.5rem; 856 | } 857 | 858 | .font-bold { 859 | font-weight: 700; 860 | } 861 | 862 | .uppercase { 863 | text-transform: uppercase; 864 | } 865 | 866 | .leading-tight { 867 | line-height: 1.25; 868 | } 869 | 870 | .tracking-wide { 871 | letter-spacing: 0.025em; 872 | } 873 | 874 | .text-blue-400 { 875 | --tw-text-opacity: 1; 876 | color: rgb(96 165 250 / var(--tw-text-opacity)); 877 | } 878 | 879 | .text-gray-500 { 880 | --tw-text-opacity: 1; 881 | color: rgb(107 114 128 / var(--tw-text-opacity)); 882 | } 883 | 884 | .text-gray-600 { 885 | --tw-text-opacity: 1; 886 | color: rgb(75 85 99 / var(--tw-text-opacity)); 887 | } 888 | 889 | .text-green-100 { 890 | --tw-text-opacity: 1; 891 | color: rgb(220 252 231 / var(--tw-text-opacity)); 892 | } 893 | 894 | .text-purple-200 { 895 | --tw-text-opacity: 1; 896 | color: rgb(233 213 255 / var(--tw-text-opacity)); 897 | } 898 | 899 | .text-red-400 { 900 | --tw-text-opacity: 1; 901 | color: rgb(248 113 113 / var(--tw-text-opacity)); 902 | } 903 | 904 | .text-white { 905 | --tw-text-opacity: 1; 906 | color: rgb(255 255 255 / var(--tw-text-opacity)); 907 | } 908 | 909 | .text-gray-100 { 910 | --tw-text-opacity: 1; 911 | color: rgb(243 244 246 / var(--tw-text-opacity)); 912 | } 913 | 914 | .text-gray-700 { 915 | --tw-text-opacity: 1; 916 | color: rgb(55 65 81 / var(--tw-text-opacity)); 917 | } 918 | 919 | .text-gray-900 { 920 | --tw-text-opacity: 1; 921 | color: rgb(17 24 39 / var(--tw-text-opacity)); 922 | } 923 | 924 | .underline { 925 | text-decoration-line: underline; 926 | } 927 | 928 | .placeholder-gray-300::-moz-placeholder { 929 | --tw-placeholder-opacity: 1; 930 | color: rgb(209 213 219 / var(--tw-placeholder-opacity)); 931 | } 932 | 933 | .placeholder-gray-300::placeholder { 934 | --tw-placeholder-opacity: 1; 935 | color: rgb(209 213 219 / var(--tw-placeholder-opacity)); 936 | } 937 | 938 | .placeholder-purple-300::-moz-placeholder { 939 | --tw-placeholder-opacity: 1; 940 | color: rgb(216 180 254 / var(--tw-placeholder-opacity)); 941 | } 942 | 943 | .placeholder-purple-300::placeholder { 944 | --tw-placeholder-opacity: 1; 945 | color: rgb(216 180 254 / var(--tw-placeholder-opacity)); 946 | } 947 | 948 | .shadow { 949 | --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); 950 | --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); 951 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 952 | } 953 | 954 | .shadow-lg { 955 | --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); 956 | --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); 957 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 958 | } 959 | 960 | .outline-none { 961 | outline: 2px solid transparent; 962 | outline-offset: 2px; 963 | } 964 | 965 | .focus\:border-green-400:focus { 966 | --tw-border-opacity: 1; 967 | border-color: rgb(74 222 128 / var(--tw-border-opacity)); 968 | } 969 | 970 | .focus\:outline-none:focus { 971 | outline: 2px solid transparent; 972 | outline-offset: 2px; 973 | } 974 | 975 | @media (min-width: 768px) { 976 | .md\:w-2\/3 { 977 | width: 66.666667%; 978 | } 979 | 980 | .md\:w-3\/4 { 981 | width: 75%; 982 | } 983 | 984 | .md\:w-auto { 985 | width: auto; 986 | } 987 | 988 | .md\:grid-cols-2 { 989 | grid-template-columns: repeat(2, minmax(0, 1fr)); 990 | } 991 | 992 | .md\:px-12 { 993 | padding-left: 3rem; 994 | padding-right: 3rem; 995 | } 996 | } 997 | 998 | @media (min-width: 1024px) { 999 | .lg\:w-1\/2 { 1000 | width: 50%; 1001 | } 1002 | 1003 | .lg\:px-16 { 1004 | padding-left: 4rem; 1005 | padding-right: 4rem; 1006 | } 1007 | 1008 | .lg\:text-5xl { 1009 | font-size: 3rem; 1010 | line-height: 1; 1011 | } 1012 | } 1013 | 1014 | @media (min-width: 1280px) { 1015 | .xl\:px-32 { 1016 | padding-left: 8rem; 1017 | padding-right: 8rem; 1018 | } 1019 | 1020 | .xl\:px-16 { 1021 | padding-left: 4rem; 1022 | padding-right: 4rem; 1023 | } 1024 | } -------------------------------------------------------------------------------- /midjourney_automation_bot/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 |
15 |
17 |
18 |
19 |

Let's automate Midjourney!

20 |
21 | Need a freelancer? Send us an email. 23 |
24 |
25 |
26 |
27 | Bot Command 28 | 31 |
32 | 33 |
34 | Channel URL 35 | 38 |
39 |
40 |
41 |
42 |
43 |
44 | Art Type 45 | 48 |
49 |
50 | Descriptors 51 | 54 |
55 |
56 | Topic 57 | 60 |
61 |
62 |
63 | 66 |
67 |
68 |
69 | 70 | 71 | 92 | 93 | -------------------------------------------------------------------------------- /midjourney_automation_bot/web/input.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /midjourney_automation_bot/web/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passivebot/midjourney-automation-bot/17a0e64f416cd4e02677469c8fb0572e18e6417d/midjourney_automation_bot/web/logo.png -------------------------------------------------------------------------------- /midjourney_automation_bot/web/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["**/*.{html,js}"], 4 | darkMode: false, // or 'media' or 'class' 5 | purge: { 6 | enabled: true, 7 | content: ["**/*.{html,js}"], 8 | }, 9 | theme: { 10 | extend: {}, 11 | }, 12 | plugins: [], 13 | } 14 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Eel==0.16.0 2 | loguru==0.7.0 3 | openai==0.27.8 4 | playwright==1.35.0 5 | Requests==2.31.0 6 | --------------------------------------------------------------------------------