├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── README_CN.md ├── chart ├── flow.jpg └── program.jpg ├── client.py ├── config.example.py ├── example.py ├── main.py ├── midjounery.py ├── midjounery_manager.py ├── midjourney ├── Midjourney.py └── __inin__.py ├── poetry.lock ├── pyproject.toml ├── requirements.txt ├── start.sh └── test.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | 4 | ko_fi: ezioruan 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | idea 2 | .DS_Store 3 | .ipynb_checkpoints 4 | .mypy_cache 5 | .vscode 6 | __pycache__ 7 | .pytest_cache 8 | htmlcov 9 | dist 10 | site 11 | .coverage 12 | coverage.xml 13 | .netlify 14 | test.db 15 | log.txt 16 | Pipfile.lock 17 | env3.* 18 | env 19 | docs_build 20 | venv 21 | docs.zip 22 | archive.zip 23 | 24 | # vim temporary files 25 | *~ 26 | .*.sw? 27 | 28 | # ignore pycharm 29 | .idea 30 | .env 31 | config.py 32 | .venv/ 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 中文说明[README_CN.md](README_CN.md) 2 | # midjourney-python-api 3 | This is a Python client for the unofficial MidJourney API, This implementation uses a Discord self bot, and utilizes this library: Merubokkusu/Discord-S.C.U.M. Please be aware that there might be a risk of being banned. 4 | 5 | 6 | ### *** risky actions: [issue #66](https://github.com/Merubokkusu/Discord-S.C.U.M/issues/66#issue-876713938) 7 | 8 | ## Key Features 9 | - [x] Info 10 | - [x] Imagine prompt 11 | - [x] Image Upscale and Vectorize by label 12 | - [x] All messages return via WebSocket, including banned words check and image processing 13 | - [x] Auto reconnect WebSocket 14 | - [x] status for execute command (imagine, interact), failed or success 15 | 16 | ## Planned Features 17 | - [x] Multi-account support 18 | - [ ] Full support for all MidJourney APIs 19 | 20 | 21 | ### Setup, choose one of the following methods 22 | 23 | #### by pip 24 | ```bash 25 | # use pip, create visual env 26 | python -m venv .venv 27 | pip install -r requirements.txt 28 | ``` 29 | 30 | 31 | ### by poetry 32 | ```bash 33 | poetry install 34 | ``` 35 | 36 | ## Configuration 37 | 38 | ## copy the config file and edit it, you can set all accounts in the config file 39 | ```bash 40 | cp config.example.py config.py 41 | ``` 42 | 43 | ### Start the main Services 44 | 45 | ```python 46 | python main.py 47 | ``` 48 | 49 | ## Start test client to listen redis messages 50 | 51 | ```python 52 | python client.py 53 | ``` 54 | 55 | ## Start test client to send redis messages to test 56 | 57 | ```python 58 | python test.py 59 | ``` 60 | 61 | ## Technical Principles: 62 | 63 | Multiple accounts need to be configured in `config.py`, each account requires the following fields: `name`, `token`, `application_id`, `guild_id` and `channel_id`. 64 | 65 | The Python program will start two threads: 66 | 67 | 1. One thread is used to receive messages from Redis and dispatch to subprocesses. 68 | 69 | 2. The other thread is used to manage subprocesses. 70 | 71 | The subprocess management thread will start one process for each account based on account configurations. Each process will have two threads: 72 | 73 | 1. Redis receiving thread: receive tasks and assemble messages. The message queue name is \`midjounery_task\`. The message format is as follows: 74 | 75 | ``` 76 | { 77 | "cmd": string, command to execute, currently supports "imagine" and "interact", 78 | 79 | "args": array, arguments passed to the command, "imagine" only has one string "prompt", "interact" has two params "message_id" and "label", 80 | 81 | "channel_id": which channel to send the execution to, "imagine" can be omitted, program will randomly schedule across accounts, "interact" must pass the correct "channel_id". 82 | } 83 | ``` 84 | 85 | 2. Redis sending thread: sends real-time WSS messages. The message queue name is \`midjounery_notification\`. 86 | 87 | The program flow is as follows: 88 | 89 | 1. Send messages from Redis task queue. 90 | 91 | 2. The main process receives the message, and randomly selects a channel if "channel_id" is not present. 92 | 93 | 3. Send to each subprocess through inter-process communication queue. 94 | 95 | 4. Each account subprocess will receive this message, but only process tasks matching its own "channel_id", other tasks will be ignored. 96 | 97 | 5. After receiving the task, the subprocess will call its internal function to execute. 98 | 99 | 6. The execution result is listened via WSS, then sent externally via Redis queue. 100 | 101 | 102 | ## workflow 103 | ![workflow](chart/flow.jpg) 104 | 105 | ## program 106 | ![program](chart/program.jpg) 107 | 108 | 109 | ## Discussion 110 | - discord : https://discord.gg/AJSGUVeMd9 111 | 112 | ## Donation 113 | Loved the project? Condider buy me a coffee :D [Kofi](https://ko-fi.com/ezioruan) 114 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # midjourney-python-api 2 | 这是非官方 MidJourney API 的 Python 客户端。 3 | 4 | 此实现使用了 Discord self bot,并采用了这个库:[Merubokkusu/Discord-S.C.U.M](https://github.com/Merubokkusu/Discord-S.C.U.M)。请注意,这可能存在被封禁的风险 5 | ### *** 风险操作:[问题 #66](https://github.com/Merubokkusu/Discord-S.C.U.M/issues/66#issue-876713938) 6 | 7 | ## 主要功能 8 | - [x] Info 9 | - [x] Imagine prompt 10 | - [x] 标签图片放大和向量化 11 | - [x] 所有消息通过WebSocket返回,包括禁用词检查和图像处理 12 | - [x] 自动重连 WebSocket 13 | - [x] 操作(imagine,interact) 的状态和结果更新 14 | 15 | 16 | ## 计划功能 17 | - [x] 多账户支持 18 | - [ ] 完全支持所有 MidJourney APIs 19 | 20 | ## 安装设置, 以下两种方法任选其一 21 | 22 | ### 通过 pip 23 | ```bash 24 | # 使用 pip,创建虚拟环境 25 | python -m .venv 26 | pip install -r requirements.txt 27 | ``` 28 | 29 | ### 通过 poetry 30 | ```bash 31 | poetry install 32 | ``` 33 | 34 | ## 在config.py 里面配置多个账号 35 | ``` 36 | cp config.example.py config.py 37 | ``` 38 | 39 | ### 启动服务 40 | ``` 41 | python main.py 42 | 43 | ``` 44 | ## 启动测试的客户端监听redis信息 45 | ``` 46 | python client.py 47 | ``` 48 | 49 | ## 启动测试的客户端发送redis信息 50 | ``` 51 | python test.py 52 | ``` 53 | 54 | 55 | ## 技术原理: 56 | 需要在**账号配置中心**,里面配置多个账号,每个账号需要的字段包括:`name`、`token`、`application_id`、`guild_id` 和 `channel_id`。 57 | 58 | Python 程序会开启两个线程: 59 | 60 | 1. 一个线程用于从 Redis 接收消息,调度给子进程。 61 | 2. 另一个线程用于管理子进程。 62 | 63 | 子进程管理线程会根据账号配置为每个账号开启一个进程。每个进程中会有两个线程: 64 | 65 | 1. Redis 接收线程:接收任务,制作消息。消息队列名为 `midjounery_task`。消息格式如下: 66 | 67 | ``` 68 | { 69 | "cmd": 字符串,要执行的命令,目前支持 "imagine" 和 "interact", 70 | "args": 数组,传给命令的参数,"imagine" 的参数只有一个字符串 "prompt","interact" 有两个参数 "message_id" 和 "label", 71 | "channel_id": 需要发到哪个 channel 执行,"imagine" 可以不传,程序会自己随机调度到多账号,"interact" 必须传正确的 "channel_id"。 72 | } 73 | ``` 74 | 75 | 2. Redis 发送线程:发送实时的 WSS 消息。消息队列名为 `midjounery_notification`。 76 | 77 | 程序的流程如下: 78 | 79 | 1. 从 Redis 制作任务队列发送消息。 80 | 2. 主进程接收到消息,如果没有 "channel_id",则随机选择一个。 81 | 3. 通过进程间通信的队列发送给每个子进程。 82 | 4. 每个账户子进程都会收到这个消息,但是它们只会处理与自己 "channel_id" 相匹配的任务,其他任务会被忽略。 83 | 5. 收到任务后,子进程会调用自己内部的函数执行。 84 | 6. 执行结果通过 WSS 监听,然后通过 Redis 的队列发送到外部。 85 | 86 | 87 | ## 工作流程图 88 | ![workflow](chart/flow.jpg) 89 | 90 | ## 程序结构图 91 | ![program](chart/program.jpg) 92 | 93 | ## 交流群 94 | ![image](https://github.com/ezioruan/midjourney-python-api/assets/631411/99274e48-c3ff-442e-a515-f48b335d3db9) 95 | -------------------------------------------------------------------------------- /chart/flow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ezioruan/midjourney-python-api/be626e0f21454eea5aeb8b64a386184a7af371b5/chart/flow.jpg -------------------------------------------------------------------------------- /chart/program.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ezioruan/midjourney-python-api/be626e0f21454eea5aeb8b64a386184a7af371b5/chart/program.jpg -------------------------------------------------------------------------------- /client.py: -------------------------------------------------------------------------------- 1 | # -* coding:UTF-8 -* 2 | # !/usr/bin/env python 3 | import json 4 | import os 5 | import pprint 6 | import time 7 | import traceback 8 | import config 9 | 10 | import redis 11 | 12 | pp = pprint.PrettyPrinter(depth=4) 13 | 14 | 15 | redis_client = redis.Redis( 16 | host=config.REDIS_HOST, 17 | port=config.REDIS_PORT, 18 | password=config.REDIS_PASSWORD, 19 | db=config.REDIS_DB, 20 | ) 21 | 22 | print(f"connect to redis {config.REDIS_HOST} success, start to listen") 23 | 24 | 25 | def start_imagine(): 26 | task = {"cmd": "imagine", "args": ["a little cat"]} 27 | redis_client.rpush(config.REDIS_TASK_CHANNEL, json.dumps(task)) 28 | 29 | 30 | def main(): 31 | # Connect to Redis 32 | 33 | while True: 34 | # Block until a message is available 35 | _, message = redis_client.blpop(config.REDIS_NOTIFY_CHANNEL) 36 | print("message", message) 37 | 38 | # Extract message id and content 39 | try: 40 | message_decoded = json.loads(message.decode("utf-8")) 41 | pp.pprint(message_decoded) 42 | except Exception: 43 | print("message decode error") 44 | traceback.print_exc() 45 | 46 | 47 | if __name__ == "__main__": 48 | main() 49 | -------------------------------------------------------------------------------- /config.example.py: -------------------------------------------------------------------------------- 1 | REDIS_HOST = "127.0.0.1" 2 | REDIS_PORT = 6379 3 | REDIS_PASSWORD = "" 4 | REDIS_DB = 0 5 | REDIS_TASK_CHANNEL = "midjounery_task" 6 | REDIS_NOTIFY_CHANNEL = "midjounery_notify" 7 | 8 | 9 | # put account config here 10 | ACCOUNT_LIST = [ 11 | { 12 | "name": "", 13 | "token": "", 14 | "application_id": "", 15 | "channel_id": "", 16 | "guild_id" : "", 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | from midjourney.Midjourney import MidjourneyClient 2 | import pprint 3 | 4 | import time 5 | 6 | 7 | def process_message(message): 8 | pprint.pprint(message) 9 | 10 | 11 | def main(): 12 | client = MidjourneyClient( 13 | name="test", 14 | token="", # your discord token 15 | application_id="", # bot application_id 16 | guild_id="", # your discord server id or None 17 | channel_id="", # your channel_id 18 | message_handler=process_message, 19 | ) 20 | client.run() 21 | time.sleep(3) 22 | 23 | # get info of the Client 24 | client.info() 25 | 26 | client.imagine("Imagine a world where you can do this") 27 | 28 | client.interact(message_id="", label="U1") 29 | 30 | 31 | if __name__ == "__main__": 32 | main() 33 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import json 2 | import multiprocessing 3 | import os 4 | import threading 5 | import config 6 | 7 | import redis 8 | 9 | from midjounery_manager import distribute_task, manage_clients 10 | 11 | 12 | redis_client = redis.Redis( 13 | host=config.REDIS_HOST, 14 | port=config.REDIS_PORT, 15 | password=config.REDIS_PASSWORD, 16 | db=config.REDIS_DB, 17 | ) 18 | 19 | 20 | def send_message(message_id, message): 21 | message = json.dumps(message) 22 | print("Send message to Redis", message) 23 | redis_client.rpush(config.REDIS_NOTIFY_CHANNEL, message) 24 | 25 | 26 | def listen_to_redis(ready_event, task_queues): 27 | # Wait for the signal that clients are ready 28 | ready_event.wait() 29 | 30 | while True: 31 | _, task = redis_client.blpop(config.REDIS_TASK_CHANNEL) 32 | print("get task from Redis", task) 33 | task = json.loads(task.decode("utf-8")) 34 | for task_queue in task_queues: 35 | distribute_task(task_queue, task) 36 | 37 | 38 | if __name__ == "__main__": 39 | # Event to signal that clients are ready 40 | clients_ready_event = threading.Event() 41 | 42 | # Start managing client processes and get the list of task queues 43 | task_queues = manage_clients(send_message, clients_ready_event) 44 | 45 | # Thread for listening to Redis and distributing tasks 46 | redis_listener_thread = threading.Thread( 47 | target=listen_to_redis, args=(clients_ready_event, task_queues) 48 | ) 49 | redis_listener_thread.start() 50 | 51 | # Wait for Redis listener thread to finish 52 | redis_listener_thread.join() 53 | -------------------------------------------------------------------------------- /midjounery.py: -------------------------------------------------------------------------------- 1 | import pprint 2 | import traceback 3 | import re 4 | 5 | import discum 6 | from discum.utils.button import Buttoner 7 | from discum.utils.slash import SlashCommander 8 | 9 | pp = pprint.PrettyPrinter(depth=4) 10 | 11 | 12 | class MidjouneryClient: 13 | def __init__( 14 | self, 15 | name, 16 | token, 17 | application_id, 18 | guild_id, 19 | channel_id, 20 | message_handler=None, 21 | ): 22 | self.name = name 23 | self.token = token 24 | self.application_id = application_id 25 | self.guild_id = guild_id 26 | self.channel_id = channel_id 27 | self.no_received_times = 0 28 | 29 | if not message_handler: 30 | self.message_handler = print 31 | else: 32 | self.message_handler = message_handler 33 | 34 | def run(self): 35 | self.bot = discum.Client(token=self.token, log=False) 36 | self.slash_cmds = None 37 | self.add_message_hooks_if_needed() 38 | self.bot.gateway.run(auto_reconnect=True) 39 | 40 | def re_run(self): 41 | print("re_run") 42 | self.bot.gateway.close() 43 | self.bot.gateway._after_message_hooks = [] 44 | self.run() 45 | 46 | def __str__(self): 47 | return f" {self.channel_id} " 48 | 49 | def subscribeToGuildEvents(self): 50 | try: 51 | self.bot.gateway.subscribeToGuildEvents(wait=1) 52 | except KeyError as e: 53 | pass 54 | except Exception as e: 55 | traceback.print_exc() 56 | 57 | def parse_process_message(self, message): 58 | print("parse_process_message", message) 59 | match_with_progress_and_mode = re.search( 60 | r"\*\*(.*)\*\* - <@(.*)> \((.*)\) \((.*)\)", message 61 | ) 62 | match_without_progress_and_mode = re.search( 63 | r"\*\*(.*)\*\* - <@(.*)>", message 64 | ) 65 | 66 | if match_with_progress_and_mode: 67 | prompt = match_with_progress_and_mode.group(1) 68 | user_id = match_with_progress_and_mode.group(2) 69 | progress = match_with_progress_and_mode.group(3) 70 | mode = match_with_progress_and_mode.group(4) 71 | elif match_without_progress_and_mode: 72 | prompt = match_without_progress_and_mode.group(1) 73 | user_id = match_without_progress_and_mode.group(2) 74 | progress = "" 75 | mode = "" 76 | else: 77 | prompt = "" 78 | user_id = "" 79 | progress = "" 80 | mode = "" 81 | 82 | return { 83 | "prompt": prompt, 84 | "progress": progress, 85 | "mode": mode, 86 | } 87 | 88 | def parse_settings(self, message): 89 | """ 90 | parse message from Adjust your settings here. Current suffix: ` --s 750 --v 5.1` 91 | """ 92 | match = re.search(r"`(.*?)`", text) 93 | if match: 94 | settings = match.group(1) 95 | self.settings = settings 96 | 97 | def parse_command_message(self, message): 98 | """ 99 | parse message like this: /imagine A sex girl 100 | """ 101 | print("parse_command_message", message) 102 | match = re.search(r"/(\w+)\s+(.*)", message) 103 | 104 | if match: 105 | command = match.group(1) 106 | prompt = match.group(2) 107 | else: 108 | command = "" 109 | prompt = "" 110 | 111 | return { 112 | "command": command, 113 | "prompt": prompt, 114 | } 115 | 116 | def set_slash_cmds(self): 117 | if not self.slash_cmds: 118 | self.slash_cmds = { 119 | c["name"]: c 120 | for c in self.bot.getSlashCommands(self.application_id).json() 121 | } 122 | 123 | def get_slash_cmd(self, cmd_name): 124 | if not self.slash_cmds: 125 | self.set_slash_cmds() 126 | return self.slash_cmds.get(cmd_name) 127 | 128 | def add_message_hooks_if_needed(self): 129 | if not self.bot.gateway._after_message_hooks: 130 | self.bot.gateway.command(self.process_message) 131 | 132 | def execute_slash_cmd(self, cmd_name, inputs={}): 133 | slash_cmd = self.get_slash_cmd(cmd_name) 134 | s = SlashCommander(slash_cmd, application_id=self.application_id) 135 | data = s.get([cmd_name], inputs=inputs) 136 | self.add_message_hooks_if_needed() 137 | result = self.bot.triggerSlashCommand( 138 | applicationID=self.application_id, 139 | channelID=self.channel_id, 140 | guildID=self.guild_id, 141 | data=data, 142 | ) 143 | 144 | def imagine(self, prompt): 145 | cmd_name = "imagine" 146 | inputs = {"prompt": prompt} 147 | self.execute_slash_cmd(cmd_name, inputs=inputs) 148 | 149 | def info(self): 150 | cmd_name = "info" 151 | inputs = {} 152 | self.execute_slash_cmd(cmd_name, inputs=inputs) 153 | 154 | def settings(self): 155 | cmd_name = "settings" 156 | inputs = {} 157 | self.execute_slash_cmd(cmd_name, inputs=inputs) 158 | 159 | def interact(self, message_id, label): 160 | message = self.bot.getMessage(self.channel_id, message_id) 161 | if not message: 162 | return 163 | data = message.json()[0] 164 | buttons = Buttoner(data["components"]) 165 | result = self.bot.click( 166 | data["author"]["id"], 167 | channelID=data["channel_id"], 168 | guildID=self.guild_id, 169 | messageID=data["id"], 170 | messageFlags=data["flags"], 171 | sessionID=self.bot.gateway.session_id, 172 | data=buttons.getButton(label), 173 | ) 174 | 175 | def process_message(self, resp): 176 | if resp.raw["op"] == 11 and not self.bot.gateway.READY: 177 | self.no_received_times += 1 178 | print( 179 | "op code 11 received by discord > ", 180 | resp.raw, 181 | self.no_received_times, 182 | ) 183 | if self.no_received_times >= 3: 184 | print("op code 11 received 3 times, message idle, re_run") 185 | self.re_run() 186 | return 187 | if ( 188 | resp.event.ready_supplemental 189 | ): # ready_supplemental is sent after ready 190 | user = self.bot.gateway.session.user 191 | pprint.pprint(user) 192 | print( 193 | "Logged in as {}#{}".format( 194 | user.get("username"), user.get("discriminator") 195 | ) 196 | ) 197 | self.subscribeToGuildEvents() 198 | self.info() 199 | self.settings() 200 | 201 | if resp.event.message or resp.event.message_updated: 202 | message = resp.parsed.auto() 203 | message_id = message.get("id") 204 | guild_id = message.get("guild_id") 205 | channel_id = message.get("channel_id") 206 | timestamp = message.get("timestamp") 207 | application_id = message.get("application_id") 208 | components = message.get("components") 209 | attachments = message.get("attachments") 210 | message_reference = message.get("message_reference") 211 | message_type = message.get("type") 212 | content = message.get("content") 213 | # 20: updated message 214 | if ( 215 | message_type 216 | not in [ 217 | "application_command", 218 | "reply", 219 | 0, 220 | 19, 221 | 20, 222 | "default", 223 | ] 224 | or channel_id != self.channel_id 225 | ): 226 | # only parse message in current channel 227 | return 228 | pprint.pprint(message) 229 | 230 | if message_type == "20" and content.startswith( 231 | "Adjust your settings here.Current suffix:" 232 | ): 233 | # settings message 234 | self.parse_settings_message(content) 235 | 236 | result = { 237 | "message_id": message_id, 238 | "channel_id": channel_id, 239 | "guild_id": guild_id, 240 | "timestamp": timestamp, 241 | "application_id": application_id, 242 | "components": components, 243 | "attachments": attachments, 244 | "message_reference": message_reference, 245 | "message_type": str(message_type), 246 | } 247 | embeds = message.get("embeds", [{}]) 248 | embed = embeds[0] if embeds else {} 249 | footer_text = embed.get("footer", {}).get("text") 250 | if message_type in ["application_command", 20] and footer_text: 251 | # bland message 252 | result["status"] = "failed" 253 | result["content"] = embed.get("description") 254 | result["prompt"] = self.parse_command_message(footer_text).get( 255 | "prompt" 256 | ) 257 | self.message_handler(message_id, result) 258 | return 259 | 260 | result.update(self.parse_process_message(content)) 261 | result["status"] = "success" 262 | result["content"] = content 263 | self.message_handler(message_id, result) 264 | 265 | def handle_task(self, task): 266 | if task.get("channel_id") != self.channel_id: 267 | return 268 | cmd = task.get("cmd") 269 | args = task.get("args", []) 270 | method = getattr(self, cmd) 271 | try: 272 | method(*args) 273 | except Exception: 274 | traceback.print_exc() 275 | -------------------------------------------------------------------------------- /midjounery_manager.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | import random 3 | import threading 4 | 5 | from config import ACCOUNT_LIST 6 | from midjounery import MidjouneryClient 7 | 8 | 9 | def get_random_channel_id(): 10 | account = random.choice(ACCOUNT_LIST) 11 | return account["channel_id"] 12 | 13 | 14 | def handle_task(client, task_queue): 15 | print("start handle_task in client", client) 16 | while True: 17 | task = task_queue.get() 18 | print("get task from queue", task) 19 | client.handle_task(task) 20 | print("handle_task done", task) 21 | 22 | 23 | def start_client(account, message_handler, task_queue): 24 | client = MidjouneryClient(**account, message_handler=message_handler) 25 | client_run_thread = threading.Thread(target=client.run) 26 | client_run_thread.start() 27 | client_handle_task_thread = threading.Thread( 28 | target=handle_task, args=(client, task_queue) 29 | ) 30 | client_handle_task_thread.start() 31 | client_run_thread.join() 32 | client_handle_task_thread.join() 33 | 34 | 35 | def distribute_task(task_queue, task): 36 | channel_id = task.get("channel_id") 37 | if not channel_id: 38 | channel_id = get_random_channel_id() 39 | task["channel_id"] = channel_id 40 | print("distribute_task", task) 41 | task_queue.put(task) 42 | 43 | 44 | def manage_clients(message_handler, clients_ready_event): 45 | # Start client processes and store client objects 46 | task_queues = [] 47 | for account in ACCOUNT_LIST: 48 | task_queue = multiprocessing.Queue() 49 | p = multiprocessing.Process( 50 | target=start_client, args=(account, message_handler, task_queue) 51 | ) 52 | p.start() 53 | task_queues.append(task_queue) 54 | 55 | # Signal that clients are ready 56 | clients_ready_event.set() 57 | return task_queues 58 | -------------------------------------------------------------------------------- /midjourney/Midjourney.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | 3 | import discum 4 | from discum.utils.button import Buttoner 5 | from discum.utils.slash import SlashCommander 6 | import threading 7 | 8 | 9 | class MidjourneyClient: 10 | def __init__( 11 | self, 12 | name, 13 | token, 14 | application_id, 15 | guild_id, 16 | channel_id, 17 | message_handler=None, 18 | ): 19 | self.name = name 20 | self.token = token 21 | self.application_id = application_id 22 | self.guild_id = guild_id 23 | self.channel_id = channel_id 24 | self.no_received_times = 0 25 | 26 | if not message_handler: 27 | self.message_handler = print 28 | else: 29 | self.message_handler = message_handler 30 | 31 | def run(self, log=False): 32 | self.bot = discum.Client(token=self.token, log=log) 33 | self.slash_cmds = None 34 | self.add_message_hooks_if_needed() 35 | # run the client in thread so it doesn't block 36 | client_thread = threading.Thread(target=self.bot.gateway.run) 37 | client_thread.start() 38 | 39 | def re_run(self): 40 | self.bot.gateway.close() 41 | self.bot.gateway._after_message_hooks = [] 42 | self.run() 43 | 44 | def __str__(self): 45 | return f" {self.channel_id} " 46 | 47 | def subscribeToGuildEvents(self): 48 | try: 49 | self.bot.gateway.subscribeToGuildEvents(wait=1) 50 | except KeyError as e: 51 | pass 52 | except Exception as e: 53 | traceback.print_exc() 54 | 55 | def set_slash_cmds(self): 56 | if not self.slash_cmds: 57 | self.slash_cmds = { 58 | c["name"]: c 59 | for c in self.bot.getSlashCommands(self.application_id).json() 60 | } 61 | 62 | def get_slash_cmd(self, cmd_name): 63 | if not self.slash_cmds: 64 | self.set_slash_cmds() 65 | return self.slash_cmds.get(cmd_name) 66 | 67 | def add_message_hooks_if_needed(self): 68 | if not self.bot.gateway._after_message_hooks: 69 | self.bot.gateway.command(self.process_message) 70 | 71 | def execute_slash_cmd(self, cmd_name, inputs={}): 72 | slash_cmd = self.get_slash_cmd(cmd_name) 73 | s = SlashCommander(slash_cmd, application_id=self.application_id) 74 | data = s.get([cmd_name], inputs=inputs) 75 | self.add_message_hooks_if_needed() 76 | result = self.bot.triggerSlashCommand( 77 | applicationID=self.application_id, 78 | channelID=self.channel_id, 79 | guildID=self.guild_id, 80 | data=data, 81 | ) 82 | 83 | def imagine(self, prompt): 84 | cmd_name = "imagine" 85 | inputs = {"prompt": prompt} 86 | self.execute_slash_cmd(cmd_name, inputs=inputs) 87 | 88 | def info(self): 89 | cmd_name = "info" 90 | inputs = {} 91 | self.execute_slash_cmd(cmd_name, inputs=inputs) 92 | 93 | def interact(self, message_id, label): 94 | message = self.bot.getMessage(self.channel_id, message_id) 95 | if not message: 96 | return 97 | data = message.json()[0] 98 | buttons = Buttoner(data["components"]) 99 | result = self.bot.click( 100 | data["author"]["id"], 101 | channelID=data["channel_id"], 102 | guildID=self.guild_id, 103 | messageID=data["id"], 104 | messageFlags=data["flags"], 105 | sessionID=self.bot.gateway.session_id, 106 | data=buttons.getButton(label), 107 | ) 108 | 109 | def process_message(self, resp): 110 | if resp.raw["op"] == 11 and not self.bot.gateway.READY: 111 | self.no_received_times += 1 112 | print( 113 | "op code 11 received by discord > ", 114 | resp.raw, 115 | self.no_received_times, 116 | ) 117 | if self.no_received_times >= 3: 118 | print("op code 11 received 3 times, message idle, re_run") 119 | self.re_run() 120 | return 121 | if ( 122 | resp.event.ready_supplemental 123 | ): # ready_supplemental is sent after ready 124 | user = self.bot.gateway.session.user 125 | print( 126 | "Logged in as {}#{}".format( 127 | user.get("username"), user.get("discriminator") 128 | ) 129 | ) 130 | self.subscribeToGuildEvents() 131 | 132 | if resp.event.message or resp.event.message_updated: 133 | message = resp.parsed.auto() 134 | self.message_handler(message) 135 | -------------------------------------------------------------------------------- /midjourney/__inin__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ezioruan/midjourney-python-api/be626e0f21454eea5aeb8b64a386184a7af371b5/midjourney/__inin__.py -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "brotli" 5 | version = "1.0.9" 6 | description = "Python bindings for the Brotli compression library" 7 | category = "main" 8 | optional = false 9 | python-versions = "*" 10 | files = [ 11 | {file = "Brotli-1.0.9-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:268fe94547ba25b58ebc724680609c8ee3e5a843202e9a381f6f9c5e8bdb5c70"}, 12 | {file = "Brotli-1.0.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:c2415d9d082152460f2bd4e382a1e85aed233abc92db5a3880da2257dc7daf7b"}, 13 | {file = "Brotli-1.0.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5913a1177fc36e30fcf6dc868ce23b0453952c78c04c266d3149b3d39e1410d6"}, 14 | {file = "Brotli-1.0.9-cp27-cp27m-win32.whl", hash = "sha256:afde17ae04d90fbe53afb628f7f2d4ca022797aa093e809de5c3cf276f61bbfa"}, 15 | {file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7cb81373984cc0e4682f31bc3d6be9026006d96eecd07ea49aafb06897746452"}, 16 | {file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:db844eb158a87ccab83e868a762ea8024ae27337fc7ddcbfcddd157f841fdfe7"}, 17 | {file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9744a863b489c79a73aba014df554b0e7a0fc44ef3f8a0ef2a52919c7d155031"}, 18 | {file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a72661af47119a80d82fa583b554095308d6a4c356b2a554fdc2799bc19f2a43"}, 19 | {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ee83d3e3a024a9618e5be64648d6d11c37047ac48adff25f12fa4226cf23d1c"}, 20 | {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:19598ecddd8a212aedb1ffa15763dd52a388518c4550e615aed88dc3753c0f0c"}, 21 | {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:44bb8ff420c1d19d91d79d8c3574b8954288bdff0273bf788954064d260d7ab0"}, 22 | {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e23281b9a08ec338469268f98f194658abfb13658ee98e2b7f85ee9dd06caa91"}, 23 | {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3496fc835370da351d37cada4cf744039616a6db7d13c430035e901443a34daa"}, 24 | {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83bb06a0192cccf1eb8d0a28672a1b79c74c3a8a5f2619625aeb6f28b3a82bb"}, 25 | {file = "Brotli-1.0.9-cp310-cp310-win32.whl", hash = "sha256:26d168aac4aaec9a4394221240e8a5436b5634adc3cd1cdf637f6645cecbf181"}, 26 | {file = "Brotli-1.0.9-cp310-cp310-win_amd64.whl", hash = "sha256:622a231b08899c864eb87e85f81c75e7b9ce05b001e59bbfbf43d4a71f5f32b2"}, 27 | {file = "Brotli-1.0.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cc0283a406774f465fb45ec7efb66857c09ffefbe49ec20b7882eff6d3c86d3a"}, 28 | {file = "Brotli-1.0.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:11d3283d89af7033236fa4e73ec2cbe743d4f6a81d41bd234f24bf63dde979df"}, 29 | {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c1306004d49b84bd0c4f90457c6f57ad109f5cc6067a9664e12b7b79a9948ad"}, 30 | {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1375b5d17d6145c798661b67e4ae9d5496920d9265e2f00f1c2c0b5ae91fbde"}, 31 | {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cab1b5964b39607a66adbba01f1c12df2e55ac36c81ec6ed44f2fca44178bf1a"}, 32 | {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ed6a5b3d23ecc00ea02e1ed8e0ff9a08f4fc87a1f58a2530e71c0f48adf882f"}, 33 | {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cb02ed34557afde2d2da68194d12f5719ee96cfb2eacc886352cb73e3808fc5d"}, 34 | {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b3523f51818e8f16599613edddb1ff924eeb4b53ab7e7197f85cbc321cdca32f"}, 35 | {file = "Brotli-1.0.9-cp311-cp311-win32.whl", hash = "sha256:ba72d37e2a924717990f4d7482e8ac88e2ef43fb95491eb6e0d124d77d2a150d"}, 36 | {file = "Brotli-1.0.9-cp311-cp311-win_amd64.whl", hash = "sha256:3ffaadcaeafe9d30a7e4e1e97ad727e4f5610b9fa2f7551998471e3736738679"}, 37 | {file = "Brotli-1.0.9-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4"}, 38 | {file = "Brotli-1.0.9-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6b2ae9f5f67f89aade1fab0f7fd8f2832501311c363a21579d02defa844d9296"}, 39 | {file = "Brotli-1.0.9-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:68715970f16b6e92c574c30747c95cf8cf62804569647386ff032195dc89a430"}, 40 | {file = "Brotli-1.0.9-cp35-cp35m-win32.whl", hash = "sha256:defed7ea5f218a9f2336301e6fd379f55c655bea65ba2476346340a0ce6f74a1"}, 41 | {file = "Brotli-1.0.9-cp35-cp35m-win_amd64.whl", hash = "sha256:88c63a1b55f352b02c6ffd24b15ead9fc0e8bf781dbe070213039324922a2eea"}, 42 | {file = "Brotli-1.0.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:503fa6af7da9f4b5780bb7e4cbe0c639b010f12be85d02c99452825dd0feef3f"}, 43 | {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:40d15c79f42e0a2c72892bf407979febd9cf91f36f495ffb333d1d04cebb34e4"}, 44 | {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:93130612b837103e15ac3f9cbacb4613f9e348b58b3aad53721d92e57f96d46a"}, 45 | {file = "Brotli-1.0.9-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87fdccbb6bb589095f413b1e05734ba492c962b4a45a13ff3408fa44ffe6479b"}, 46 | {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:6d847b14f7ea89f6ad3c9e3901d1bc4835f6b390a9c71df999b0162d9bb1e20f"}, 47 | {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:495ba7e49c2db22b046a53b469bbecea802efce200dffb69b93dd47397edc9b6"}, 48 | {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:4688c1e42968ba52e57d8670ad2306fe92e0169c6f3af0089be75bbac0c64a3b"}, 49 | {file = "Brotli-1.0.9-cp36-cp36m-win32.whl", hash = "sha256:61a7ee1f13ab913897dac7da44a73c6d44d48a4adff42a5701e3239791c96e14"}, 50 | {file = "Brotli-1.0.9-cp36-cp36m-win_amd64.whl", hash = "sha256:1c48472a6ba3b113452355b9af0a60da5c2ae60477f8feda8346f8fd48e3e87c"}, 51 | {file = "Brotli-1.0.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b78a24b5fd13c03ee2b7b86290ed20efdc95da75a3557cc06811764d5ad1126"}, 52 | {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9d12cf2851759b8de8ca5fde36a59c08210a97ffca0eb94c532ce7b17c6a3d1d"}, 53 | {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6c772d6c0a79ac0f414a9f8947cc407e119b8598de7621f39cacadae3cf57d12"}, 54 | {file = "Brotli-1.0.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29d1d350178e5225397e28ea1b7aca3648fcbab546d20e7475805437bfb0a130"}, 55 | {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7bbff90b63328013e1e8cb50650ae0b9bac54ffb4be6104378490193cd60f85a"}, 56 | {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ec1947eabbaf8e0531e8e899fc1d9876c179fc518989461f5d24e2223395a9e3"}, 57 | {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12effe280b8ebfd389022aa65114e30407540ccb89b177d3fbc9a4f177c4bd5d"}, 58 | {file = "Brotli-1.0.9-cp37-cp37m-win32.whl", hash = "sha256:f909bbbc433048b499cb9db9e713b5d8d949e8c109a2a548502fb9aa8630f0b1"}, 59 | {file = "Brotli-1.0.9-cp37-cp37m-win_amd64.whl", hash = "sha256:97f715cf371b16ac88b8c19da00029804e20e25f30d80203417255d239f228b5"}, 60 | {file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e16eb9541f3dd1a3e92b89005e37b1257b157b7256df0e36bd7b33b50be73bcb"}, 61 | {file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:160c78292e98d21e73a4cc7f76a234390e516afcd982fa17e1422f7c6a9ce9c8"}, 62 | {file = "Brotli-1.0.9-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b663f1e02de5d0573610756398e44c130add0eb9a3fc912a09665332942a2efb"}, 63 | {file = "Brotli-1.0.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5b6ef7d9f9c38292df3690fe3e302b5b530999fa90014853dcd0d6902fb59f26"}, 64 | {file = "Brotli-1.0.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a674ac10e0a87b683f4fa2b6fa41090edfd686a6524bd8dedbd6138b309175c"}, 65 | {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e2d9e1cbc1b25e22000328702b014227737756f4b5bf5c485ac1d8091ada078b"}, 66 | {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b336c5e9cf03c7be40c47b5fd694c43c9f1358a80ba384a21969e0b4e66a9b17"}, 67 | {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:85f7912459c67eaab2fb854ed2bc1cc25772b300545fe7ed2dc03954da638649"}, 68 | {file = "Brotli-1.0.9-cp38-cp38-win32.whl", hash = "sha256:35a3edbe18e876e596553c4007a087f8bcfd538f19bc116917b3c7522fca0429"}, 69 | {file = "Brotli-1.0.9-cp38-cp38-win_amd64.whl", hash = "sha256:269a5743a393c65db46a7bb982644c67ecba4b8d91b392403ad8a861ba6f495f"}, 70 | {file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2aad0e0baa04517741c9bb5b07586c642302e5fb3e75319cb62087bd0995ab19"}, 71 | {file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5cb1e18167792d7d21e21365d7650b72d5081ed476123ff7b8cac7f45189c0c7"}, 72 | {file = "Brotli-1.0.9-cp39-cp39-manylinux1_i686.whl", hash = "sha256:16d528a45c2e1909c2798f27f7bf0a3feec1dc9e50948e738b961618e38b6a7b"}, 73 | {file = "Brotli-1.0.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:56d027eace784738457437df7331965473f2c0da2c70e1a1f6fdbae5402e0389"}, 74 | {file = "Brotli-1.0.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bf919756d25e4114ace16a8ce91eb340eb57a08e2c6950c3cebcbe3dff2a5e7"}, 75 | {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e4c4e92c14a57c9bd4cb4be678c25369bf7a092d55fd0866f759e425b9660806"}, 76 | {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e48f4234f2469ed012a98f4b7874e7f7e173c167bed4934912a29e03167cf6b1"}, 77 | {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ed4c92a0665002ff8ea852353aeb60d9141eb04109e88928026d3c8a9e5433c"}, 78 | {file = "Brotli-1.0.9-cp39-cp39-win32.whl", hash = "sha256:cfc391f4429ee0a9370aa93d812a52e1fee0f37a81861f4fdd1f4fb28e8547c3"}, 79 | {file = "Brotli-1.0.9-cp39-cp39-win_amd64.whl", hash = "sha256:854c33dad5ba0fbd6ab69185fec8dab89e13cda6b7d191ba111987df74f38761"}, 80 | {file = "Brotli-1.0.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9749a124280a0ada4187a6cfd1ffd35c350fb3af79c706589d98e088c5044267"}, 81 | {file = "Brotli-1.0.9-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:73fd30d4ce0ea48010564ccee1a26bfe39323fde05cb34b5863455629db61dc7"}, 82 | {file = "Brotli-1.0.9-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02177603aaca36e1fd21b091cb742bb3b305a569e2402f1ca38af471777fb019"}, 83 | {file = "Brotli-1.0.9-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:76ffebb907bec09ff511bb3acc077695e2c32bc2142819491579a695f77ffd4d"}, 84 | {file = "Brotli-1.0.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b43775532a5904bc938f9c15b77c613cb6ad6fb30990f3b0afaea82797a402d8"}, 85 | {file = "Brotli-1.0.9-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5bf37a08493232fbb0f8229f1824b366c2fc1d02d64e7e918af40acd15f3e337"}, 86 | {file = "Brotli-1.0.9-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:330e3f10cd01da535c70d09c4283ba2df5fb78e915bea0a28becad6e2ac010be"}, 87 | {file = "Brotli-1.0.9-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e1abbeef02962596548382e393f56e4c94acd286bd0c5afba756cffc33670e8a"}, 88 | {file = "Brotli-1.0.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3148362937217b7072cf80a2dcc007f09bb5ecb96dae4617316638194113d5be"}, 89 | {file = "Brotli-1.0.9-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:336b40348269f9b91268378de5ff44dc6fbaa2268194f85177b53463d313842a"}, 90 | {file = "Brotli-1.0.9-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b8b09a16a1950b9ef495a0f8b9d0a87599a9d1f179e2d4ac014b2ec831f87e7"}, 91 | {file = "Brotli-1.0.9-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c8e521a0ce7cf690ca84b8cc2272ddaf9d8a50294fd086da67e517439614c755"}, 92 | {file = "Brotli-1.0.9.zip", hash = "sha256:4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438"}, 93 | ] 94 | 95 | [[package]] 96 | name = "certifi" 97 | version = "2023.5.7" 98 | description = "Python package for providing Mozilla's CA Bundle." 99 | category = "main" 100 | optional = false 101 | python-versions = ">=3.6" 102 | files = [ 103 | {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, 104 | {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, 105 | ] 106 | 107 | [[package]] 108 | name = "charset-normalizer" 109 | version = "3.1.0" 110 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 111 | category = "main" 112 | optional = false 113 | python-versions = ">=3.7.0" 114 | files = [ 115 | {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, 116 | {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, 117 | {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, 118 | {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, 119 | {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, 120 | {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, 121 | {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, 122 | {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, 123 | {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, 124 | {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, 125 | {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, 126 | {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, 127 | {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, 128 | {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, 129 | {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, 130 | {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, 131 | {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, 132 | {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, 133 | {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, 134 | {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, 135 | {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, 136 | {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, 137 | {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, 138 | {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, 139 | {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, 140 | {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, 141 | {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, 142 | {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, 143 | {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, 144 | {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, 145 | {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, 146 | {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, 147 | {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, 148 | {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, 149 | {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, 150 | {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, 151 | {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, 152 | {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, 153 | {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, 154 | {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, 155 | {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, 156 | {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, 157 | {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, 158 | {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, 159 | {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, 160 | {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, 161 | {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, 162 | {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, 163 | {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, 164 | {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, 165 | {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, 166 | {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, 167 | {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, 168 | {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, 169 | {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, 170 | {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, 171 | {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, 172 | {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, 173 | {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, 174 | {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, 175 | {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, 176 | {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, 177 | {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, 178 | {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, 179 | {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, 180 | {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, 181 | {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, 182 | {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, 183 | {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, 184 | {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, 185 | {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, 186 | {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, 187 | {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, 188 | {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, 189 | {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, 190 | ] 191 | 192 | [[package]] 193 | name = "colorama" 194 | version = "0.4.6" 195 | description = "Cross-platform colored terminal text." 196 | category = "main" 197 | optional = false 198 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 199 | files = [ 200 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 201 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 202 | ] 203 | 204 | [[package]] 205 | name = "discum" 206 | version = "1.4.1" 207 | description = "A Discord API Wrapper for Userbots/Selfbots written in Python." 208 | category = "main" 209 | optional = false 210 | python-versions = ">=2.7.0" 211 | files = [] 212 | develop = false 213 | 214 | [package.dependencies] 215 | brotli = "*" 216 | colorama = "*" 217 | filetype = "*" 218 | requests = "*" 219 | requests_toolbelt = "*" 220 | ua-parser = "*" 221 | websocket-client = "0.59.0" 222 | 223 | [package.extras] 224 | ra = ["pycryptodome", "pypng", "pyqrcode"] 225 | 226 | [package.source] 227 | type = "git" 228 | url = "https://github.com/Merubokkusu/Discord-S.C.U.M.git" 229 | reference = "master" 230 | resolved_reference = "3a3d9334f66e2f7c7954fcb616a06c52b252804e" 231 | 232 | [[package]] 233 | name = "filetype" 234 | version = "1.2.0" 235 | description = "Infer file type and MIME type of any file/buffer. No external dependencies." 236 | category = "main" 237 | optional = false 238 | python-versions = "*" 239 | files = [ 240 | {file = "filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25"}, 241 | {file = "filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb"}, 242 | ] 243 | 244 | [[package]] 245 | name = "idna" 246 | version = "3.4" 247 | description = "Internationalized Domain Names in Applications (IDNA)" 248 | category = "main" 249 | optional = false 250 | python-versions = ">=3.5" 251 | files = [ 252 | {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, 253 | {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, 254 | ] 255 | 256 | [[package]] 257 | name = "requests" 258 | version = "2.30.0" 259 | description = "Python HTTP for Humans." 260 | category = "main" 261 | optional = false 262 | python-versions = ">=3.7" 263 | files = [ 264 | {file = "requests-2.30.0-py3-none-any.whl", hash = "sha256:10e94cc4f3121ee6da529d358cdaeaff2f1c409cd377dbc72b825852f2f7e294"}, 265 | {file = "requests-2.30.0.tar.gz", hash = "sha256:239d7d4458afcb28a692cdd298d87542235f4ca8d36d03a15bfc128a6559a2f4"}, 266 | ] 267 | 268 | [package.dependencies] 269 | certifi = ">=2017.4.17" 270 | charset-normalizer = ">=2,<4" 271 | idna = ">=2.5,<4" 272 | urllib3 = ">=1.21.1,<3" 273 | 274 | [package.extras] 275 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 276 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 277 | 278 | [[package]] 279 | name = "requests-toolbelt" 280 | version = "1.0.0" 281 | description = "A utility belt for advanced users of python-requests" 282 | category = "main" 283 | optional = false 284 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 285 | files = [ 286 | {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, 287 | {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, 288 | ] 289 | 290 | [package.dependencies] 291 | requests = ">=2.0.1,<3.0.0" 292 | 293 | [[package]] 294 | name = "six" 295 | version = "1.16.0" 296 | description = "Python 2 and 3 compatibility utilities" 297 | category = "main" 298 | optional = false 299 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 300 | files = [ 301 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 302 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 303 | ] 304 | 305 | [[package]] 306 | name = "ua-parser" 307 | version = "0.16.1" 308 | description = "Python port of Browserscope's user agent parser" 309 | category = "main" 310 | optional = false 311 | python-versions = "*" 312 | files = [ 313 | {file = "ua-parser-0.16.1.tar.gz", hash = "sha256:ed3efc695f475ffe56248c9789b3016247e9c20e3556cfa4d5aadc78ab4b26c6"}, 314 | {file = "ua_parser-0.16.1-py2.py3-none-any.whl", hash = "sha256:f97126300df8ac0f8f2c9d8559669532d626a1af529265fd253cba56e73ab36e"}, 315 | ] 316 | 317 | [[package]] 318 | name = "urllib3" 319 | version = "2.0.2" 320 | description = "HTTP library with thread-safe connection pooling, file post, and more." 321 | category = "main" 322 | optional = false 323 | python-versions = ">=3.7" 324 | files = [ 325 | {file = "urllib3-2.0.2-py3-none-any.whl", hash = "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e"}, 326 | {file = "urllib3-2.0.2.tar.gz", hash = "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc"}, 327 | ] 328 | 329 | [package.extras] 330 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] 331 | secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] 332 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 333 | zstd = ["zstandard (>=0.18.0)"] 334 | 335 | [[package]] 336 | name = "websocket-client" 337 | version = "0.59.0" 338 | description = "WebSocket client for Python with low level API options" 339 | category = "main" 340 | optional = false 341 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 342 | files = [ 343 | {file = "websocket-client-0.59.0.tar.gz", hash = "sha256:d376bd60eace9d437ab6d7ee16f4ab4e821c9dae591e1b783c58ebd8aaf80c5c"}, 344 | {file = "websocket_client-0.59.0-py2.py3-none-any.whl", hash = "sha256:2e50d26ca593f70aba7b13a489435ef88b8fc3b5c5643c1ce8808ff9b40f0b32"}, 345 | ] 346 | 347 | [package.dependencies] 348 | six = "*" 349 | 350 | [metadata] 351 | lock-version = "2.0" 352 | python-versions = "^3.10" 353 | content-hash = "9e1d76aa8b83745523ddb46e8d12902aa3003f226f32c0f906449a9a7ac87af4" 354 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "midjounery-python-sdk" 3 | version = "0.1.0" 4 | description = "Python client for the unofficial MidJourney API." 5 | authors = ["ezioruan "] 6 | readme = "README.md" 7 | packages = [{include = "midjounery_python_sdk"}] 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.10" 11 | 12 | 13 | [build-system] 14 | requires = ["poetry-core"] 15 | build-backend = "poetry.core.masonry.api" 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | redis 2 | git+https://github.com/Merubokkusu/Discord-S.C.U.M.git#egg=discum 3 | 4 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | script_dir=$(dirname "$(realpath "$0")") 4 | cd "$script_dir" 5 | 6 | # Find any running main.py processes and kill them if found 7 | main_pid=$(ps aux | grep '[p]ython main.py' | awk '{print $2}') 8 | if [ ! -z "$main_pid" ]; then 9 | echo "Found a running main.py process, process ID: $main_pid" 10 | kill $main_pid 11 | echo "Killed the main.py process" 12 | fi 13 | 14 | # Start a new main.py process and redirect input and output to nohup.out 15 | nohup .venv/bin/python main.py > nohup.out 2>&1 & 16 | 17 | echo "Started a new main.py process" 18 | 19 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | # -* coding:UTF-8 -* 2 | # !/usr/bin/env python 3 | import json 4 | import os 5 | import pprint 6 | 7 | import redis 8 | import config 9 | 10 | pp = pprint.PrettyPrinter(depth=4) 11 | 12 | 13 | redis_client = redis.Redis( 14 | host=config.REDIS_HOST, 15 | port=config.REDIS_PORT, 16 | password=config.REDIS_PASSWORD, 17 | db=config.REDIS_DB, 18 | ) 19 | 20 | 21 | def start_imagine(): 22 | task1 = {"cmd": "imagine", "args": ["a little boy and girl"]} 23 | print("imagine", task1) 24 | redis_client.rpush(config.REDIS_TASK_CHANNEL, json.dumps(task1)) 25 | # task2 = { 26 | # "cmd": "interact", 27 | # "channel_id": "1099614488631726142", 28 | # "args": ["1104986052114001931", "U1"], 29 | # } 30 | # print("interact", task2) 31 | # redis_client.rpush(config.REDIS_TASK_CHANNEL, json.dumps(task2)) 32 | 33 | 34 | def main(): 35 | start_imagine() 36 | 37 | 38 | if __name__ == "__main__": 39 | main() 40 | --------------------------------------------------------------------------------