BG5 commited on
Commit
41c1b5e
·
1 Parent(s): da06705

Upload 116 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. Dockerfile +34 -0
  2. LICENSE +201 -0
  3. README3333.md +79 -0
  4. docker/Dockerfile +35 -0
  5. docker/build-image.sh +34 -0
  6. docker/build-manifest.sh +21 -0
  7. docs/api.md +139 -0
  8. docs/config.md +68 -0
  9. docs/discord-params.md +11 -0
  10. docs/docker-start.md +21 -0
  11. docs/img_10.png +0 -0
  12. docs/img_8.png +0 -0
  13. docs/img_9.png +0 -0
  14. docs/manager-qrcode.png +0 -0
  15. docs/params_user.png +0 -0
  16. docs/railway-start.md +36 -0
  17. docs/railway_img_1.png +0 -0
  18. docs/railway_img_10.png +0 -0
  19. docs/railway_img_11.png +0 -0
  20. docs/railway_img_12.png +0 -0
  21. docs/railway_img_2.png +0 -0
  22. docs/railway_img_3.png +0 -0
  23. docs/railway_img_4.png +0 -0
  24. docs/railway_img_5.png +0 -0
  25. docs/railway_img_6.png +0 -0
  26. docs/railway_img_7.png +0 -0
  27. docs/railway_img_8.png +0 -0
  28. docs/railway_img_9.png +0 -0
  29. docs/receipt-code.png +0 -0
  30. docs/zeabur-start.md +33 -0
  31. pom.xml +120 -0
  32. src/main/java/com/github/novicezk/midjourney/Constants.java +20 -0
  33. src/main/java/com/github/novicezk/midjourney/ProxyApplication.java +19 -0
  34. src/main/java/com/github/novicezk/midjourney/ProxyProperties.java +207 -0
  35. src/main/java/com/github/novicezk/midjourney/ReturnCode.java +42 -0
  36. src/main/java/com/github/novicezk/midjourney/controller/AccountController.java +36 -0
  37. src/main/java/com/github/novicezk/midjourney/controller/SubmitController.java +228 -0
  38. src/main/java/com/github/novicezk/midjourney/controller/TaskController.java +64 -0
  39. src/main/java/com/github/novicezk/midjourney/domain/DiscordAccount.java +38 -0
  40. src/main/java/com/github/novicezk/midjourney/domain/DomainObject.java +72 -0
  41. src/main/java/com/github/novicezk/midjourney/dto/BaseSubmitDTO.java +16 -0
  42. src/main/java/com/github/novicezk/midjourney/dto/SubmitBlendDTO.java +21 -0
  43. src/main/java/com/github/novicezk/midjourney/dto/SubmitChangeDTO.java +25 -0
  44. src/main/java/com/github/novicezk/midjourney/dto/SubmitDescribeDTO.java +15 -0
  45. src/main/java/com/github/novicezk/midjourney/dto/SubmitImagineDTO.java +26 -0
  46. src/main/java/com/github/novicezk/midjourney/dto/SubmitSimpleChangeDTO.java +17 -0
  47. src/main/java/com/github/novicezk/midjourney/dto/TaskConditionDTO.java +14 -0
  48. src/main/java/com/github/novicezk/midjourney/enums/BlendDimensions.java +21 -0
  49. src/main/java/com/github/novicezk/midjourney/enums/MessageType.java +26 -0
  50. src/main/java/com/github/novicezk/midjourney/enums/TaskAction.java +30 -0
