├── images └── readme.md ├── sender_params.json ├── requirements.txt ├── LICENSE ├── upsender.py ├── prompt_sender.py ├── README_zh-CN.md ├── url_receiver.py ├── README.md └── app.py /images/readme.md: -------------------------------------------------------------------------------- 1 | 这里是图片缓存目录 2 | -------------------------------------------------------------------------------- /sender_params.json: -------------------------------------------------------------------------------- 1 | {"channelid": [ "xxxx" , "xxx", "xxxx"], "authorization": "", "application_id": "", "guild_id": "", "session_id": "", "version": "", "id": "", "flags":[ "--v 5", "--niji 5"]} 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | blinker==1.6.2 2 | certifi==2022.12.7 3 | charset-normalizer==3.1.0 4 | click==8.1.3 5 | Flask==2.3.1 6 | Flask-Cors==3.0.10 7 | Flask-Uploads==0.2.1 8 | idna==3.4 9 | importlib-metadata==6.6.0 10 | itsdangerous==2.1.2 11 | Jinja2==3.1.2 12 | MarkupSafe==2.1.2 13 | numpy==1.24.3 14 | pandas==2.0.1 15 | python-dateutil==2.8.2 16 | pytz==2023.3 17 | requests==2.29.0 18 | six==1.16.0 19 | tzdata==2023.3 20 | urllib3==1.26.15 21 | Werkzeug==2.3.2 22 | zipp==3.15.0 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 CelestialRipple 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 | -------------------------------------------------------------------------------- /upsender.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | 4 | class Sender: 5 | 6 | def __init__(self, 7 | params, 8 | index): 9 | self.index = index 10 | self.params = params 11 | self.sender_initializer() 12 | 13 | def sender_initializer(self): 14 | 15 | with open(self.params, "r") as json_file: 16 | params = json.load(json_file) 17 | 18 | self.channelid = params['channelid'] 19 | self.authorization = params['authorization'] 20 | self.application_id = params['application_id'] 21 | self.guild_id = params['guild_id'] 22 | self.session_id = params['session_id'] 23 | self.version = params['version'] 24 | self.id = params['id'] 25 | self.flags = params['flags'] 26 | 27 | def send(self, message_id, number,uuid): 28 | header = {'authorization': self.authorization} 29 | payload = {'type': 3, 30 | 'application_id': self.application_id, 31 | 'guild_id': self.guild_id, 32 | 'channel_id': self.channelid[self.index], 33 | 'session_id': self.session_id, 34 | "message_flags": 0, 35 | "message_id": message_id, 36 | "data": {"component_type": 2, "custom_id": f"MJ::JOB::upsample::{number}::{uuid}"}} 37 | 38 | r = requests.post('https://discord.com/api/v9/interactions', 39 | json=payload, 40 | headers=header) 41 | while r.status_code != 204: 42 | r = requests.post('https://discord.com/api/v9/interactions', 43 | json=payload, 44 | headers=header) 45 | 46 | print('Upscale request for message_id [{}] and number [{}] successfully sent!'.format(message_id, number)) 47 | -------------------------------------------------------------------------------- /prompt_sender.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | 4 | class Sender: 5 | 6 | def __init__(self, 7 | params, 8 | index,flag): 9 | 10 | self.params = params 11 | self.index = index 12 | try: 13 | self.flag = int(flag) 14 | except ValueError: 15 | self.flag = 0 16 | self.sender_initializer() 17 | 18 | def sender_initializer(self): 19 | 20 | with open(self.params, "r") as json_file: 21 | params = json.load(json_file) 22 | 23 | self.channelid=params['channelid'] 24 | self.authorization=params['authorization'] 25 | self.application_id = params['application_id'] 26 | self.guild_id = params['guild_id'] 27 | self.session_id = params['session_id'] 28 | self.version = params['version'] 29 | self.id = params['id'] 30 | self.flags = params['flags'] 31 | 32 | 33 | def send(self, prompt): 34 | header = { 35 | 'authorization': self.authorization 36 | } 37 | 38 | payload = {'type': 2, 39 | 'application_id': self.application_id, 40 | 'guild_id': self.guild_id, 41 | 'channel_id': self.channelid[self.index], 42 | 'session_id': self.session_id, 43 | 'data': { 44 | 'version': self.version, 45 | 'id': self.id, 46 | 'name': 'imagine', 47 | 'type': 1, 48 | 'options': [{'type': 3, 'name': 'prompt', 'value': str(prompt) + ' ' + self.flags[self.flag]}], 49 | 'attachments': []} 50 | } 51 | 52 | r = requests.post('https://discord.com/api/v9/interactions', json = payload , headers = header) 53 | while r.status_code != 204: 54 | r = requests.post('https://discord.com/api/v9/interactions', json = payload , headers = header) 55 | 56 | print('prompt [{}] successfully sent!'.format(prompt)) 57 | -------------------------------------------------------------------------------- /README_zh-CN.md: -------------------------------------------------------------------------------- 1 | [在线演示](https://hiripple.com/midjourney) 2 | # Midjourney-Web-API 3 | 非官方的Midjourney-Web-API,仅用于学习与研究。 4 | 5 | ## :sparkles: 特性 6 | * ✨ 部署简单、易用 7 | * 👋 支持缓存图片,中国大陆访问友好 8 | * 💾 支持图片Upscale功能,获取高清大图 9 | * 📚 多线程并发,高速绘图 10 | * 💻 自动清理数据库、错误处理功能完善 11 | * 🔐 可设置跨域限制,防盗用 12 | 13 | ## QuickStart快速开始 14 | 1、 clone项目 15 | ```shell 16 | git clone https://github.com/CelestialRipple/Midjourney-Web-API 17 | cd Midjourney-Web-API 18 | ``` 19 | 2、 获取Cookie(请确认你可以在discord中使用Midjourney绘图) 20 | - 进入Discord中与Midjourney Bot的对话框- 21 | - 打开任一浏览器的开发者工具(右键/F12),选中网络(network)选项 22 | - 发送任意绘图请求 23 | - 开发者工具中搜索interaction,查看请求头部与负载 24 | 25 | 26 | example: 27 | ![weixin](https://user-images.githubusercontent.com/115361435/235084018-32aaad31-45f6-447d-b854-f92241c927e8.png) 28 | ![weixin-2](https://user-images.githubusercontent.com/115361435/235084031-3948e15c-f48f-41c8-aa43-9712cb310909.png) 29 | 30 | 3、 将请求头部中的信息填入sender_params.json。 31 | 值得注意的时,如果你需要多线程画图,请将不同频道抓取的channelid填入channelid数组中(Standard计划最大并发数为3,PRO计划为12) 32 | 33 | 4、 启动Web-API 34 | ```shell 35 | pip -r requirements.txt 36 | python app.py 37 | ``` 38 | 39 | 5、(可选)进入APP.py配置跨域 40 | ```shell 41 | nano app.py 42 | ``` 43 | ## (更新)外部API使用介绍: 44 | ### 请求方式 45 | - post请求:http://localhost:5000/api/send_and_receive" 46 | - 可选参数:cdn=true(默认false,启用后服务器将缓存图片然后再发送,大陆访问更友好) 47 | 例子: 48 | ```python 49 | import requests 50 | import json 51 | 52 | payload = { 53 | "prompt": "your_prompt_here" 54 | } 55 | 56 | url = "http://localhost:5000/api/send_and_receive"; 57 | 58 | response = requests.post(url, data=json.dumps(payload), headers={'Content-Type': 'application/json'}) 59 | 60 | print(response.json()) 61 | ``` 62 | - get请求:http://localhost:5000/upscale" 63 | - 必填参数:file_name(string),需要执行upscale的文件名(例如rockgifinrock1971_link_and_zelda_33e8886f-adae-4579-9882-bae644005f5b.png) 64 | - 必填参数:number(number),需要执行upscale的图片序号(例1/2/3/4)。 65 | - 可选参数:cdn=true(默认false,启用后服务器将缓存图片然后再发送,大陆访问更友好) 66 | 例子: 67 | ```python 68 | import requests 69 | 70 | base_url = 'http://localhost:5000' # 替换为您的 Flask 应用实际运行的 URL 71 | file_name = 'rockgifinrock1971_link_and_zelda_33e8886f-adae-4579-9882-bae644005f5b.png' # 替换为您的实际文件名 72 | number = 3 # 替换为您想要使用的数字 73 | 74 | response = requests.get(f'{base_url}/upscale', params={'file_name': file_name, 'number': number}) 75 | 76 | if response.status_code == 200: 77 | print('Success!') 78 | print(response.json()) 79 | else: 80 | print(f'Error: {response.status_code}') 81 | print(response.text) 82 | ``` 83 | ## 解答 84 | Q:sender_params.json中的信息隔多久更新? 85 | A:项目运行以来已经有两周,目前仍未过期。 86 | 87 | ## 更新计划 88 | 89 | - 模型切换 90 | - 多账号并发 91 | - 更快捷地获取cookie 92 | 93 | ## 联系方式: 94 | 如需建议和合作:Me@hiripple.com 95 | 请作者吃疯狂星期四,加快项目进度:https://afdian.net/a/hiripple/plan 96 | 97 | ## License 98 | MIT 99 | 100 | ## 补充 101 | Sender.py与Receiver.py基于https://github.com/George-iam/Midjourney_api二次开发 102 | -------------------------------------------------------------------------------- /url_receiver.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import time 4 | import pandas as pd 5 | import os 6 | import re 7 | from datetime import datetime, timezone, timedelta 8 | from dateutil.parser import parse 9 | 10 | class Receiver: 11 | 12 | def __init__(self, params, index): 13 | 14 | self.params = params 15 | self.index = index 16 | self.sender_initializer() 17 | self.latest_image_timestamp = datetime.now(timezone.utc) - timedelta(days=1) 18 | self.df = pd.DataFrame(columns = ['prompt', 'url', 'filename', 'is_downloaded']) 19 | 20 | 21 | def sender_initializer(self): 22 | 23 | with open(self.params, "r") as json_file: 24 | params = json.load(json_file) 25 | 26 | self.channelid=params['channelid'][self.index] 27 | self.authorization=params['authorization'] 28 | self.headers = {'authorization' : self.authorization} 29 | 30 | def retrieve_messages(self): 31 | r = requests.get( 32 | f'https://discord.com/api/v10/channels/{self.channelid}/messages?limit={10}', headers=self.headers) 33 | jsonn = json.loads(r.text) 34 | return jsonn 35 | 36 | 37 | def collecting_results(self): 38 | message_list = self.retrieve_messages() 39 | self.awaiting_list = pd.DataFrame(columns = ['prompt', 'status']) 40 | for message in message_list: 41 | 42 | if (message['author']['username'] == 'Midjourney Bot') and ('**' in message['content']): 43 | 44 | if len(message['attachments']) > 0: 45 | 46 | if (message['attachments'][0]['filename'][-4:] == '.png') or ('(Open on website for full quality)' in message['content']): 47 | id = message['id'] 48 | prompt = message['content'].split('**')[1].split(' --')[0] 49 | url = message['attachments'][0]['url'] 50 | filename = message['attachments'][0]['filename'] 51 | if id not in self.df.index: 52 | self.df.loc[id] = [prompt, url, filename, 0] 53 | self.latest_image_timestamp = parse(message["timestamp"]) 54 | 55 | else: 56 | id = message['id'] 57 | prompt = message['content'].split('**')[1].split(' --')[0] 58 | if ('(fast)' in message['content']) or ('(relaxed)' in message['content']): 59 | try: 60 | status = re.findall("(\w*%)", message['content'])[0] 61 | except: 62 | status = 'unknown status' 63 | self.awaiting_list.loc[id] = [prompt, status] 64 | 65 | else: 66 | id = message['id'] 67 | prompt = message['content'].split('**')[1].split(' --')[0] 68 | if '(Waiting to start)' in message['content']: 69 | status = 'Waiting to start' 70 | self.awaiting_list.loc[id] = [prompt, status] 71 | 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [中文版本](https://github.com/CelestialRipple/Midjourney-Web-API/blob/main/README_zh-CN.md)-[Live Demo](https://hiripple.com/midjourney) 2 | # Midjourney-Web-API 3 | Unofficial Midjourney-Web-API, for learning and research purposes only. 4 | 5 | ## :sparkles: Features 6 | * ✨ Simple and easy to deploy 7 | * 👋 Support for caching images, mainland China access friendly 8 | * 💾 Support image Upscale function, get HD large image 9 | * 📚 Multi-threaded concurrency, high-speed drawing 10 | * 💻 Automatic database cleanup and error handling functions are perfect 11 | * 🔐 Cross-domain restrictions can be set, anti-theft 12 | 13 | ## QuickStart fast start 14 | 1. clone project 15 | ```shell 16 | git clone https://github.com/CelestialRipple/Midjourney-Web-API 17 | cd Midjourney-Web-API 18 | ``` 19 | 2、 Get Cookie (make sure you can use Midjourney drawing in Discord) 20 | - Go to the dialog box in Discord with Midjourney Bot - 21 | - Open the developer tools of any browser (right click/F12) and check the network option 22 | - Send any drawing request 23 | - Search for interaction in Developer Tools and check the request header and load 24 | example: 25 | ! [weixin](https://user-images.githubusercontent.com/115361435/235084018-32aaad31-45f6-447d-b854-f92241c927e8.png) 26 | ! [weixin-2](https://user-images.githubusercontent.com/115361435/235084031-3948e15c-f48f-41c8-aa43-9712cb310909.png) 27 | 28 | 3. Fill the information in the request header into sender_params.json. 29 | It is worth noting that if you need multi-threaded drawing, please fill in the channelid array with the channelid of different channels (the maximum number of concurrency is 3 for Standard plan, 12 for PRO plan) 30 | 31 | 4、 Start Web-API 32 | ```shell 33 | pip -r requirements.txt 34 | python app.py 35 | ``` 36 | 37 | 5. (Optional) Go to app.py to configure cross-domain 38 | ```shell 39 | nano app.py 40 | ``` 41 | ## (Updated) Introduction to external API usage: 42 | ### Request method 43 | - post request: http://localhost:5000/api/send_and_receive" 44 | - Optional parameter: cdn=true (default false, after enabling the server will cache the image before sending, continental access is more friendly) 45 | Example: 46 | ```python 47 | import requests 48 | import json 49 | 50 | payload = { 51 | "prompt": "your_prompt_here" 52 | } 53 | 54 | url = "http://localhost:5000/api/send_and_receive"; 55 | 56 | response = requests.post(url, data=json.dumps(payload), headers={'Content-Type': 'application/json'}) 57 | 58 | print(response.json()) 59 | ``` 60 | - get request: http://localhost:5000/upscale" 61 | - Mandatory parameter: file_name(string), the name of the file that needs to be executed upscale (e.g. rockgifinrock1971_link_and_zelda_33e8886f-adae-4579-9882-bae644005f5b.png) 62 | - Mandatory parameter: number (number), the serial number of the image that needs to be executed upscale (example 1/2/3/4). 63 | - Optional parameters: cdn=true (default false, after enabling the server will cache pictures before sending, continental access more friendly) 64 | Example: 65 | ```python 66 | import requests 67 | 68 | base_url = 'http://localhost:5000' # Replace with the URL your Flask application is actually running on 69 | file_name = 'rockgifinrock1971_link_and_zelda_33e8886f-adae-4579-9882-bae644005f5b.png' # Replace with your actual file name 70 | number = 3 # Replace with the number you want to use 71 | 72 | response = requests.get(f'{base_url}/upscale', params={'file_name': file_name, 'number': number}) 73 | 74 | if response.status_code == 200. 75 | print('Success!') 76 | print(response.json()) 77 | else. 78 | print(f'Error: {response.status_code}') 79 | print(response.text) 80 | ``` 81 | ## Q&A 82 | Q: How often is the information in sender_params.json updated? 83 | A: It has been two weeks since the project was run, and it is still not expired. 84 | 85 | ## Future plan 86 | 87 | - Model switching 88 | - Multi-account concurrency 89 | - easier cookie retrieval 90 | 91 | ## Contact: 92 | For suggestions and cooperation: Me@hiripple.com 93 | Ask the author to eat Crazy Thursday to speed up the project: https://afdian.net/a/hiripple/plan 94 | 95 | ## License 96 | MIT 97 | 98 | ## Additions 99 | Sender.py and Receiver.py are based on https://github.com/George-iam/Midjourney_api二次开发 100 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import time 2 | import os 3 | import requests 4 | import re 5 | import threading 6 | import sqlite3 7 | import json 8 | from urllib.parse import urlparse 9 | from werkzeug.utils import secure_filename 10 | from flask import Flask, request, jsonify 11 | from flask import send_from_directory 12 | from prompt_sender import Sender 13 | from url_receiver import Receiver 14 | from flask_cors import CORS 15 | from upsender import Sender as UpSender 16 | 17 | app = Flask(__name__) 18 | CORS(app, origins="*") # 允许所有跨域访问,但可能存在安全风险 19 | # CORS(app, origins="https://example.com") # 添加 origins 参数以限制允许的来源 20 | global timeout_error_message 21 | timeout_error_message = None 22 | def init_db(): 23 | conn = sqlite3.connect('images.db') 24 | c = conn.cursor() 25 | c.execute('''CREATE TABLE IF NOT EXISTS images 26 | (message_id TEXT PRIMARY KEY, url TEXT, filename TEXT, thread_index INTEGER)''') 27 | conn.commit() 28 | conn.close() 29 | 30 | 31 | 32 | def clear_database(db_path): 33 | conn = sqlite3.connect(db_path) 34 | c = conn.cursor() 35 | c.execute("DELETE FROM images") 36 | conn.commit() 37 | conn.close() 38 | 39 | 40 | # 24小时刷新数据库 41 | def clear_database_every_24_hours(db_path): 42 | while True: 43 | time.sleep(24 * 60 * 60) # 等待24小时(24小时 * 60分钟 * 60秒) 44 | clear_database(db_path) 45 | 46 | 47 | def extract_uuid(filename): 48 | uuid_pattern = r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' 49 | match = re.search(uuid_pattern, filename) 50 | if match: 51 | return match.group(0) 52 | else: 53 | return None 54 | 55 | 56 | def print_stored_data(): 57 | conn = sqlite3.connect('images.db') 58 | c = conn.cursor() 59 | c.execute("SELECT * FROM images") 60 | rows = c.fetchall() 61 | conn.close() 62 | 63 | print("Stored data in the database:") 64 | print("message_id | url | filename") 65 | print("---------------------------------------------") 66 | for row in rows: 67 | print(f"{row[0]} | {row[1]} | {row[2]} | {row[3]}") 68 | 69 | 70 | def get_message_id_from_db(filename, db_path): 71 | conn = sqlite3.connect(db_path) 72 | cursor = conn.cursor() 73 | 74 | # 查询数据库以获取与 filename 相关联的 message_id 75 | cursor.execute("SELECT message_id FROM images WHERE filename=?", 76 | (filename, )) 77 | result = cursor.fetchone() 78 | 79 | if result: 80 | message_id = result[0] 81 | else: 82 | message_id = None 83 | 84 | conn.close() 85 | return message_id 86 | 87 | 88 | def reset_request_in_progress(available_thread_index): 89 | global request_in_progress 90 | global timeout_error_message 91 | 92 | with request_in_progress_lock: 93 | request_in_progress[available_thread_index] = False 94 | timeout_error_message = '请求超时,请检查是否输入了违禁词,请稍后再试' 95 | 96 | def get_thread_index_from_db(filename, db_path): 97 | conn = sqlite3.connect(db_path) 98 | cursor = conn.cursor() 99 | 100 | cursor.execute("SELECT thread_index FROM images WHERE filename=?", (filename, )) 101 | result = cursor.fetchone() 102 | 103 | if result: 104 | thread_index = result[0] 105 | else: 106 | thread_index = None 107 | 108 | conn.close() 109 | return thread_index 110 | 111 | # 创建一个 API 路由,接受 POST 请求,用户通过关键词参数发送请求 112 | @app.route('/api/send_and_receive', methods=['POST']) 113 | def send_and_receive(): 114 | global request_in_progress 115 | global timeout_error_message 116 | available_thread_index = None 117 | for i, in_progress in enumerate(request_in_progress): 118 | if not in_progress: 119 | available_thread_index = i 120 | break 121 | 122 | if available_thread_index is None: 123 | return jsonify({ 124 | 'error': 'The current queue is full, please try again later(当前队列已满,请稍后再试)' 125 | }) 126 | 127 | request_in_progress[available_thread_index] = True 128 | try: 129 | # 从请求中获取关键词参数 130 | flag = request.args.get('flag', 0) 131 | data = request.get_json() 132 | prompt = data.get('prompt') 133 | sender = Sender(params, available_thread_index, flag) 134 | sender.send(prompt) 135 | 136 | # 使用 Receiver 类接收图片 URL 137 | receiver = Receiver(params, available_thread_index) 138 | receiver.collecting_results() 139 | 140 | initial_image_timestamp = receiver.latest_image_timestamp 141 | 142 | # 设置最大等待时间 143 | max_wait_time = 300 # 最大等待时间,单位为秒 144 | 145 | # 创建一个定时器,在最大等待时间后重置request_in_progress标志 146 | timeout_timer = threading.Timer(max_wait_time, reset_request_in_progress, args=(available_thread_index,)) 147 | timeout_timer.start() 148 | 149 | 150 | # 等待新图片出现 151 | wait_time = 0 152 | while wait_time < max_wait_time: 153 | receiver.collecting_results() 154 | current_image_timestamp = receiver.latest_image_timestamp 155 | 156 | if current_image_timestamp and current_image_timestamp > initial_image_timestamp: 157 | # 发现新图片,跳出循环 158 | timeout_timer.cancel() # 取消定时器 159 | break 160 | 161 | # 等待一段时间 162 | time.sleep(1) 163 | wait_time += 1 164 | 165 | if current_image_timestamp and current_image_timestamp > initial_image_timestamp: 166 | latest_image_id = receiver.df.index[-1] 167 | latest_image_url = receiver.df.loc[latest_image_id].url 168 | latest_filename = receiver.df.loc[latest_image_id].filename 169 | cdn = request.args.get('cdn', False) 170 | if cdn: 171 | image_filename = download_image(latest_image_url) 172 | latest_image_url = f"/images/{image_filename}" 173 | conn = sqlite3.connect('images.db') 174 | c = conn.cursor() 175 | c.execute( 176 | "INSERT OR REPLACE INTO images (message_id, url, filename, thread_index) VALUES (?, ?, ?, ?)", 177 | (latest_image_id, latest_image_url, latest_filename, available_thread_index)) 178 | 179 | conn.commit() 180 | conn.close() 181 | 182 | # 输出存储在数据库中的数据 183 | print_stored_data() 184 | else: 185 | latest_image_url = None 186 | with request_in_progress_lock: 187 | request_in_progress[available_thread_index] = False 188 | # 将最新图片的URL作为响应返回 189 | if timeout_error_message: 190 | response = jsonify({'error': timeout_error_message}) 191 | timeout_error_message = None 192 | return response 193 | return jsonify({'latest_image_url': latest_image_url}) 194 | except Exception as e: 195 | print(f"Error: {e}") 196 | with request_in_progress_lock: 197 | request_in_progress[available_thread_index] = False 198 | return jsonify({'error': str(e)}) 199 | 200 | def download_image(url): 201 | response = requests.get(url) 202 | filename = secure_filename(os.path.basename(urlparse(url).path)) 203 | image_path = os.path.join('images', filename) 204 | with open(image_path, 'wb') as f: 205 | f.write(response.content) 206 | return filename 207 | 208 | 209 | @app.route('/images/', methods=['GET']) 210 | def serve_image(filename): 211 | return send_from_directory('images', filename) 212 | 213 | 214 | @app.route('/upscale', methods=['GET']) 215 | def upscale(): 216 | global request_in_progress 217 | global timeout_error_message 218 | 219 | file_name = request.args.get('file_name') 220 | number = request.args.get('number') 221 | 222 | if file_name is None or number is None: 223 | return jsonify({'error': 'Both file_name and number parameters are required'}) 224 | 225 | thread_index = get_thread_index_from_db(file_name, 'images.db') 226 | 227 | if thread_index is None: 228 | return jsonify({'error': f'No thread_index found for file_name: {file_name}'}) 229 | 230 | if request_in_progress[thread_index]: 231 | return jsonify({ 232 | 'error': 'The current queue is full, please try again later(当前队列已满,请稍后再试)'}) 233 | 234 | request_in_progress[thread_index] = True 235 | file_name = request.args.get('file_name') 236 | number = request.args.get('number') 237 | 238 | if file_name is None or number is None: 239 | return jsonify( 240 | {'error': 'Both file_name and number parameters are required'}) 241 | 242 | message_id = get_message_id_from_db(file_name, 'images.db') 243 | 244 | if message_id is None: 245 | return jsonify( 246 | {'error': f'No message_id found for file_name: {file_name}'}) 247 | try: 248 | sender = UpSender(params, thread_index) 249 | uuid = extract_uuid(file_name) 250 | sender.send(message_id, number, uuid) 251 | 252 | # 使用 Receiver 类接收图片 URL 253 | receiver = Receiver(params, thread_index) 254 | receiver.collecting_results() 255 | 256 | initial_image_timestamp = receiver.latest_image_timestamp 257 | 258 | # 等待新图片出现 259 | max_wait_time = 300 # 最大等待时间,单位为秒 260 | wait_time = 0 261 | while wait_time < max_wait_time: 262 | receiver.collecting_results() 263 | current_image_timestamp = receiver.latest_image_timestamp 264 | if current_image_timestamp and current_image_timestamp > initial_image_timestamp: 265 | # 发现新图片,跳出循环 266 | break 267 | 268 | # 等待一段时间 269 | time.sleep(1) 270 | wait_time += 1 271 | if current_image_timestamp and current_image_timestamp > initial_image_timestamp: 272 | latest_image_id = receiver.df.index[-1] 273 | latest_image_url = receiver.df.loc[latest_image_id].url 274 | cdn = request.args.get('cdn', False) 275 | if cdn: 276 | image_filename = download_image(latest_image_url) 277 | latest_image_url = f"/images/{image_filename}" 278 | else: 279 | latest_image_url = None 280 | 281 | with request_in_progress_lock: 282 | request_in_progress[thread_index] = False 283 | # 将最新图片的URL作为响应返回 284 | return jsonify({'latest_image_url': latest_image_url}) 285 | except Exception as e: 286 | print(f"Error: {e}") 287 | with request_in_progress_lock: 288 | request_in_progress[thread_index] = False 289 | return jsonify({'error': str(e)}) 290 | 291 | if __name__ == "__main__": 292 | 293 | current_directory = os.path.dirname(os.path.abspath(__file__)) 294 | params = os.path.join(current_directory, 'sender_params.json') 295 | with open(params, 'r') as f: 296 | sender_params = json.load(f) 297 | 298 | channelid = sender_params['channelid'] 299 | num_threads = len(channelid) 300 | request_in_progress = [False] * num_threads 301 | request_in_progress_lock = threading.Lock() 302 | init_db() 303 | db_path = 'images.db' 304 | clear_db_thread = threading.Thread(target=clear_database_every_24_hours, 305 | args=(db_path, )) 306 | clear_db_thread.daemon = True # 设置为守护线程,这样在主程序结束时,线程也会结束 307 | clear_db_thread.start() 308 | app.run(debug=True, host='0.0.0.0') 309 | --------------------------------------------------------------------------------