Midjourney模拟API生图调用

发布时间 2023-12-20 09:29:26作者: 梅丹隆

image.png
目前Midjourney没有对外开放API接口,所以通过MJ自动化生图的主要方式是,集成Discord应用机器人,通过机器人与MJ机器人进行交互,并监听频道内的生图结果,最终拿到图片地址。
简单介绍下步骤

一、购买MJ账号

命令行中输入/subscribe指令并回车
点击跳转按钮
MJ资费

二、获取账号Authorization

在网页中向Midjourney Bot发送/imagine进行生图
我们在检查元素中能够获取调用的地址和Authorization
image.png

三、模拟Http请求调用

public String generateImage(String promptText) {
    Map<String, String> headerMap = Maps.newHashMap();
    // 添加付费账号对应的 authorization
    headerMap.put("authorization", getAuthorization());
    headerMap.put("Content-Type", "application/json");
    // 构建请求(详见下方payload_json)
    InteractionsRequest req = buildInteractionsRequest(promptText);
    String reqJson = JSON.toJSONString(req);
    log.info("===reqJson is {}", reqJson);
    try {
        // POST请求需要代理
        String res = HttpUtils.httpPostProxy(MidjourneyContants.API_URL, reqJson, headerMap);
        return res;
    } catch (Exception e) {
        log.error("VolcanoTtsService generateVoice error,request={}", reqJson, e);
    }
    return StringUtils.EMPTY;
}
{
  "type": 2, // 注意:type为2不能缺少
  "application_id": "",
  "guild_id": "",
  "channel_id": "",
  "session_id": "",
  "data": {
    "version": "",
    "id": "",
    "name": "imagine",
    "type": 1,
    "options": [
      {
        "type": 3,
        "name": "prompt",
        "value": "a beautiful lady"
      }
    ],
    "application_command": {
      "id": "",
      "application_id": "",
      "version": "",
      "default_member_permissions": null,
      "type": 1,
      "nsfw": false,
      "name": "imagine",
      "description": "Create images with Midjourney",
      "dm_permission": true,
      "contexts": null,
      "options": [
        {
          "type": 3,
          "name": "prompt",
          "description": "The prompt to imagine",
          "required": true
        }
      ]
    },
    "attachments": [ ]
  },
  "nonce": ""
}

四、Discord机器人监听消息

之前写过监听消息的方法,这里就不再赘述了
Discord 机器人Java Api使用

1、生图完成结果

生图结果是四张图的拼图
image.png
我们收到的响应通过debug可以看到
image.png
需要关注:

  1. 我们的prompt在响应中是在event.message.content中可以获得,前后有**标识
private static final String PROMPT_MARKER = "**";

private String getPromptBetweenMarkers(String messageContent, String marker) {
    return str.substring(str.indexOf(marker) + marker.length(), str.lastIndexOf(marker));
}
  1. 如果我们想获取U1的单张图片,那么需要获取上图的messageIdU1对应的customId两个参数

五、调用获取单张图片

获取单图的所调用的Http地址与生图相同,不通的是Body中的payload_json。所需要的参数如下:

{
  "type": 3, // 注意:type为3不能错
  "nonce": "",
  "guild_id": "",
  "channel_id": "",
  "message_flags": 0,
  "message_id": "生图响应中的messageId",
  "application_id": "",
  "session_id": "",
  "data": {
    "component_type": 2,
    "custom_id": "生图响应中的customId"
  }
}

响应:
image.png

六、外网图片转换

获取的MJ单图图片是外网图片,转换成内网的方式有很多,比如说先下载本地File再上传到自己的图床等

七、Discord监听器完整代码

@Slf4j
@Component
public class ImageGenerateComplete implements MessageCreateListener {

    @Autowired
    private IMidjourneyService midjourneyService;

    @Override
    public void onMessageCreate(MessageCreateEvent event) {
        // 反转为Spring的Bean管理
        ImageGenerateComplete self = SpringUtil.getBean(this);
        BizExecutor.getInstance().getThreadPool().execute(() -> self.eventSolution(event));
    }

    public void eventSolution(MessageCreateEvent event) {
        try{
            MessageAuthor messageAuthor = event.getMessageAuthor();
            // 消息发送这是否为机器人
            if(messageAuthor.isBotUser()){
                // 发送人id,可用来校验是否为需要监听的应用
                long messageAuthorId = messageAuthor.getId();
                Message message = event.getMessage();
                long messageId = message.getId();
                // 生图返回的消息类型为NORMAL。点击U1生成单图的消息类型为REPLY
                if(MessageType.NORMAL.equals(message.getType())){
                    // 示例默认取U1,非空校验略
                    String customId = message.getComponents().get(0).asActionRow().orElse(null)
                        .getComponents().stream().filter(c -> c.asButton().orElse(null).getLabel().orElse("").equals("U1")).findFirst().orElse(null)
                        .asButton().orElse(null).getCustomId().orElse(null);
                    // 获取单张图片服务
                    midjourneyService.getSingleImage(messageId, customId);
                }
            }
        }catch (Exception e) {
            log.info("!==[ImageGenerateComplete#eventSolution] ex is " + e.getMessage());
            // do sth.
        }
    }
}
@Slf4j
@Component
public class SingleImageComplete implements MessageCreateListener {

    private static final String PROMPT_MARKER = "**";

    @Resource
    private IMidjourneryBiz midjourneryBiz;

    @Override
    public void onMessageCreate(MessageCreateEvent event) {
        // 反转为Spring的Bean管理
        SingleImageComplete self = SpringUtil.getBean(this);
        BizExecutor.getInstance().getThreadPool().execute(() -> self.eventSolution(event));
    }

    public void eventSolution(MessageCreateEvent event) {
        try{
            MessageAuthor messageAuthor = event.getMessageAuthor();
            if(messageAuthor.isBotUser()){
                Message message = event.getMessage();
                long messageId = message.getId();
                if(MessageType.REPLY.equals(message.getType())){
                    String messageContent = message.getContent();
                    // 获取prompt
                    String prompt = getStringBetweenMarkers(messageContent, PROMPT_MARKER);
                    // 获取图片地址
                    String discordImageUrl = event.getMessage().getAttachments().get(0).getUrl().toString();
                    // 转内网图片地址
                }
            }
        }catch (Exception e) {
            log.info("!==[SingleImageComplete#eventSolution] ex is " + e.getMessage());
        }
    }

    private String getStringBetweenMarkers(String str, String marker) {
        return str.substring(str.indexOf(marker) + marker.length(), str.lastIndexOf(marker));
    }

}

可参考我的GitHub:
all-in-one/springboot/aigc at master · Meidanlong/all-in-one