Dockerfile ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM maven:3.8.5-openjdk-17
2
+
3
+ ARG user=spring
4
+ ARG group=spring
5
+
6
+ ENV SPRING_HOME=/home/spring
7
+
8
+ RUN groupadd -g 1000 ${group} \
9
+ && useradd -d "$SPRING_HOME" -u 1000 -g 1000 -m -s /bin/bash ${user} \
10
+ && mkdir -p $SPRING_HOME/config \
11
+ && mkdir -p $SPRING_HOME/logs \
12
+ && chown -R ${user}:${group} $SPRING_HOME/config $SPRING_HOME/logs
13
+
14
+ # Railway 不支持使用 VOLUME, 本地需要构建时,取消下一行的注释
15
+ # VOLUME ["$SPRING_HOME/config", "$SPRING_HOME/logs"]
16
+
17
+ USER ${user}
18
+ WORKDIR $SPRING_HOME
19
+
20
+ COPY . .
21
+
22
+ RUN mvn clean package \
23
+ && mv target/midjourney-proxy-*.jar ./app.jar \
24
+ && rm -rf target
25
+
26
+ EXPOSE 8080 9876
27
+
28
+ ENV JAVA_OPTS -XX:MaxRAMPercentage=85 -Djava.awt.headless=true -XX:+HeapDumpOnOutOfMemoryError \
29
+ -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35 -Xlog:gc:file=/home/spring/logs/gc.log \
30
+ -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9876 -Dcom.sun.management.jmxremote.ssl=false \
31
+ -Dcom.sun.management.jmxremote.authenticate=false -Dlogging.file.path=/home/spring/logs \
32
+ -Dserver.port=8080 -Duser.timezone=Asia/Shanghai
33
+
34
+ ENTRYPOINT ["bash","-c","java $JAVA_OPTS -jar app.jar"]
LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.
README3333.md ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # midjourney-proxy
2
+
3
+ 代理 MidJourney 的discord频道,实现api形式调用AI绘图
4
+
5
+ [![GitHub release](https://img.shields.io/static/v1?label=release&message=v2.5&color=blue)](https://www.github.com/novicezk/midjourney-proxy)
6
+ [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
7
+
8
+ ## 主要功能
9
+ - [x] 支持 Imagine 指令和相关动作
10
+ - [x] Imagine 时支持添加图片base64,作为垫图
11
+ - [x] 支持 Blend(图片混合)、Describe(图生文) 指令
12
+ - [x] 支持任务实时进度
13
+ - [x] 支持中英文翻译,需配置百度翻译或gpt
14
+ - [x] prompt 敏感词判断,支持覆盖调整
15
+ - [x] user-token 连接 wss,可以获取错误信息和完整功能
16
+ - [x] 支持 discord域名(server、cdn、wss)反代,配置 mj.ng-discord
17
+ - [x] 支持多账号配置,每个账号可设置对应的任务队列
18
+
19
+ **🚀 更多功能请查看 [midjourney-proxy-plus](https://github.com/litter-coder/midjourney-proxy-plus)**
20
+ > - [x] 支持开源版的所有功能
21
+ > - [x] 支持 Shorten(prompt分析) 指令
22
+ > - [x] 支持焦点移动: Pan ⬅️ ➡️ ⬆️ ⬇️
23
+ > - [x] 支持图片变焦: Zoom 🔍
24
+ > - [x] 支持局部重绘: Vary (Region) 🖌
25
+ > - [x] 支持几乎所有的关联按钮动作和🎛️ Remix模式
26
+ > - [x] 支持获取图片的seed值
27
+ > - [x] 中英文翻译额外支持deepl
28
+ > - [x] 账号池持久化,动态维护
29
+ > - [x] 支持获取账号/info、/settings信息
30
+ > - [x] 内嵌管理后台页面
31
+
32
+ ## 使用前提
33
+ 1. 注册并订阅 MidJourney,创建自己的频道,参考 https://docs.midjourney.com/docs/quick-start
34
+ 2. 获取用户Token、服务器ID、频道ID:[获取方式](./docs/discord-params.md)
35
+
36
+ ## 快速启动
37
+ 1. `Railway`: 基于Railway平台,不需要自己的服务器: [部署方式](./docs/railway-start.md);若Railway不能使用,可使用Zeabur启动
38
+ 2. `Zeabur`: 基于Zeabur平台,不需要自己的服务器: [部署方式](./docs/zeabur-start.md)
39
+ 3. `Docker`: 在服务器或本地使用Docker启动: [部署方式](./docs/docker-start.md)
40
+
41
+ ## 本地开发
42
+ - 依赖java17和maven
43
+ - 更改配置项: 修改src/main/application.yml
44
+ - 项目运行: 启动ProxyApplication的main函数
45
+ - 更改代码后,构建镜像: Dockerfile取消VOLUME的注释,执行 `docker build . -t midjourney-proxy`
46
+
47
+ ## 配置项
48
+ - mj.accounts: 参考 [账号池配置](./docs/config.md#%E8%B4%A6%E5%8F%B7%E6%B1%A0%E9%85%8D%E7%BD%AE%E5%8F%82%E8%80%83)
49
+ - mj.task-store.type: 任务存储方式,默认in_memory(内存\重启后丢失),可选redis
50
+ - mj.task-store.timeout: 任务存储过期时间,过期后删除,默认30天
51
+ - mj.api-secret: 接口密钥,为空不启用鉴权;调用接口时需要加请求头 mj-api-secret
52
+ - mj.translate-way: 中文prompt翻译成英文的方式,可选null(默认)、baidu、gpt、deepl
53
+ - 更多配置查看 [配置项](./docs/config.md)
54
+
55
+ ## 相关文档
56
+ 1. [API接口说明](./docs/api.md)
57
+ 2. [版本更新记录](https://github.com/novicezk/midjourney-proxy/wiki/%E6%9B%B4%E6%96%B0%E8%AE%B0%E5%BD%95)
58
+
59
+ ## 注意事项
60
+ 1. 作图频繁等行为,可能会触发midjourney账号警告,请谨慎使用
61
+ 2. 常见问题及解决办法见 [Wiki / FAQ](https://github.com/novicezk/midjourney-proxy/wiki/FAQ)
62
+ 3. 在 [Issues](https://github.com/novicezk/midjourney-proxy/issues) 中提出其他问题或建议
63
+ 4. 感兴趣的朋友也欢迎加入交流群讨论一下,扫码进群名额已满,加管理员微信邀请进群
64
+
65
+ <img src="https://raw.githubusercontent.com/novicezk/midjourney-proxy/main/docs/manager-qrcode.png" width="220" alt="微信二维码"/>
66
+
67
+ ## 应用项目
68
+ 依赖此项目且开源的,欢迎联系作者,加到此处展示
69
+ - [wechat-midjourney](https://github.com/novicezk/wechat-midjourney) : 代理微信客户端,接入MidJourney,仅示例应用场景,不再更新
70
+ - [stable-diffusion-mobileui](https://github.com/yuanyuekeji/stable-diffusion-mobileui) : SDUI,基于本接口和SD,可一键打包生成H5和小程序
71
+ - [ChatGPT-Midjourney](https://github.com/Licoy/ChatGPT-Midjourney) : 一键拥有你自己的 ChatGPT+Midjourney 网页服务
72
+ - [MidJourney-Web](https://github.com/ConnectAI-E/MidJourney-Web) : 🍎 Supercharged Experience For MidJourney On Web UI
73
+
74
+ ## 其它
75
+ 如果觉得这个项目对你有所帮助,请帮忙点个star;也可以请作者喝杯茶~
76
+
77
+ <img src="https://raw.githubusercontent.com/novicezk/midjourney-proxy/main/docs/receipt-code.png" width="220" alt="二维码"/>
78
+
79
+ [![Star History Chart](https://api.star-history.com/svg?repos=novicezk/midjourney-proxy&type=Date)](https://star-history.com/#novicezk/midjourney-proxy&Date)
docker/Dockerfile ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM openjdk:17.0
2
+
3
+ ARG user=spring
4
+ ARG group=spring
5
+
6
+ ENV SPRING_HOME=/home/spring
7
+ ENV APP_HOME=$SPRING_HOME/app
8
+
9
+ ENV JAVA_OPTS -XX:MaxRAMPercentage=85 -Djava.awt.headless=true -XX:+HeapDumpOnOutOfMemoryError \
10
+ -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35 -Xlog:gc:file=/home/spring/logs/gc.log \
11
+ -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9876 -Dcom.sun.management.jmxremote.ssl=false \
12
+ -Dcom.sun.management.jmxremote.authenticate=false -Dlogging.file.path=/home/spring/logs \
13
+ -Dserver.port=8080 -Duser.timezone=Asia/Shanghai
14
+
15
+ RUN groupadd -g 1000 ${group} \
16
+ && useradd -d "$SPRING_HOME" -u 1000 -g 1000 -m -s /bin/bash ${user} \
17
+ && mkdir -p $SPRING_HOME/config \
18
+ && mkdir -p $SPRING_HOME/logs \
19
+ && mkdir -p $APP_HOME \
20
+ && chown -R ${user}:${group} $SPRING_HOME/config $SPRING_HOME/logs $APP_HOME
21
+
22
+ VOLUME ["$SPRING_HOME/config", "$SPRING_HOME/logs"]
23
+
24
+ USER ${user}
25
+
26
+ WORKDIR $SPRING_HOME
27
+
28
+ EXPOSE 8080 9876
29
+
30
+ ENTRYPOINT ["bash","-c","java $JAVA_OPTS -cp ./app org.springframework.boot.loader.JarLauncher"]
31
+
32
+ COPY --chown=${user}:${group} dependencies $APP_HOME/
33
+ COPY --chown=${user}:${group} spring-boot-loader $APP_HOME/
34
+ COPY --chown=${user}:${group} snapshot-dependencies $APP_HOME/
35
+ COPY --chown=${user}:${group} application $APP_HOME/
docker/build-image.sh ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ set -e -u -o pipefail
3
+
4
+ if [ $# -lt 1 ]; then
5
+ echo 'version is required'
6
+ exit 1
7
+ fi
8
+
9
+ VERSION=$1
10
+ ARCH=amd64
11
+
12
+ if [ $# -ge 2 ]; then
13
+ ARCH=$2
14
+ fi
15
+
16
+ JAR_FILE_COUNT=$(find "../target/" -maxdepth 1 -name '*.jar' | wc -l)
17
+ if [ $JAR_FILE_COUNT == 0 ]; then
18
+ echo "jar file not found, please execute: mvn clean package"
19
+ exit 1
20
+ fi
21
+
22
+ JAR_FILE_NAME=$(ls ../target/*.jar|grep -v source)
23
+ echo ${JAR_FILE_NAME}
24
+
25
+ cp ${JAR_FILE_NAME} ./app.jar
26
+
27
+ java -Djarmode=layertools -jar app.jar extract
28
+
29
+ docker build . -t midjourney-proxy:${VERSION}
30
+
31
+ rm -rf application dependencies snapshot-dependencies spring-boot-loader app.jar
32
+
33
+ docker tag midjourney-proxy:${VERSION} novicezk/midjourney-proxy-${ARCH}:${VERSION}
34
+ docker push novicezk/midjourney-proxy-${ARCH}:${VERSION}
docker/build-manifest.sh ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ set -e -u -o pipefail
3
+
4
+ if [ $# -lt 1 ]; then
5
+ echo 'version is required'
6
+ exit 1
7
+ fi
8
+
9
+ VERSION=$1
10
+
11
+ echo "create manifest..."
12
+ docker manifest create novicezk/midjourney-proxy:${VERSION} novicezk/midjourney-proxy-amd64:${VERSION} novicezk/midjourney-proxy-arm64v8:${VERSION}
13
+
14
+ echo "annotate amd64..."
15
+ docker manifest annotate novicezk/midjourney-proxy:${VERSION} novicezk/midjourney-proxy-amd64:${VERSION} --os linux --arch amd64
16
+
17
+ echo "annotate arm64v8..."
18
+ docker manifest annotate novicezk/midjourney-proxy:${VERSION} novicezk/midjourney-proxy-arm64v8:${VERSION} --os linux --arch arm64 --variant v8
19
+
20
+ echo "push manifest..."
21
+ docker manifest push novicezk/midjourney-proxy:${VERSION}
docs/api.md ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # API接口说明
2
+
3
+ `http://ip:port/mj` 已有api文档,此处仅作补充
4
+
5
+ ## 1. 数据结构
6
+
7
+ ### 任务
8
+ | 字段 | 类型 | 示例 | 描述 |
9
+ |:-----:|:----:|:----|:----|
10
+ | id | string | 1689231405853400 | 任务ID |
11
+ | action | string | IMAGINE | 任务类型: IMAGINE(绘图)、UPSCALE(选中放大)、VARIATION(选中变换)、REROLL(重新执行)、DESCRIBE(图生文)、BLEAND(图片混合) |
12
+ | status | string | SUCCESS | 任务状态: NOT_START(未启动)、SUBMITTED(已提交处理)、IN_PROGRESS(执行中)、FAILURE(失败)、SUCCESS(成功) |
13
+ | prompt | string | 猫猫 | 提示词 |
14
+ | promptEn | string | Cat | 英文提示词 |
15
+ | description | string | /imagine 猫猫 | 任务描述 |
16
+ | submitTime | number | 1689231405854 | 提交时间 |
17
+ | startTime | number | 1689231442755 | 开始执行时间 |
18
+ | finishTime | number | 1689231544312 | 结束时间 |
19
+ | progress | string | 100% | 任务进度 |
20
+ | imageUrl | string | https://cdn.discordapp.com/attachments/xxx/xxx/xxxx.png | 生成图片的url, 成功或执行中时有值,可能为png或webp |
21
+ | failReason | string | [Invalid parameter] Invalid value | 失败原因, 失败时有值 |
22
+ | properties | object | {"finalPrompt": "Cat"} | 任务的扩展属性,系统内部使用 |
23
+
24
+
25
+ ## 2. 任务提交返回
26
+ - code=1: 提交成功,result为任务ID
27
+ ```json
28
+ {
29
+ "code": 1,
30
+ "description": "成功",
31
+ "result": "8498455807619990",
32
+ "properties": {
33
+ "discordInstanceId": "1118138338562560102"
34
+ }
35
+ }
36
+ ```
37
+ - code=21: 任务已存在,U时可能发生
38
+ ```json
39
+ {
40
+ "code": 21,
41
+ "description": "任务已存在",
42
+ "result": "0741798445574458",
43
+ "properties": {
44
+ "status": "SUCCESS",
45
+ "imageUrl": "https://xxxx"
46
+ }
47
+ }
48
+ ```
49
+ - code=22: 提交成功,进入队列等待
50
+ ```json
51
+ {
52
+ "code": 22,
53
+ "description": "排队中,前面还有1个任务",
54
+ "result": "0741798445574458",
55
+ "properties": {
56
+ "numberOfQueues": 1,
57
+ "discordInstanceId": "1118138338562560102"
58
+ }
59
+ }
60
+ ```
61
+ - code=23: 队列已满,请稍后尝试
62
+ ```json
63
+ {
64
+ "code": 23,
65
+ "description": "队列已满,请稍后尝试",
66
+ "result": "14001929738841620",
67
+ "properties": {
68
+ "discordInstanceId": "1118138338562560102"
69
+ }
70
+ }
71
+ ```
72
+ - code=24: prompt包含敏感词
73
+ ```json
74
+ {
75
+ "code": 24,
76
+ "description": "可能包含敏感词",
77
+ "properties": {
78
+ "promptEn": "nude body",
79
+ "bannedWord": "nude"
80
+ }
81
+ }
82
+ ```
83
+ - other: 提交错误,description为错误描述
84
+
85
+ ## 3. `/mj/submit/simple-change` 绘图变化-simple
86
+ 接口作用同 `/mj/submit/change`(绘图变化),传参方式不同,该接口接收content,格式为`ID 操作`,例如:1320098173412546 U2
87
+
88
+ - 放大 U1~U4
89
+ - 变换 V1~V4
90
+ - 重新执行 R
91
+
92
+ ## 4. `/mj/submit/describe` 图生文
93
+ ```json
94
+ {
95
+ // 图片的base64字符串
96
+ "base64": "data:image/png;base64,xxx"
97
+ }
98
+ ```
99
+
100
+ 后续任务完成后,properties中finalPrompt即为图片生成的prompt
101
+ ```json
102
+ {
103
+ "id":"14001929738841620",
104
+ "action":"DESCRIBE",
105
+ "status": "SUCCESS",
106
+ "description":"/describe 14001929738841620.png",
107
+ "imageUrl":"https://cdn.discordapp.com/attachments/xxx/xxx/14001929738841620.png",
108
+ "properties": {
109
+ "finalPrompt": "1️⃣ Cat --ar 5:4\n\n2️⃣ Cat2 --ar 5:4\n\n3️⃣ Cat3 --ar 5:4\n\n4️⃣ Cat4 --ar 5:4"
110
+ }
111
+ // ...
112
+ }
113
+ ```
114
+
115
+ ## 5. 任务变更回调
116
+ 任务状态变化或进度改变时,会调用业务系统的接口
117
+ - 接口地址为配置的 mj.notify-hook,任务提交时支持传`notifyHook`以改变此任务的回调地址
118
+ - 两者都为空时,不触发回调
119
+
120
+ POST application/json
121
+ ```json
122
+ {
123
+ "id": "14001929738841620",
124
+ "action": "IMAGINE",
125
+ "status": "SUCCESS",
126
+ "prompt": "猫猫",
127
+ "promptEn": "Cat",
128
+ "description": "/imagine 猫猫",
129
+ "submitTime": 1689231405854,
130
+ "startTime": 1689231442755,
131
+ "finishTime": 1689231544312,
132
+ "progress": "100%",
133
+ "imageUrl": "https://cdn.discordapp.com/attachments/xxx/xxx/xxxx.png",
134
+ "failReason": null,
135
+ "properties": {
136
+ "finalPrompt": "Cat"
137
+ }
138
+ }
139
+ ```
docs/config.md ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## 配置项
2
+
3
+ | 变量名 | 非空 | 描述 |
4
+ |:------------------------------|:--:|:----------------------------------------------|
5
+ | mj.accounts | 是 | [账号池配置](./config.md#%E8%B4%A6%E5%8F%B7%E6%B1%A0%E9%85%8D%E7%BD%AE%E5%8F%82%E8%80%83),配置后不需要额外设置mj.discord |
6
+ | mj.discord.guild-id | 是 | discord服务器ID |
7
+ | mj.discord.channel-id | 是 | discord频道ID |
8
+ | mj.discord.user-token | 是 | discord用户Token |
9
+ | mj.discord.user-agent | 否 | 调用discord接口、连接wss时的user-agent,建议从浏览器network复制 |
10
+ | mj.discord.core-size | 否 | 并发数,默认为3 |
11
+ | mj.discord.queue-size | 否 | 等待队列,默认长度10 |
12
+ | mj.discord.timeout-minutes | 否 | 任务超时时间,默认为5分钟 |
13
+ | mj.api-secret | 否 | 接口密钥,为空不启用鉴权;调用接口时需要加请求头 mj-api-secret |
14
+ | mj.notify-hook | 否 | 全局的任务状态变更回调地址 |
15
+ | mj.notify-notify-pool-size | 否 | 通知回调线程池大小,默认10 |
16
+ | mj.task-store.type | 否 | 任务存储方式,默认in_memory(内存\重启后丢失),可选redis |
17
+ | mj.task-store.timeout | 否 | 任务过期时间,过期后删除,默认30天 |
18
+ | mj.proxy.host | 否 | 代理host,全局代理不生效时设置 |
19
+ | mj.proxy.port | 否 | 代理port,全局代理不生效时设置 |
20
+ | mj.ng-discord.server | 否 | https://discord.com 反代地址 |
21
+ | mj.ng-discord.cdn | 否 | https://cdn.discordapp.com 反代地址 |
22
+ | mj.ng-discord.wss | 否 | wss://gateway.discord.gg 反代地址 |
23
+ | mj.translate-way | 否 | 中文prompt翻译成英文的方式,可选null(默认)、baidu、gpt |
24
+ | mj.baidu-translate.appid | 否 | 百度翻译的appid |
25
+ | mj.baidu-translate.app-secret | 否 | 百度翻译的app-secret |
26
+ | mj.openai.gpt-api-url | 否 | 自定义gpt的接口地址,默认不需要配置 |
27
+ | mj.openai.gpt-api-key | 否 | gpt的api-key |
28
+ | mj.openai.timeout | 否 | openai调用的超时时间,默认30秒 |
29
+ | mj.openai.model | 否 | openai的模型,默认gpt-3.5-turbo |
30
+ | mj.openai.max-tokens | 否 | 返回结果的最大分词数,默认2048 |
31
+ | mj.openai.temperature | 否 | 相似度(0-2.0),默认0 |
32
+ | spring.redis | 否 | 任务存储方式设置为redis,需配置redis相关属性 |
33
+
34
+ ### 账号池配置参考
35
+ ```yaml
36
+ mj:
37
+ accounts:
38
+ - guild-id: xxx
39
+ channel-id: xxx
40
+ user-token: xxxx
41
+ user-agent: xxxx
42
+ - guild-id: xxx
43
+ channel-id: xxx
44
+ user-token: xxxx
45
+ user-agent: xxxx
46
+ ```
47
+
48
+ 账号字段说明
49
+
50
+ | 名称 | 非空 | 描述 |
51
+ |:------------------| :----: |:--------------------------------------------------------------------|
52
+ | guild-id | 是 | discord服务器ID |
53
+ | channel-id | 是 | discord频道ID |
54
+ | user-token | 是 | discord用户Token |
55
+ | user-agent | 否 | 调用discord接口、连接wss时的user-agent,建议从浏览器network复制 |
56
+ | enable | 否 | 是否可用,默认true |
57
+ | core-size | 否 | 并发数,默认3 |
58
+ | queue-size | 否 | 等待队列长度,默认10 |
59
+ | timeout-minutes | 否 | 任务超时时间(分钟),默认5 |
60
+
61
+ ### spring.redis配置参考
62
+ ```yaml
63
+ spring:
64
+ redis:
65
+ host: 10.107.xxx.xxx
66
+ port: 6379
67
+ password: xxx
68
+ ```
docs/discord-params.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## 获取discord配置参数
2
+
3
+ ### 1. 获取用户Token
4
+ 进入频道,打开network,刷新页面,找到 `messages` 的请求,这里的 authorization 即用户Token,后续设置到 `mj.discord.user-token`
5
+
6
+ ![User Token](img_8.png)
7
+
8
+ ### 2. 获取服务器ID、频道ID
9
+
10
+ 频道的url里取出 服务器ID、频道ID,后续设置到配置项
11
+ ![Guild Channel ID](img_9.png)
docs/docker-start.md ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Docker 部署教程
2
+
3
+ 1. /xxx/xxx/config目录下创建 application.yml(mj配置项)、banned-words.txt(可选,覆盖默认的敏感词文件);参考src/main/resources下的文件
4
+ 2. 启动容器,映射config目录
5
+ ```shell
6
+ docker run -d --name midjourney-proxy \
7
+ -p 8080:8080 \
8
+ -v /xxx/xxx/config:/home/spring/config \
9
+ novicezk/midjourney-proxy:2.5
10
+ ```
11
+ 3. 访问 `http://ip:port/mj` 查看API文档
12
+
13
+ 附: 不映射config目录方式,直接在启动命令中设置参数
14
+ ```shell
15
+ docker run -d --name midjourney-proxy \
16
+ -p 8080:8080 \
17
+ -e mj.discord.guild-id=xxx \
18
+ -e mj.discord.channel-id=xxx \
19
+ -e mj.discord.user-token=xxx \
20
+ novicezk/midjourney-proxy:2.5
21
+ ```
docs/img_10.png ADDED
docs/img_8.png ADDED
docs/img_9.png ADDED
docs/manager-qrcode.png ADDED
docs/params_user.png ADDED
docs/railway-start.md ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Railway 部署教程
2
+
3
+ Railway是一个提供弹性部署方案的平台,服务器在海外,方便MidJourney的调用。
4
+
5
+ **Railway 提供 5 美元,500 个小时/月的免费额度**
6
+
7
+ ### 1. Fork本仓库
8
+ ### 2. Railway使用github账号登录
9
+ 进入 [railway官网](https://railway.app) 选择 `Login` -> `Github`,登录github账号
10
+
11
+ ### 3. [New Project](https://railway.app/new) 添加对fork仓库的授权
12
+ ![railway_img_1](./railway_img_1.png)
13
+ ![railway_img_2](./railway_img_2.png)
14
+ ![railway_img_3](./railway_img_3.png)
15
+
16
+ ### 4. 选择该fork仓库,新建项目,设置环境变量
17
+ ![railway_img_4](./railway_img_4.png)
18
+ ![railway_img_5](./railway_img_5.png)
19
+ ![railway_img_6](./railway_img_6.png)
20
+ ![railway_img_7](./railway_img_7.png)
21
+ 此处配置项参考 [Wiki / 配置项](https://github.com/novicezk/midjourney-proxy/wiki/%E9%85%8D%E7%BD%AE%E9%A1%B9) ,建议配置api密钥启用鉴权,接口调用时需添加请求头 `mj-api-secret`
22
+
23
+ ### 5. 启动服务
24
+ 进入刚才的Project,它应该已经在自动部署了,后续更新配置之后会自动重新部署
25
+ ![railway_img_8](./railway_img_8.png)
26
+
27
+ 若部署启动失败请查看日志,检查配置项
28
+ ![railway_img_9](./railway_img_9.png)
29
+ ![railway_img_10](./railway_img_10.png)
30
+
31
+ ### 6. 开始使用
32
+ 等待部署成功后,生成随机域名
33
+ ![railway_img_11](./railway_img_11.png)
34
+ ![railway_img_12](./railway_img_12.png)
35
+
36
+ 访问 `https://midjourney-proxy-***.app/mj`
docs/railway_img_1.png ADDED
docs/railway_img_10.png ADDED
docs/railway_img_11.png ADDED
docs/railway_img_12.png ADDED
docs/railway_img_2.png ADDED
docs/railway_img_3.png ADDED
docs/railway_img_4.png ADDED
docs/railway_img_5.png ADDED
docs/railway_img_6.png ADDED
docs/railway_img_7.png ADDED
docs/railway_img_8.png ADDED
docs/railway_img_9.png ADDED
docs/receipt-code.png ADDED
docs/zeabur-start.md ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Zeabur 部署教程
2
+
3
+ ### Zeabur 优势
4
+ 1. 新注册的 `Github` 账号可能无法使用 `Railway`,但是能用 `Zeabur`
5
+ 2. 通过 `Railway` 部署的项目会自动生成一个域名,然而因为某些原因,形如 `*.up.railway.app` 的域名在国内无法访问
6
+ 3. `Zeabur` 服务器运行在国外,但是其生成的域名 `*.zeabur.app` 没有被污染,国内可直接访问
7
+
8
+ ### 开始部署
9
+
10
+ 1. 打开网址 https://zeabur.com/zh-CN
11
+ 2. 点击现在开始
12
+ 3. 点击 `Sign in with GitHub`
13
+ 4. 登陆你的 `Github` 账号
14
+ 5. 点击 `Authorize zeabur` 授权
15
+ 6. 点击 `创建项目` 并输入一个项目名称,点击 `创建`
16
+ 7. 点击 `+` 添加服务,选择 `Git-Deploy service from source code in GitHub repository.`
17
+ 8. 点击 `Configure GitHub` 根据需要选择 `All repositories` 或者 `Only select repositories`
18
+ 9. 点击 `install`,之后自动跳转,最好再刷新一下页面
19
+ 10. 点击 你 fork 的 `midjourney-proxy` 项目
20
+ 11. 点击环境变量,点击编辑原始环境变量,添加你需要的环境变量
21
+ 12. 关于环境变量,与 `Railway` 稍有不同,需要把 `.` 和 `-` 全部换成 `_`,例如如下格式
22
+ ```properties
23
+ PORT=8080
24
+ mj_discord_guild_id=xxx
25
+ mj_discord_channel_id=xxx
26
+ mj_discord_user_token=xxx
27
+ mj_api_secret=***
28
+ ```
29
+ 此处配置项参考 [Wiki / 配置项](https://github.com/novicezk/midjourney-proxy/wiki/%E9%85%8D%E7%BD%AE%E9%A1%B9) ,建议配置api密钥启用鉴权,接口调用时需添加请求头 `mj-api-secret`
30
+ 13. 然后取消 `Building`,点击 `Redeploy` (此做法是为了让环境变量生效)
31
+ 14. 部署 `midjourney-proxy` 大概需要 `2` 分钟,此时你可以做的是:配置域名
32
+ 15. 点击下方的域名,点击生成域名,输入前缀,例如 `midjourney-proxy-demo`,点击保存;或者添加自定义域名,之后加上 `CNAME` 解析
33
+ 16. 等待部署成功,访问 `https://midjourney-proxy-demo.zeabur.app/mj`
pom.xml ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project xmlns="http://maven.apache.org/POM/4.0.0"
3
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5
+ <modelVersion>4.0.0</modelVersion>
6
+
7
+ <parent>
8
+ <groupId>org.springframework.boot</groupId>
9
+ <artifactId>spring-boot-starter-parent</artifactId>
10
+ <version>2.6.14</version>
11
+ </parent>
12
+
13
+ <groupId>com.github.novicezk</groupId>
14
+ <artifactId>midjourney-proxy</artifactId>
15
+ <version>2.5</version>
16
+
17
+ <properties>
18
+ <hutool.version>5.8.18</hutool.version>
19
+ <org-json.version>20220924</org-json.version>
20
+ <jda.version>5.0.0-beta.9</jda.version>
21
+ <chatgpt-java.version>1.0.14-beta1</chatgpt-java.version>
22
+ <dataurl.version>2.0.0</dataurl.version>
23
+ <knife4j.verison>4.1.0</knife4j.verison>
24
+ <user-agent-utils.verison>1.21</user-agent-utils.verison>
25
+ <httpclient.verison>4.5.14</httpclient.verison>
26
+ <java.version>17</java.version>
27
+ <maven.compiler.source>${java.version}</maven.compiler.source>
28
+ <maven.compiler.target>${java.version}</maven.compiler.target>
29
+ </properties>
30
+
31
+ <dependencies>
32
+ <dependency>
33
+ <groupId>org.springframework.boot</groupId>
34
+ <artifactId>spring-boot-starter-web</artifactId>
35
+ </dependency>
36
+ <dependency>
37
+ <groupId>org.springframework.boot</groupId>
38
+ <artifactId>spring-boot-starter-data-redis</artifactId>
39
+ </dependency>
40
+
41
+ <dependency>
42
+ <groupId>cn.hutool</groupId>
43
+ <artifactId>hutool-core</artifactId>
44
+ <version>${hutool.version}</version>
45
+ </dependency>
46
+ <dependency>
47
+ <groupId>cn.hutool</groupId>
48
+ <artifactId>hutool-cache</artifactId>
49
+ <version>${hutool.version}</version>
50
+ </dependency>
51
+ <dependency>
52
+ <groupId>cn.hutool</groupId>
53
+ <artifactId>hutool-crypto</artifactId>
54
+ <version>${hutool.version}</version>
55
+ </dependency>
56
+ <dependency>
57
+ <groupId>org.json</groupId>
58
+ <artifactId>json</artifactId>
59
+ <version>${org-json.version}</version>
60
+ </dependency>
61
+ <dependency>
62
+ <groupId>net.dv8tion</groupId>
63
+ <artifactId>JDA</artifactId>
64
+ <version>${jda.version}</version>
65
+ <exclusions>
66
+ <exclusion>
67
+ <groupId>club.minnced</groupId>
68
+ <artifactId>opus-java</artifactId>
69
+ </exclusion>
70
+ </exclusions>
71
+ </dependency>
72
+ <dependency>
73
+ <groupId>com.unfbx</groupId>
74
+ <artifactId>chatgpt-java</artifactId>
75
+ <version>${chatgpt-java.version}</version>
76
+ <exclusions>
77
+ <exclusion>
78
+ <artifactId>slf4j-simple</artifactId>
79
+ <groupId>org.slf4j</groupId>
80
+ </exclusion>
81
+ </exclusions>
82
+ </dependency>
83
+ <dependency>
84
+ <groupId>eu.maxschuster</groupId>
85
+ <artifactId>dataurl</artifactId>
86
+ <version>${dataurl.version}</version>
87
+ </dependency>
88
+ <dependency>
89
+ <groupId>com.github.xiaoymin</groupId>
90
+ <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
91
+ <version>${knife4j.verison}</version>
92
+ </dependency>
93
+ <dependency>
94
+ <groupId>eu.bitwalker</groupId>
95
+ <artifactId>UserAgentUtils</artifactId>
96
+ <version>${user-agent-utils.verison}</version>
97
+ </dependency>
98
+
99
+ <dependency>
100
+ <groupId>org.springframework.boot</groupId>
101
+ <artifactId>spring-boot-configuration-processor</artifactId>
102
+ <optional>true</optional>
103
+ </dependency>
104
+ <dependency>
105
+ <groupId>org.projectlombok</groupId>
106
+ <artifactId>lombok</artifactId>
107
+ <optional>true</optional>
108
+ </dependency>
109
+ </dependencies>
110
+
111
+ <build>
112
+ <plugins>
113
+ <plugin>
114
+ <groupId>org.springframework.boot</groupId>
115
+ <artifactId>spring-boot-maven-plugin</artifactId>
116
+ </plugin>
117
+ </plugins>
118
+ </build>
119
+
120
+ </project>
src/main/java/com/github/novicezk/midjourney/Constants.java ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.github.novicezk.midjourney;
2
+
3
+ import lombok.experimental.UtilityClass;
4
+
5
+ @UtilityClass
6
+ public final class Constants {
7
+ // 任务扩展属性 start
8
+ public static final String TASK_PROPERTY_NOTIFY_HOOK = "notifyHook";
9
+ public static final String TASK_PROPERTY_FINAL_PROMPT = "finalPrompt";
10
+ public static final String TASK_PROPERTY_MESSAGE_ID = "messageId";
11
+ public static final String TASK_PROPERTY_MESSAGE_HASH = "messageHash";
12
+ public static final String TASK_PROPERTY_PROGRESS_MESSAGE_ID = "progressMessageId";
13
+ public static final String TASK_PROPERTY_FLAGS = "flags";
14
+ public static final String TASK_PROPERTY_NONCE = "nonce";
15
+ public static final String TASK_PROPERTY_DISCORD_INSTANCE_ID = "discordInstanceId";
16
+ // 任务扩展属性 end
17
+
18
+ public static final String API_SECRET_HEADER_NAME = "mj-api-secret";
19
+ public static final String DEFAULT_DISCORD_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36";
20
+ }
src/main/java/com/github/novicezk/midjourney/ProxyApplication.java ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.github.novicezk.midjourney;
2
+
3
+ import org.springframework.boot.SpringApplication;
4
+ import org.springframework.boot.autoconfigure.SpringBootApplication;
5
+ import org.springframework.context.annotation.Import;
6
+ import org.springframework.scheduling.annotation.EnableScheduling;
7
+ import spring.config.BeanConfig;
8
+ import spring.config.WebMvcConfig;
9
+
10
+ @EnableScheduling
11
+ @SpringBootApplication
12
+ @Import({BeanConfig.class, WebMvcConfig.class})
13
+ public class ProxyApplication {
14
+
15
+ public static void main(String[] args) {
16
+ SpringApplication.run(ProxyApplication.class, args);
17
+ }
18
+
19
+ }
src/main/java/com/github/novicezk/midjourney/ProxyProperties.java ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.github.novicezk.midjourney;
2
+
3
+ import com.github.novicezk.midjourney.enums.TranslateWay;
4
+ import lombok.Data;
5
+ import org.springframework.boot.context.properties.ConfigurationProperties;
6
+ import org.springframework.stereotype.Component;
7
+
8
+ import java.time.Duration;
9
+ import java.util.ArrayList;
10
+ import java.util.List;
11
+
12
+ @Data
13
+ @Component
14
+ @ConfigurationProperties(prefix = "mj")
15
+ public class ProxyProperties {
16
+ /**
17
+ * task存储配置.
18
+ */
19
+ private final TaskStore taskStore = new TaskStore();
20
+ /**
21
+ * discord账号选择规则.
22
+ */
23
+ private String accountChooseRule = "BestWaitIdleRule";
24
+ /**
25
+ * discord单账号配置.
26
+ */
27
+ private final DiscordAccountConfig discord = new DiscordAccountConfig();
28
+ /**
29
+ * discord账号池配置.
30
+ */
31
+ private final List<DiscordAccountConfig> accounts = new ArrayList<>();
32
+ /**
33
+ * 代理配置.
34
+ */
35
+ private final ProxyConfig proxy = new ProxyConfig();
36
+ /**
37
+ * 反代配置.
38
+ */
39
+ private final NgDiscordConfig ngDiscord = new NgDiscordConfig();
40
+ /**
41
+ * 百度翻译配置.
42
+ */
43
+ private final BaiduTranslateConfig baiduTranslate = new BaiduTranslateConfig();
44
+ /**
45
+ * openai配置.
46
+ */
47
+ private final OpenaiConfig openai = new OpenaiConfig();
48
+ /**
49
+ * 中文prompt翻译方式.
50
+ */
51
+ private TranslateWay translateWay = TranslateWay.NULL;
52
+ /**
53
+ * 接口密钥,为空不启用鉴权;调用接口时需要加请求头 mj-api-secret.
54
+ */
55
+ private String apiSecret;
56
+ /**
57
+ * 任务状态变更回调地址.
58
+ */
59
+ private String notifyHook;
60
+ /**
61
+ * 通知回调线程池大小.
62
+ */
63
+ private int notifyPoolSize = 10;
64
+
65
+ @Data
66
+ public static class DiscordAccountConfig {
67
+ /**
68
+ * 服务器ID.
69
+ */
70
+ private String guildId;
71
+ /**
72
+ * 频道ID.
73
+ */
74
+ private String channelId;
75
+ /**
76
+ * 用户Token.
77
+ */
78
+ private String userToken;
79
+ /**
80
+ * 用户UserAgent.
81
+ */
82
+ private String userAgent = Constants.DEFAULT_DISCORD_USER_AGENT;
83
+ /**
84
+ * 是否可用.
85
+ */
86
+ private boolean enable = true;
87
+ /**
88
+ * 并发数.
89
+ */
90
+ private int coreSize = 3;
91
+ /**
92
+ * 等待队列长度.
93
+ */
94
+ private int queueSize = 10;
95
+ /**
96
+ * 任务超时时间(分钟).
97
+ */
98
+ private int timeoutMinutes = 5;
99
+ }
100
+
101
+ @Data
102
+ public static class BaiduTranslateConfig {
103
+ /**
104
+ * 百度翻译的APP_ID.
105
+ */
106
+ private String appid;
107
+ /**
108
+ * 百度翻译的密钥.
109
+ */
110
+ private String appSecret;
111
+ }
112
+
113
+ @Data
114
+ public static class OpenaiConfig {
115
+ /**
116
+ * 自定义gpt的api-url.
117
+ */
118
+ private String gptApiUrl;
119
+ /**
120
+ * gpt的api-key.
121
+ */
122
+ private String gptApiKey;
123
+ /**
124
+ * 超时时间.
125
+ */
126
+ private Duration timeout = Duration.ofSeconds(30);
127
+ /**
128
+ * 使用的模型.
129
+ */
130
+ private String model = "gpt-3.5-turbo";
131
+ /**
132
+ * 返回结果的最大分词数.
133
+ */
134
+ private int maxTokens = 2048;
135
+ /**
136
+ * 相似度,取值 0-2.
137
+ */
138
+ private double temperature = 0;
139
+ }
140
+
141
+ @Data
142
+ public static class TaskStore {
143
+ /**
144
+ * 任务过期时间,默认30天.
145
+ */
146
+ private Duration timeout = Duration.ofDays(30);
147
+ /**
148
+ * 任务存储方式: redis(默认)、in_memory.
149
+ */
150
+ private Type type = Type.IN_MEMORY;
151
+
152
+ public enum Type {
153
+ /**
154
+ * redis.
155
+ */
156
+ REDIS,
157
+ /**
158
+ * in_memory.
159
+ */
160
+ IN_MEMORY
161
+ }
162
+ }
163
+
164
+ @Data
165
+ public static class ProxyConfig {
166
+ /**
167
+ * 代理host.
168
+ */
169
+ private String host;
170
+ /**
171
+ * 代理端口.
172
+ */
173
+ private Integer port;
174
+ }
175
+
176
+ @Data
177
+ public static class NgDiscordConfig {
178
+ /**
179
+ * https://discord.com 反代.
180
+ */
181
+ private String server;
182
+ /**
183
+ * https://cdn.discordapp.com 反代.
184
+ */
185
+ private String cdn;
186
+ /**
187
+ * wss://gateway.discord.gg 反代.
188
+ */
189
+ private String wss;
190
+ }
191
+
192
+ @Data
193
+ public static class TaskQueueConfig {
194
+ /**
195
+ * 并发数.
196
+ */
197
+ private int coreSize = 3;
198
+ /**
199
+ * 等待队列长度.
200
+ */
201
+ private int queueSize = 10;
202
+ /**
203
+ * 任务超时时间(分钟).
204
+ */
205
+ private int timeoutMinutes = 5;
206
+ }
207
+ }
src/main/java/com/github/novicezk/midjourney/ReturnCode.java ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.github.novicezk.midjourney;
2
+
3
+ import lombok.experimental.UtilityClass;
4
+
5
+ @UtilityClass
6
+ public final class ReturnCode {
7
+ /**
8
+ * 成功.
9
+ */
10
+ public static final int SUCCESS = 1;
11
+ /**
12
+ * 数据未找到.
13
+ */
14
+ public static final int NOT_FOUND = 3;
15
+ /**
16
+ * 校验错误.
17
+ */
18
+ public static final int VALIDATION_ERROR = 4;
19
+ /**
20
+ * 系统异常.
21
+ */
22
+ public static final int FAILURE = 9;
23
+
24
+ /**
25
+ * 已存在.
26
+ */
27
+ public static final int EXISTED = 21;
28
+ /**
29
+ * 排队中.
30
+ */
31
+ public static final int IN_QUEUE = 22;
32
+ /**
33
+ * 队列已满.
34
+ */
35
+ public static final int QUEUE_REJECTED = 23;
36
+ /**
37
+ * prompt包含敏感词.
38
+ */
39
+ public static final int BANNED_PROMPT = 24;
40
+
41
+
42
+ }
src/main/java/com/github/novicezk/midjourney/controller/AccountController.java ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.github.novicezk.midjourney.controller;
2
+
3
+ import com.github.novicezk.midjourney.domain.DiscordAccount;
4
+ import com.github.novicezk.midjourney.loadbalancer.DiscordInstance;
5
+ import com.github.novicezk.midjourney.loadbalancer.DiscordLoadBalancer;
6
+ import io.swagger.annotations.Api;
7
+ import io.swagger.annotations.ApiOperation;
8
+ import io.swagger.annotations.ApiParam;
9
+ import lombok.RequiredArgsConstructor;
10
+ import org.springframework.web.bind.annotation.GetMapping;
11
+ import org.springframework.web.bind.annotation.PathVariable;
12
+ import org.springframework.web.bind.annotation.RequestMapping;
13
+ import org.springframework.web.bind.annotation.RestController;
14
+
15
+ import java.util.List;
16
+
17
+ @Api(tags = "账号查询")
18
+ @RestController
19
+ @RequestMapping("/account")
20
+ @RequiredArgsConstructor
21
+ public class AccountController {
22
+ private final DiscordLoadBalancer loadBalancer;
23
+
24
+ @ApiOperation(value = "指定ID获取账号")
25
+ @GetMapping("/{id}/fetch")
26
+ public DiscordAccount fetch(@ApiParam(value = "账号ID") @PathVariable String id) {
27
+ DiscordInstance instance = this.loadBalancer.getDiscordInstance(id);
28
+ return instance == null ? null : instance.account();
29
+ }
30
+
31
+ @ApiOperation(value = "查询所有账号")
32
+ @GetMapping("/list")
33
+ public List<DiscordAccount> list() {
34
+ return this.loadBalancer.getAllInstances().stream().map(DiscordInstance::account).toList();
35
+ }
36
+ }
src/main/java/com/github/novicezk/midjourney/controller/SubmitController.java ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.github.novicezk.midjourney.controller;
2
+
3
+ import cn.hutool.core.text.CharSequenceUtil;
4
+ import cn.hutool.core.util.RandomUtil;
5
+ import com.github.novicezk.midjourney.Constants;
6
+ import com.github.novicezk.midjourney.ProxyProperties;
7
+ import com.github.novicezk.midjourney.ReturnCode;
8
+ import com.github.novicezk.midjourney.dto.BaseSubmitDTO;
9
+ import com.github.novicezk.midjourney.dto.SubmitBlendDTO;
10
+ import com.github.novicezk.midjourney.dto.SubmitChangeDTO;
11
+ import com.github.novicezk.midjourney.dto.SubmitDescribeDTO;
12
+ import com.github.novicezk.midjourney.dto.SubmitImagineDTO;
13
+ import com.github.novicezk.midjourney.dto.SubmitSimpleChangeDTO;
14
+ import com.github.novicezk.midjourney.enums.TaskAction;
15
+ import com.github.novicezk.midjourney.enums.TaskStatus;
16
+ import com.github.novicezk.midjourney.exception.BannedPromptException;
17
+ import com.github.novicezk.midjourney.result.SubmitResultVO;
18
+ import com.github.novicezk.midjourney.service.TaskService;
19
+ import com.github.novicezk.midjourney.service.TaskStoreService;
20
+ import com.github.novicezk.midjourney.service.TranslateService;
21
+ import com.github.novicezk.midjourney.support.Task;
22
+ import com.github.novicezk.midjourney.support.TaskCondition;
23
+ import com.github.novicezk.midjourney.util.BannedPromptUtils;
24
+ import com.github.novicezk.midjourney.util.ConvertUtils;
25
+ import com.github.novicezk.midjourney.util.MimeTypeUtils;
26
+ import com.github.novicezk.midjourney.util.SnowFlake;
27
+ import com.github.novicezk.midjourney.util.TaskChangeParams;
28
+ import eu.maxschuster.dataurl.DataUrl;
29
+ import eu.maxschuster.dataurl.DataUrlSerializer;
30
+ import eu.maxschuster.dataurl.IDataUrlSerializer;
31
+ import io.swagger.annotations.Api;
32
+ import io.swagger.annotations.ApiOperation;
33
+ import lombok.RequiredArgsConstructor;
34
+ import org.springframework.web.bind.annotation.PostMapping;
35
+ import org.springframework.web.bind.annotation.RequestBody;
36
+ import org.springframework.web.bind.annotation.RequestMapping;
37
+ import org.springframework.web.bind.annotation.RestController;
38
+
39
+ import java.net.MalformedURLException;
40
+ import java.util.ArrayList;
41
+ import java.util.List;
42
+ import java.util.Optional;
43
+ import java.util.Set;
44
+
45
+ @Api(tags = "任务提交")
46
+ @RestController
47
+ @RequestMapping("/submit")
48
+ @RequiredArgsConstructor
49
+ public class SubmitController {
50
+ private final TranslateService translateService;
51
+ private final TaskStoreService taskStoreService;
52
+ private final ProxyProperties properties;
53
+ private final TaskService taskService;
54
+
55
+ @ApiOperation(value = "提交Imagine任务")
56
+ @PostMapping("/imagine")
57
+ public SubmitResultVO imagine(@RequestBody SubmitImagineDTO imagineDTO) {
58
+ String prompt = imagineDTO.getPrompt();
59
+ if (CharSequenceUtil.isBlank(prompt)) {
60
+ return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "prompt不能为空");
61
+ }
62
+ prompt = prompt.trim();
63
+ Task task = newTask(imagineDTO);
64
+ task.setAction(TaskAction.IMAGINE);
65
+ task.setPrompt(prompt);
66
+ String promptEn = translatePrompt(prompt);
67
+ try {
68
+ BannedPromptUtils.checkBanned(promptEn);
69
+ } catch (BannedPromptException e) {
70
+ return SubmitResultVO.fail(ReturnCode.BANNED_PROMPT, "可能包含敏感词")
71
+ .setProperty("promptEn", promptEn).setProperty("bannedWord", e.getMessage());
72
+ }
73
+ List<String> base64Array = Optional.ofNullable(imagineDTO.getBase64Array()).orElse(new ArrayList<>());
74
+ if (CharSequenceUtil.isNotBlank(imagineDTO.getBase64())) {
75
+ base64Array.add(imagineDTO.getBase64());
76
+ }
77
+ List<DataUrl> dataUrls;
78
+ try {
79
+ dataUrls = ConvertUtils.convertBase64Array(base64Array);
80
+ } catch (MalformedURLException e) {
81
+ return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "base64格式错误");
82
+ }
83
+ task.setPromptEn(promptEn);
84
+ task.setDescription("/imagine " + prompt);
85
+ return this.taskService.submitImagine(task, dataUrls);
86
+ }
87
+
88
+ @ApiOperation(value = "绘图变化-simple")
89
+ @PostMapping("/simple-change")
90
+ public SubmitResultVO simpleChange(@RequestBody SubmitSimpleChangeDTO simpleChangeDTO) {
91
+ TaskChangeParams changeParams = ConvertUtils.convertChangeParams(simpleChangeDTO.getContent());
92
+ if (changeParams == null) {
93
+ return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "content参数错误");
94
+ }
95
+ SubmitChangeDTO changeDTO = new SubmitChangeDTO();
96
+ changeDTO.setAction(changeParams.getAction());
97
+ changeDTO.setTaskId(changeParams.getId());
98
+ changeDTO.setIndex(changeParams.getIndex());
99
+ changeDTO.setState(simpleChangeDTO.getState());
100
+ changeDTO.setNotifyHook(simpleChangeDTO.getNotifyHook());
101
+ return change(changeDTO);
102
+ }
103
+
104
+ @ApiOperation(value = "绘图变化")
105
+ @PostMapping("/change")
106
+ public SubmitResultVO change(@RequestBody SubmitChangeDTO changeDTO) {
107
+ if (CharSequenceUtil.isBlank(changeDTO.getTaskId())) {
108
+ return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "taskId不能为空");
109
+ }
110
+ if (!Set.of(TaskAction.UPSCALE, TaskAction.VARIATION, TaskAction.REROLL).contains(changeDTO.getAction())) {
111
+ return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "action参数错误");
112
+ }
113
+ String description = "/up " + changeDTO.getTaskId();
114
+ if (TaskAction.REROLL.equals(changeDTO.getAction())) {
115
+ description += " R";
116
+ } else {
117
+ description += " " + changeDTO.getAction().name().charAt(0) + changeDTO.getIndex();
118
+ }
119
+ if (TaskAction.UPSCALE.equals(changeDTO.getAction())) {
120
+ TaskCondition condition = new TaskCondition().setDescription(description);
121
+ Task existTask = this.taskStoreService.findOne(condition);
122
+ if (existTask != null) {
123
+ return SubmitResultVO.of(ReturnCode.EXISTED, "任务已存在", existTask.getId())
124
+ .setProperty("status", existTask.getStatus())
125
+ .setProperty("imageUrl", existTask.getImageUrl());
126
+ }
127
+ }
128
+ Task targetTask = this.taskStoreService.get(changeDTO.getTaskId());
129
+ if (targetTask == null) {
130
+ return SubmitResultVO.fail(ReturnCode.NOT_FOUND, "关联任务不存在或已失效");
131
+ }
132
+ if (!TaskStatus.SUCCESS.equals(targetTask.getStatus())) {
133
+ return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "关联任务状态错误");
134
+ }
135
+ if (!Set.of(TaskAction.IMAGINE, TaskAction.VARIATION, TaskAction.REROLL, TaskAction.BLEND).contains(targetTask.getAction())) {
136
+ return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "关联任务不允许执行变化");
137
+ }
138
+ Task task = newTask(changeDTO);
139
+ task.setAction(changeDTO.getAction());
140
+ task.setPrompt(targetTask.getPrompt());
141
+ task.setPromptEn(targetTask.getPromptEn());
142
+ task.setProperty(Constants.TASK_PROPERTY_FINAL_PROMPT, targetTask.getProperty(Constants.TASK_PROPERTY_FINAL_PROMPT));
143
+ task.setProperty(Constants.TASK_PROPERTY_PROGRESS_MESSAGE_ID, targetTask.getProperty(Constants.TASK_PROPERTY_MESSAGE_ID));
144
+ task.setProperty(Constants.TASK_PROPERTY_DISCORD_INSTANCE_ID, targetTask.getProperty(Constants.TASK_PROPERTY_DISCORD_INSTANCE_ID));
145
+ task.setDescription(description);
146
+ int messageFlags = targetTask.getPropertyGeneric(Constants.TASK_PROPERTY_FLAGS);
147
+ String messageId = targetTask.getPropertyGeneric(Constants.TASK_PROPERTY_MESSAGE_ID);
148
+ String messageHash = targetTask.getPropertyGeneric(Constants.TASK_PROPERTY_MESSAGE_HASH);
149
+ if (TaskAction.UPSCALE.equals(changeDTO.getAction())) {
150
+ return this.taskService.submitUpscale(task, messageId, messageHash, changeDTO.getIndex(), messageFlags);
151
+ } else if (TaskAction.VARIATION.equals(changeDTO.getAction())) {
152
+ return this.taskService.submitVariation(task, messageId, messageHash, changeDTO.getIndex(), messageFlags);
153
+ } else {
154
+ return this.taskService.submitReroll(task, messageId, messageHash, messageFlags);
155
+ }
156
+ }
157
+
158
+ @ApiOperation(value = "提交Describe任务")
159
+ @PostMapping("/describe")
160
+ public SubmitResultVO describe(@RequestBody SubmitDescribeDTO describeDTO) {
161
+ if (CharSequenceUtil.isBlank(describeDTO.getBase64())) {
162
+ return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "base64不能为空");
163
+ }
164
+ IDataUrlSerializer serializer = new DataUrlSerializer();
165
+ DataUrl dataUrl;
166
+ try {
167
+ dataUrl = serializer.unserialize(describeDTO.getBase64());
168
+ } catch (MalformedURLException e) {
169
+ return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "base64格式错误");
170
+ }
171
+ Task task = newTask(describeDTO);
172
+ task.setAction(TaskAction.DESCRIBE);
173
+ String taskFileName = task.getId() + "." + MimeTypeUtils.guessFileSuffix(dataUrl.getMimeType());
174
+ task.setDescription("/describe " + taskFileName);
175
+ return this.taskService.submitDescribe(task, dataUrl);
176
+ }
177
+
178
+ @ApiOperation(value = "提交Blend任务")
179
+ @PostMapping("/blend")
180
+ public SubmitResultVO blend(@RequestBody SubmitBlendDTO blendDTO) {
181
+ List<String> base64Array = blendDTO.getBase64Array();
182
+ if (base64Array == null || base64Array.size() < 2 || base64Array.size() > 5) {
183
+ return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "base64List参数错误");
184
+ }
185
+ if (blendDTO.getDimensions() == null) {
186
+ return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "dimensions参数错误");
187
+ }
188
+ IDataUrlSerializer serializer = new DataUrlSerializer();
189
+ List<DataUrl> dataUrlList = new ArrayList<>();
190
+ try {
191
+ for (String base64 : base64Array) {
192
+ DataUrl dataUrl = serializer.unserialize(base64);
193
+ dataUrlList.add(dataUrl);
194
+ }
195
+ } catch (MalformedURLException e) {
196
+ return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "base64格式错误");
197
+ }
198
+ Task task = newTask(blendDTO);
199
+ task.setAction(TaskAction.BLEND);
200
+ task.setDescription("/blend " + task.getId() + " " + dataUrlList.size());
201
+ return this.taskService.submitBlend(task, dataUrlList, blendDTO.getDimensions());
202
+ }
203
+
204
+ private Task newTask(BaseSubmitDTO base) {
205
+ Task task = new Task();
206
+ task.setId(System.currentTimeMillis() + "" + RandomUtil.randomNumbers(3));
207
+ task.setSubmitTime(System.currentTimeMillis());
208
+ task.setState(base.getState());
209
+ String notifyHook = CharSequenceUtil.isBlank(base.getNotifyHook()) ? this.properties.getNotifyHook() : base.getNotifyHook();
210
+ task.setProperty(Constants.TASK_PROPERTY_NOTIFY_HOOK, notifyHook);
211
+ task.setProperty(Constants.TASK_PROPERTY_NONCE, SnowFlake.INSTANCE.nextId());
212
+ return task;
213
+ }
214
+
215
+ private String translatePrompt(String prompt) {
216
+ String promptEn;
217
+ int paramStart = prompt.indexOf(" --");
218
+ if (paramStart > 0) {
219
+ promptEn = this.translateService.translateToEnglish(prompt.substring(0, paramStart)).trim() + prompt.substring(paramStart);
220
+ } else {
221
+ promptEn = this.translateService.translateToEnglish(prompt).trim();
222
+ }
223
+ if (CharSequenceUtil.isBlank(promptEn)) {
224
+ promptEn = prompt;
225
+ }
226
+ return promptEn;
227
+ }
228
+ }
src/main/java/com/github/novicezk/midjourney/controller/TaskController.java ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.github.novicezk.midjourney.controller;
2
+
3
+ import cn.hutool.core.comparator.CompareUtil;
4
+ import com.github.novicezk.midjourney.dto.TaskConditionDTO;
5
+ import com.github.novicezk.midjourney.loadbalancer.DiscordLoadBalancer;
6
+ import com.github.novicezk.midjourney.service.TaskStoreService;
7
+ import com.github.novicezk.midjourney.support.Task;
8
+ import io.swagger.annotations.Api;
9
+ import io.swagger.annotations.ApiOperation;
10
+ import io.swagger.annotations.ApiParam;
11
+ import lombok.RequiredArgsConstructor;
12
+ import org.springframework.web.bind.annotation.GetMapping;
13
+ import org.springframework.web.bind.annotation.PathVariable;
14
+ import org.springframework.web.bind.annotation.PostMapping;
15
+ import org.springframework.web.bind.annotation.RequestBody;
16
+ import org.springframework.web.bind.annotation.RequestMapping;
17
+ import org.springframework.web.bind.annotation.RestController;
18
+
19
+ import java.util.Collections;
20
+ import java.util.Comparator;
21
+ import java.util.List;
22
+ import java.util.Objects;
23
+
24
+ @Api(tags = "任务查询")
25
+ @RestController
26
+ @RequestMapping("/task")
27
+ @RequiredArgsConstructor
28
+ public class TaskController {
29
+ private final TaskStoreService taskStoreService;
30
+ private final DiscordLoadBalancer discordLoadBalancer;
31
+
32
+ @ApiOperation(value = "指定ID获取任务")
33
+ @GetMapping("/{id}/fetch")
34
+ public Task fetch(@ApiParam(value = "任务ID") @PathVariable String id) {
35
+ return this.taskStoreService.get(id);
36
+ }
37
+
38
+ @ApiOperation(value = "查询任务队列")
39
+ @GetMapping("/queue")
40
+ public List<Task> queue() {
41
+ return this.discordLoadBalancer.getQueueTaskIds().stream()
42
+ .map(this.taskStoreService::get).filter(Objects::nonNull)
43
+ .sorted(Comparator.comparing(Task::getSubmitTime))
44
+ .toList();
45
+ }
46
+
47
+ @ApiOperation(value = "查询所有任务")
48
+ @GetMapping("/list")
49
+ public List<Task> list() {
50
+ return this.taskStoreService.list().stream()
51
+ .sorted((t1, t2) -> CompareUtil.compare(t2.getSubmitTime(), t1.getSubmitTime()))
52
+ .toList();
53
+ }
54
+
55
+ @ApiOperation(value = "根据ID列表查询任务")
56
+ @PostMapping("/list-by-condition")
57
+ public List<Task> listByIds(@RequestBody TaskConditionDTO conditionDTO) {
58
+ if (conditionDTO.getIds() == null) {
59
+ return Collections.emptyList();
60
+ }
61
+ return conditionDTO.getIds().stream().map(this.taskStoreService::get).filter(Objects::nonNull).toList();
62
+ }
63
+
64
+ }
src/main/java/com/github/novicezk/midjourney/domain/DiscordAccount.java ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.github.novicezk.midjourney.domain;
2
+
3
+ import com.fasterxml.jackson.annotation.JsonIgnore;
4
+ import com.github.novicezk.midjourney.Constants;
5
+ import io.swagger.annotations.ApiModel;
6
+ import io.swagger.annotations.ApiModelProperty;
7
+ import lombok.Data;
8
+ import lombok.EqualsAndHashCode;
9
+
10
+ @Data
11
+ @EqualsAndHashCode(callSuper = true)
12
+ @ApiModel("Discord账号")
13
+ public class DiscordAccount extends DomainObject {
14
+
15
+ @ApiModelProperty("服务器ID")
16
+ private String guildId;
17
+ @ApiModelProperty("频道ID")
18
+ private String channelId;
19
+ @ApiModelProperty("用户Token")
20
+ private String userToken;
21
+ @ApiModelProperty("用户UserAgent")
22
+ private String userAgent = Constants.DEFAULT_DISCORD_USER_AGENT;
23
+
24
+ @ApiModelProperty("是否可用")
25
+ private boolean enable = true;
26
+
27
+ @ApiModelProperty("并发数")
28
+ private int coreSize = 3;
29
+ @ApiModelProperty("等待队列长度")
30
+ private int queueSize = 10;
31
+ @ApiModelProperty("任务超时时间(分钟)")
32
+ private int timeoutMinutes = 5;
33
+
34
+ @JsonIgnore
35
+ public String getDisplay() {
36
+ return this.channelId;
37
+ }
38
+ }
src/main/java/com/github/novicezk/midjourney/domain/DomainObject.java ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.github.novicezk.midjourney.domain;
2
+
3
+
4
+ import com.fasterxml.jackson.annotation.JsonIgnore;
5
+ import io.swagger.annotations.ApiModelProperty;
6
+ import lombok.Getter;
7
+ import lombok.Setter;
8
+
9
+ import java.io.Serializable;
10
+ import java.util.HashMap;
11
+ import java.util.Map;
12
+
13
+
14
+ public class DomainObject implements Serializable {
15
+ @Getter
16
+ @Setter
17
+ @ApiModelProperty("ID")
18
+ protected String id;
19
+
20
+ @Setter
21
+ protected Map<String, Object> properties; // 扩展属性,仅支持基本类型
22
+
23
+ @JsonIgnore
24
+ private final transient Object lock = new Object();
25
+
26
+ public void sleep() throws InterruptedException {
27
+ synchronized (this.lock) {
28
+ this.lock.wait();
29
+ }
30
+ }
31
+
32
+ public void awake() {
33
+ synchronized (this.lock) {
34
+ this.lock.notifyAll();
35
+ }
36
+ }
37
+
38
+ public DomainObject setProperty(String name, Object value) {
39
+ getProperties().put(name, value);
40
+ return this;
41
+ }
42
+
43
+ public DomainObject removeProperty(String name) {
44
+ getProperties().remove(name);
45
+ return this;
46
+ }
47
+
48
+ public Object getProperty(String name) {
49
+ return getProperties().get(name);
50
+ }
51
+
52
+ @SuppressWarnings("unchecked")
53
+ public <T> T getPropertyGeneric(String name) {
54
+ return (T) getProperty(name);
55
+ }
56
+
57
+ public <T> T getProperty(String name, Class<T> clz) {
58
+ return getProperty(name, clz, null);
59
+ }
60
+
61
+ public <T> T getProperty(String name, Class<T> clz, T defaultValue) {
62
+ Object value = getProperty(name);
63
+ return value == null ? defaultValue : clz.cast(value);
64
+ }
65
+
66
+ public Map<String, Object> getProperties() {
67
+ if (this.properties == null) {
68
+ this.properties = new HashMap<>();
69
+ }
70
+ return this.properties;
71
+ }
72
+ }
src/main/java/com/github/novicezk/midjourney/dto/BaseSubmitDTO.java ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.github.novicezk.midjourney.dto;
2
+
3
+ import io.swagger.annotations.ApiModelProperty;
4
+ import lombok.Getter;
5
+ import lombok.Setter;
6
+
7
+ @Getter
8
+ @Setter
9
+ public abstract class BaseSubmitDTO {
10
+
11
+ @ApiModelProperty("自定义参数")
12
+ protected String state;
13
+
14
+ @ApiModelProperty("回调地址, 为空时使用全局notifyHook")
15
+ protected String notifyHook;
16
+ }
src/main/java/com/github/novicezk/midjourney/dto/SubmitBlendDTO.java ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.github.novicezk.midjourney.dto;
2
+
3
+ import com.github.novicezk.midjourney.enums.BlendDimensions;
4
+ import io.swagger.annotations.ApiModel;
5
+ import io.swagger.annotations.ApiModelProperty;
6
+ import lombok.Data;
7
+ import lombok.EqualsAndHashCode;
8
+
9
+ import java.util.List;
10
+
11
+ @Data
12
+ @ApiModel("Blend提交参数")
13
+ @EqualsAndHashCode(callSuper = true)
14
+ public class SubmitBlendDTO extends BaseSubmitDTO {
15
+
16
+ @ApiModelProperty(value = "图片base64数组", required = true, example = "[\"data:image/png;base64,xxx1\", \"data:image/png;base64,xxx2\"]")
17
+ private List<String> base64Array;
18
+
19
+ @ApiModelProperty(value = "比例: PORTRAIT(2:3); SQUARE(1:1); LANDSCAPE(3:2)", example = "SQUARE")
20
+ private BlendDimensions dimensions = BlendDimensions.SQUARE;
21
+ }
src/main/java/com/github/novicezk/midjourney/dto/SubmitChangeDTO.java ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.github.novicezk.midjourney.dto;
2
+
3
+ import com.github.novicezk.midjourney.enums.TaskAction;
4
+ import io.swagger.annotations.ApiModel;
5
+ import io.swagger.annotations.ApiModelProperty;
6
+ import lombok.Data;
7
+ import lombok.EqualsAndHashCode;
8
+
9
+
10
+ @Data
11
+ @ApiModel("变化任务提交参数")
12
+ @EqualsAndHashCode(callSuper = true)
13
+ public class SubmitChangeDTO extends BaseSubmitDTO {
14
+
15
+ @ApiModelProperty(value = "任务ID", required = true, example = "\"1320098173412546\"")
16
+ private String taskId;
17
+
18
+ @ApiModelProperty(value = "UPSCALE(放大); VARIATION(变换); REROLL(重新生成)", required = true,
19
+ allowableValues = "UPSCALE, VARIATION, REROLL", example = "UPSCALE")
20
+ private TaskAction action;
21
+
22
+ @ApiModelProperty(value = "序号(1~4), action为UPSCALE,VARIATION时必传", allowableValues = "range[1, 4]", example = "1")
23
+ private Integer index;
24
+
25
+ }
src/main/java/com/github/novicezk/midjourney/dto/SubmitDescribeDTO.java ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.github.novicezk.midjourney.dto;
2
+
3
+ import io.swagger.annotations.ApiModel;
4
+ import io.swagger.annotations.ApiModelProperty;
5
+ import lombok.Data;
6
+ import lombok.EqualsAndHashCode;
7
+
8
+ @Data
9
+ @ApiModel("Describe提交参数")
10
+ @EqualsAndHashCode(callSuper = true)
11
+ public class SubmitDescribeDTO extends BaseSubmitDTO {
12
+
13
+ @ApiModelProperty(value = "图片base64", required = true, example = "data:image/png;base64,xxx")
14
+ private String base64;
15
+ }
src/main/java/com/github/novicezk/midjourney/dto/SubmitImagineDTO.java ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.github.novicezk.midjourney.dto;
2
+
3
+ import io.swagger.annotations.ApiModel;
4
+ import io.swagger.annotations.ApiModelProperty;
5
+ import lombok.Data;
6
+ import lombok.EqualsAndHashCode;
7
+
8
+ import java.util.List;
9
+
10
+
11
+ @Data
12
+ @ApiModel("Imagine提交参数")
13
+ @EqualsAndHashCode(callSuper = true)
14
+ public class SubmitImagineDTO extends BaseSubmitDTO {
15
+
16
+ @ApiModelProperty(value = "提示词", required = true, example = "Cat")
17
+ private String prompt;
18
+
19
+ @ApiModelProperty(value = "垫图base64数组")
20
+ private List<String> base64Array;
21
+
22
+ @ApiModelProperty(hidden = true)
23
+ @Deprecated(since = "3.0", forRemoval = true)
24
+ private String base64;
25
+
26
+ }
src/main/java/com/github/novicezk/midjourney/dto/SubmitSimpleChangeDTO.java ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.github.novicezk.midjourney.dto;
2
+
3
+ import io.swagger.annotations.ApiModel;
4
+ import io.swagger.annotations.ApiModelProperty;
5
+ import lombok.Data;
6
+ import lombok.EqualsAndHashCode;
7
+
8
+
9
+ @Data
10
+ @ApiModel("变化任务提交参数-simple")
11
+ @EqualsAndHashCode(callSuper = true)
12
+ public class SubmitSimpleChangeDTO extends BaseSubmitDTO {
13
+
14
+ @ApiModelProperty(value = "变化描述: ID $action$index", required = true, example = "1320098173412546 U2")
15
+ private String content;
16
+
17
+ }
src/main/java/com/github/novicezk/midjourney/dto/TaskConditionDTO.java ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.github.novicezk.midjourney.dto;
2
+
3
+ import io.swagger.annotations.ApiModel;
4
+ import lombok.Data;
5
+
6
+ import java.util.List;
7
+
8
+ @Data
9
+ @ApiModel("任务查询参数")
10
+ public class TaskConditionDTO {
11
+
12
+ private List<String> ids;
13
+
14
+ }
src/main/java/com/github/novicezk/midjourney/enums/BlendDimensions.java ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.github.novicezk.midjourney.enums;
2
+
3
+
4
+ public enum BlendDimensions {
5
+
6
+ PORTRAIT("2:3"),
7
+
8
+ SQUARE("1:1"),
9
+
10
+ LANDSCAPE("3:2");
11
+
12
+ private final String value;
13
+
14
+ BlendDimensions(String value) {
15
+ this.value = value;
16
+ }
17
+
18
+ public String getValue() {
19
+ return this.value;
20
+ }
21
+ }
src/main/java/com/github/novicezk/midjourney/enums/MessageType.java ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.github.novicezk.midjourney.enums;
2
+
3
+
4
+ public enum MessageType {
5
+ /**
6
+ * 创建.
7
+ */
8
+ CREATE,
9
+ /**
10
+ * 修改.
11
+ */
12
+ UPDATE,
13
+ /**
14
+ * 删除.
15
+ */
16
+ DELETE;
17
+
18
+ public static MessageType of(String type) {
19
+ return switch (type) {
20
+ case "MESSAGE_CREATE" -> CREATE;
21
+ case "MESSAGE_UPDATE" -> UPDATE;
22
+ case "MESSAGE_DELETE" -> DELETE;
23
+ default -> null;
24
+ };
25
+ }
26
+ }
src/main/java/com/github/novicezk/midjourney/enums/TaskAction.java ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.github.novicezk.midjourney.enums;
2
+
3
+
4
+ public enum TaskAction {
5
+ /**
6
+ * 生成图片.
7
+ */
8
+ IMAGINE,
9
+ /**
10
+ * 选中放大.
11
+ */
12
+ UPSCALE,
13
+ /**
14
+ * 选中其中的一张图,生成四张相似的.
15
+ */
16
+ VARIATION,
17
+ /**
18
+ * 重新执行.
19
+ */
20
+ REROLL,
21
+ /**
22
+ * 图转prompt.
23
+ */
24
+ DESCRIBE,
25
+ /**
26
+ * 多图混合.
27
+ */
28
+ BLEND
29
+
30
+ }