├── .env.example ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.md └── workflows │ └── v4.3.x.yaml ├── .gitignore ├── .npmrc ├── .prettierignore ├── LICENSE ├── README.md ├── README_zh.md ├── custompan.ts ├── doc ├── README.md ├── SUMMARY.md ├── faq │ ├── node.js-less-than-18.md │ ├── proxy.md │ └── vercel.md ├── getting-started │ ├── api-reference │ │ ├── README.md │ │ ├── blend.md │ │ ├── describe.md │ │ ├── fast.md │ │ ├── imagine.md │ │ ├── info.md │ │ ├── relax.md │ │ ├── reroll.md │ │ ├── reset.md │ │ ├── setting.md │ │ ├── upscale.md │ │ └── variation.md │ ├── config.md │ ├── example │ │ ├── README.md │ │ ├── image.md │ │ └── imagine.md │ ├── install.md │ └── quickstart.md └── use-cases │ ├── discord-bot.md │ └── web-ui.md ├── example ├── banend-words.ts ├── customzoom.ts ├── describe.ts ├── faceswap.ts ├── fast.ts ├── imagine-blend.ts ├── imagine-dm.ts ├── imagine-err.ts ├── imagine-niji.ts ├── imagine-ws-m.ts ├── imagine-ws.ts ├── imagine.js ├── imagine.ts ├── info.ts ├── prefer-remix.ts ├── relax.ts ├── reset.ts ├── settings.ts ├── shorten.ts ├── upscale-ws.ts ├── upscale.ts ├── variation-ws.ts ├── variation.ts ├── vary.ts └── zoomout.ts ├── images ├── ali.png └── wechat.png ├── package-lock.json ├── package.json ├── src ├── banned.words.ts ├── command.ts ├── discord.message.ts ├── discord.ws.ts ├── face.swap.ts ├── gradio │ ├── client.ts │ ├── globals.d.ts │ ├── index.ts │ ├── types.ts │ └── utils.ts ├── index.ts ├── interfaces │ ├── config.ts │ ├── index.ts │ ├── message.ts │ ├── modal.ts │ └── upload.ts ├── midjourney.api.ts ├── midjourney.ts ├── utils │ └── index.ts └── verify.human.ts ├── test ├── face.ts ├── test.ts └── test2.ts ├── tsconfig.json └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | SALAI_TOKEN="Token of the Account from which you paid MidJourney" 2 | SERVER_ID="Server id here" 3 | CHANNEL_ID="Channel in which commands are sent" 4 | SESSION_ID="session id here" 5 | HUGGINGFACE_TOKEN="verify human https://huggingface.co/docs/hub/security-tokens" 6 | IMAGE_PROXY="image proxy url here" -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | ko_fi: erictik 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report for Examples 2 | 3 | description: Create a report to help us improve 4 | labels: 5 | - bug 6 | 7 | body: 8 | - type: textarea 9 | id: code 10 | attributes: 11 | label: the code that reproduces this issue or a replay of the bug 12 | description: | 13 | public repo link or copy code 14 | validations: 15 | required: true 16 | - type: textarea 17 | id: Describe 18 | attributes: 19 | label: Describe the bug 20 | value: | 21 | **Describe the bug** 22 | A clear and concise description of what the bug is. 23 | 24 | **Expected behavior** 25 | A clear and concise description of what you expected to happen. 26 | 27 | **Screenshots** 28 | If applicable, add screenshots to help explain your problem. 29 | 30 | validations: 31 | required: true 32 | - type: textarea 33 | id: log 34 | attributes: 35 | label: error log 36 | description: Add any other context about the problem here. 37 | validations: 38 | required: true 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/v4.3.x.yaml: -------------------------------------------------------------------------------- 1 | name: verson 4.3.x 2 | env: 3 | APPVERSION: 4.3.${{ github.run_number }} 4 | on: 5 | workflow_dispatch: 6 | push: 7 | branches: 8 | - "main" 9 | paths: 10 | - "src/**.ts" 11 | jobs: 12 | publish-gpr: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: write 16 | steps: 17 | - uses: actions/checkout@v3 18 | - uses: actions/setup-node@v3.6.0 19 | with: 20 | node-version: "18.x" 21 | registry-url: "https://registry.npmjs.org" 22 | env: 23 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 24 | - run: yarn install 25 | # - run: sed -i '3s/\([0-9]\{1,3\}\.[0-9]\{1,3\}\)\.[0-9]\{1,3\}/\1.${{ github.run_number }}/g' package.json 26 | - name: sed version 27 | run: sed -i '3s/[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}/${{ env.APPVERSION }}/g' package.json 28 | - run: npm run build 29 | - run: npm publish 30 | - name: GH Release 31 | uses: softprops/action-gh-release@v0.1.15 32 | with: 33 | tag_name: ${{ env.APPVERSION }} 34 | target_commitish: ${{ github.sha }} 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | 133 | # Directory for build generated files 134 | libs 135 | .idea -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN} 2 | registry=https://registry.npmjs.org/ 3 | always-auth=true -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | libs 2 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # midjourney-client 2 | 3 | Node.js client for the unofficial MidJourney api. 4 | 5 | English / [中文文档](README_zh.md) 6 | 7 |
8 |

9 | Discord server 10 | npm version 11 |

12 |
13 | 14 | ## What's new 15 | 16 | - [face swap](https://github.com/erictik/midjourney-client/blob/main/example/faceswap.ts) 17 | - [niji bot](https://github.com/erictik/midjourney-client/blob/main/example/imagine-niji.ts) 18 | - [custom zoom](https://github.com/erictik/midjourney-client/blob/main/example/customzoom.ts) 19 | - [remix mode](https://github.com/erictik/midjourney-client/blob/main/example/variation-ws.ts) 20 | 21 | ## Example 22 | 23 | To run the included example, you must have [Node.js](https://nodejs.org/en/) installed. Then, run the following commands in the root directory of this project: 24 | 25 | 1. clone the repository 26 | 27 | ```bash 28 | git clone https://github.com/erictik/midjourney-client.git 29 | cd midjourney-client 30 | ``` 31 | 32 | 2. install dependencies 33 | 34 | ```bash 35 | yarn 36 | # or npm 37 | npm install 38 | ``` 39 | 40 | 3. set the environment variables 41 | 42 | - [Login Discord](https://discord.com/channels/@me)`F12` _OR_ `Ctrl + Shift + I` (or `Command + Option + I` on Mac) to open the developer tools _AND_ paste the following code into the console 43 | 44 | ```javascript 45 | window.webpackChunkdiscord_app.push([ 46 | [Math.random()], 47 | {}, 48 | (req) => { 49 | for (const m of Object.keys(req.c) 50 | .map((x) => req.c[x].exports) 51 | .filter((x) => x)) { 52 | if (m.default && m.default.getToken !== undefined) { 53 | return copy(m.default.getToken()); 54 | } 55 | if (m.getToken !== undefined) { 56 | return copy(m.getToken()); 57 | } 58 | } 59 | }, 60 | ]); 61 | console.log("%cWorked!", "font-size: 50px"); 62 | console.log(`%cYou now have your token in the clipboard!`, "font-size: 16px"); 63 | ``` 64 | 65 | OR [use network your Discord TOKEN](https://www.androidauthority.com/get-discord-token-3149920/) 66 | 67 | - [Join my discord server](https://discord.com/invite/GavuGHQbV4) 68 | 69 | ``` 70 | export SERVER_ID="1082500871478329374" 71 | export CHANNEL_ID="1094892992281718894" 72 | ``` 73 | 74 | - OR [Create a server](https://discord.com/blog/starting-your-first-discord-server) and [Invite Midjourney Bot to Your Server](https://docs.midjourney.com/docs/invite-the-bot) 75 | 76 | ```bash 77 | # How to get server and channel ids: 78 | # when you click on a channel in your server in the browser 79 | # expect to have the follow URL pattern 80 | # `https://discord.com/channels/$SERVER_ID/$CHANNEL_ID` 81 | export SERVER_ID="your-server-id" 82 | export CHANNEL_ID="your-channel-id" 83 | ``` 84 | 85 | - wirte your token to `.env` file or set the environment variables 86 | 87 | ```bash 88 | #example variables, please set up yours 89 | 90 | export SERVER_ID="1082500871478329374" 91 | export CHANNEL_ID="1094892992281718894" 92 | export SALAI_TOKEN="your-discord-token" 93 | ``` 94 | 95 | - Then, run the example with the following command: 96 | 97 | ```bash 98 | npx tsx example/imagine-ws.ts 99 | ``` 100 | 101 | OR 102 | 103 | ```bash 104 | yarn example:imagine 105 | # or npm 106 | npm run example:imagine 107 | ``` 108 | 109 | - [more example](./example/) 110 | 111 | ## Usage 112 | 113 | 1. Install 114 | 115 | ```bash 116 | npm i midjourney 117 | # or 118 | yarn add midjourney 119 | ``` 120 | 121 | 2. config param 122 | ```typescript 123 | export interface MJConfigParam { 124 | SalaiToken: string; //DISCORD_TOKEN 125 | ChannelId?: string; //DISCORD_CHANNEL_ID 126 | ServerId?: string; //DISCORD_SERVER_ID 127 | BotId?: typeof MJBot | typeof NijiBot; //DISCORD_BOT_ID MJBot OR NijiBot 128 | Debug?: boolean; // print log 129 | ApiInterval?: number; //ApiInterval request api interval 130 | Limit?: number; //Limit of get message list 131 | MaxWait?: number; 132 | Remix?: boolean; //Remix:true use remix mode 133 | Ws?: boolean; //Ws:true use websocket get discord message (ephemeral message) 134 | HuggingFaceToken?: string; //HuggingFaceToken for verify human 135 | SessionId?: string; 136 | DiscordBaseUrl?: string; 137 | ImageProxy?: string; 138 | WsBaseUrl?: string; 139 | fetch?: FetchFn; //Node.js<18 need node.fetch Or proxy 140 | WebSocket?: WebSocketCl; //isomorphic-ws Or proxy 141 | } 142 | ``` 143 | 3. Use Imagine 、Variation and Upscale 144 | 145 | ```typescript 146 | import { Midjourney } from "midjourney"; 147 | const client = new Midjourney({ 148 | ServerId: process.env.SERVER_ID, 149 | ChannelId: process.env.CHANNEL_ID, 150 | SalaiToken: process.env.SALAI_TOKEN, 151 | Debug: true, 152 | Ws: true, //enable ws is required for remix mode (and custom zoom) 153 | }); 154 | await client.init(); 155 | const prompt = 156 | "Christmas dinner with spaghetti with family in a cozy house, we see interior details , simple blue&white illustration"; 157 | //imagine 158 | const Imagine = await client.Imagine( 159 | prompt, 160 | (uri: string, progress: string) => { 161 | console.log("loading", uri, "progress", progress); 162 | } 163 | ); 164 | console.log(Imagine); 165 | if (!Imagine) { 166 | console.log("no message"); 167 | return; 168 | } 169 | //U1 U2 U3 U4 V1 V2 V3 V4 "Vary (Strong)" ... 170 | //⬅️,⬆️,⬇️,➡️ 171 | const V1CustomID = Imagine.options?.find((o) => o.label === "V1")?.custom; 172 | if (!V1CustomID) { 173 | console.log("no V1"); 174 | return; 175 | } 176 | // Varition V1 177 | const Varition = await client.Custom({ 178 | msgId: Imagine.id, 179 | flags: Imagine.flags, 180 | customId: V1CustomID, 181 | content: prompt, //remix mode require content 182 | loading: (uri: string, progress: string) => { 183 | console.log("loading", uri, "progress", progress); 184 | }, 185 | }); 186 | console.log(Varition); 187 | const U1CustomID = Imagine.options?.find((o) => o.label === "U1")?.custom; 188 | if (!U1CustomID) { 189 | console.log("no U1"); 190 | return; 191 | } 192 | // Upscale U1 193 | const Upscale = await client.Custom({ 194 | msgId: Imagine.id, 195 | flags: Imagine.flags, 196 | customId: U1CustomID, 197 | loading: (uri: string, progress: string) => { 198 | console.log("loading", uri, "progress", progress); 199 | }, 200 | }); 201 | if (!Upscale) { 202 | console.log("no Upscale"); 203 | return; 204 | } 205 | console.log(Upscale); 206 | const zoomout = Upscale?.options?.find((o) => o.label === "Custom Zoom"); 207 | if (!zoomout) { 208 | console.log("no zoomout"); 209 | return; 210 | } 211 | // Custom Zoom 212 | const CustomZoomout = await client.Custom({ 213 | msgId: Upscale.id, 214 | flags: Upscale.flags, 215 | content: `${prompt} --zoom 2`, // Custom Zoom require content 216 | customId: zoomout.custom, 217 | loading: (uri: string, progress: string) => { 218 | console.log("loading", uri, "progress", progress); 219 | }, 220 | }); 221 | console.log(CustomZoomout); 222 | ``` 223 | 224 | 225 | 226 | ## route-map 227 | 228 | - [x] `/imagine` `variation` `upscale` `reroll` `blend` `zoomout` `vary` 229 | - [x] `/info` 230 | - [x] `/fast ` and `/relax ` 231 | - [x] [`/prefer remix`](https://github.com/erictik/midjourney-client/blob/main/example/prefer-remix.ts) 232 | - [x] [`variation (remix mode)`](https://github.com/erictik/midjourney-client/blob/main/example/variation-ws.ts) 233 | - [x] `/describe` 234 | - [x] [`/shorten`](https://github.com/erictik/midjourney-client/blob/main/example/shorten.ts) 235 | - [x] `/settings` `reset` 236 | - [x] verify human 237 | - [x] [proxy](https://github.com/erictik/midjourney-discord/blob/main/examples/proxy.ts) 238 | - [x] [niji bot](https://github.com/erictik/midjourney-client/blob/main/example/imagine-niji.ts) 239 | - [x] [custom zoom](https://github.com/erictik/midjourney-client/blob/main/example/customzoom.ts) 240 | - [x] autoload command payload 241 | 242 | --- 243 | 244 | ## Projects 245 | 246 | - [midjourney-ui](https://github.com/erictik/midjourney-ui/) next.js + vercel 247 | - [midjourney-discord](https://github.com/erictik/midjourney-discord)-bot 248 | - [phrame](https://github.com/jakowenko/phrame) 249 | - [guapitu](https://www.guapitu.com/zh/draw?code=RRXQNF) 250 | 251 | --- 252 | 253 | ## Support 254 | 255 | If you find it valuable and would like to show your support, any donations would be greatly appreciated. Your contribution helps me maintain and improve the program. 256 | 257 | 258 |    259 | 260 | Buy Me a Coffee 261 | 262 | 263 | ## Star History 264 | 265 | [![Star History Chart](https://api.star-history.com/svg?repos=erictik/midjourney-client&type=Date)](https://star-history.com/#erictik/midjourney-client&Date) 266 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | # midjourney-api 2 | 3 | 非官方的 MidJourney api 的 Node.js 客户端。 4 | 5 | [English](README.md) / 中文文档 6 | 7 |
8 |

9 | Discord server 10 | npm version 11 |

12 |
13 | 14 | ## 最近更新 15 | 16 | - [换脸](https://github.com/erictik/midjourney-api/blob/main/example/faceswap.ts) 17 | - [支持 niji bot](https://github.com/erictik/midjourney-api/blob/main/example/imagine-niji.ts) 18 | - [custom zoom](https://github.com/erictik/midjourney-api/blob/main/example/customzoom.ts) 19 | - [remix mode](https://github.com/erictik/midjourney-api/blob/main/example/variation-ws.ts) 20 | 21 | ## 快速开始 22 | 23 | 运行本项目需要安装 [Node.js](https://nodejs.org/en/),然后在本项目的根目录运行以下命令: 24 | 25 | 1. 下载项目代码 26 | 27 | ```bash 28 | git clone https://github.com/erictik/midjourney-api.git 29 | cd midjourney-api 30 | ``` 31 | 32 | 2. 安装依赖 33 | 34 | ```bash 35 | yarn 36 | # or npm 37 | npm install 38 | ``` 39 | 40 | 3. 设置环境变量 41 | 42 | - 获取 Discord TOKEN 43 | [登录 Discor](https://discord.com/channels/@me) F12 或者 [Ctrl + Shift + I] 或者 [Command + Option + I] 打开开发者工具,然后在 Console 中输入以下代码: 44 | 45 | ```javascript 46 | window.webpackChunkdiscord_app.push([ 47 | [Math.random()], 48 | {}, 49 | (req) => { 50 | for (const m of Object.keys(req.c) 51 | .map((x) => req.c[x].exports) 52 | .filter((x) => x)) { 53 | if (m.default && m.default.getToken !== undefined) { 54 | return copy(m.default.getToken()); 55 | } 56 | if (m.getToken !== undefined) { 57 | return copy(m.getToken()); 58 | } 59 | } 60 | }, 61 | ]); 62 | console.log("%cWorked!", "font-size: 50px"); 63 | console.log(`%您的Token在剪贴板了!`, "font-size: 16px"); 64 | ``` 65 | 66 | 也可以通过 查看 network [获取 discord token](https://www.androidauthority.com/get-discord-token-3149920/) 67 | 68 | - [加入我的 Discord 服务器](https://discord.com/invite/GavuGHQbV4) 69 | ``` 70 | export SERVER_ID="1082500871478329374" 71 | export CHANNEL_ID="1094892992281718894" 72 | ``` 73 | - 或者 [创建一个 Discord 服务器](https://discord.com/blog/starting-your-first-discord-server) 并邀请 [Midjourney Bot](https://docs.midjourney.com/docs/invite-the-bot) 74 | 75 | ```bash 76 | # 在浏览器中复制你的服务器网址 77 | # `https://discord.com/channels/$SERVER_ID/$CHANNEL_ID` 78 | export SERVER_ID="your-server-id" 79 | export CHANNEL_ID="your-channel-id" 80 | ``` 81 | 82 | - 将环境变量写入`.env`文件或者 在控制台中设置 83 | 84 | ```bash 85 | #example variables, please set up yours 86 | 87 | export SERVER_ID="1082500871478329374" 88 | export CHANNEL_ID="1094892992281718894" 89 | export SALAI_TOKEN="your-discord-token" 90 | ``` 91 | 92 | 4. 现在可以运行示例了 93 | 94 | ```bash 95 | npx tsx example/imagine-ws.ts 96 | ``` 97 | 98 | 或者 99 | 100 | ```bash 101 | yarn example:imagine 102 | # or npm 103 | npm run example:imagine 104 | ``` 105 | 106 | 5. 更多使用案例 107 | - [more example](./example/) 108 | 109 | ## 在项目中使用 110 | 111 | 1. 安装 112 | 113 | ```bash 114 | npm i midjourney 115 | # or 116 | yarn add midjourney 117 | ``` 118 | 119 | 2. 使用 Imagine 、Variation 和 Upscale 120 | 121 | ```typescript 122 | import { Midjourney } from "midjourney"; 123 | const client = new Midjourney({ 124 | ServerId: process.env.SERVER_ID, 125 | ChannelId: process.env.CHANNEL_ID, 126 | SalaiToken: process.env.SALAI_TOKEN, 127 | Debug: true, 128 | Ws: true, //enable ws is required for remix mode (and custom zoom) 129 | }); 130 | await client.init(); 131 | const prompt = 132 | "Christmas dinner with spaghetti with family in a cozy house, we see interior details , simple blue&white illustration"; 133 | //imagine 134 | const Imagine = await client.Imagine( 135 | prompt, 136 | (uri: string, progress: string) => { 137 | console.log("loading", uri, "progress", progress); 138 | } 139 | ); 140 | console.log(Imagine); 141 | if (!Imagine) { 142 | console.log("no message"); 143 | return; 144 | } 145 | // U1 U2 U3 U4 V1 V2 V3 V4 "Vary (Strong)" ... 146 | const V1CustomID = Imagine.options?.find((o) => o.label === "V1")?.custom; 147 | if (!V1CustomID) { 148 | console.log("no V1"); 149 | return; 150 | } 151 | // Varition V1 152 | const Varition = await client.Custom({ 153 | msgId: Imagine.id, 154 | flags: Imagine.flags, 155 | customId: V1CustomID, 156 | content: prompt, //remix mode require content 157 | loading: (uri: string, progress: string) => { 158 | console.log("loading", uri, "progress", progress); 159 | }, 160 | }); 161 | console.log(Varition); 162 | const U1CustomID = Imagine.options?.find((o) => o.label === "U1")?.custom; 163 | if (!U1CustomID) { 164 | console.log("no U1"); 165 | return; 166 | } 167 | // Upscale U1 168 | const Upscale = await client.Custom({ 169 | msgId: Imagine.id, 170 | flags: Imagine.flags, 171 | customId: U1CustomID, 172 | loading: (uri: string, progress: string) => { 173 | console.log("loading", uri, "progress", progress); 174 | }, 175 | }); 176 | if (!Upscale) { 177 | console.log("no Upscale"); 178 | return; 179 | } 180 | console.log(Upscale); 181 | const zoomout = Upscale?.options?.find((o) => o.label === "Custom Zoom"); 182 | if (!zoomout) { 183 | console.log("no zoomout"); 184 | return; 185 | } 186 | // Custom Zoom 187 | const CustomZoomout = await client.Custom({ 188 | msgId: Upscale.id, 189 | flags: Upscale.flags, 190 | content: `${prompt} --zoom 2`, // Custom Zoom require content 191 | customId: zoomout.custom, 192 | loading: (uri: string, progress: string) => { 193 | console.log("loading", uri, "progress", progress); 194 | }, 195 | }); 196 | console.log(CustomZoomout); 197 | ``` 198 | 199 | ## route-map 200 | 201 | - [x] `/imagine` `variation` `upscale` `reroll` `blend` `zoomout` `vary` 202 | - [x] `/info` 203 | - [x] `/fast ` and `/relax ` 204 | - [x] [`/prefer remix`](https://github.com/erictik/midjourney-api/blob/main/example/prefer-remix.ts) 205 | - [x] [`variation (remix mode)`](https://github.com/erictik/midjourney-api/blob/main/example/variation-ws.ts) 206 | - [x] `/describe` 207 | - [x] [`/shorten`](https://github.com/erictik/midjourney-api/blob/main/example/shorten.ts) 208 | - [x] `/settings` `reset` 209 | - [x] verify human 210 | - [x] [proxy](https://github.com/erictik/midjourney-discord/blob/main/examples/proxy.ts) 211 | - [x] [niji bot](https://github.com/erictik/midjourney-api/blob/main/example/imagine-niji.ts) 212 | - [x] [custom zoom](https://github.com/erictik/midjourney-api/blob/main/example/customzoom.ts) 213 | - [x] autoload command payload 214 | 215 | --- 216 | 217 | ## 应用项目 218 | 219 | - [midjourney-ui](https://github.com/erictik/midjourney-ui/) next.js + vercel 220 | - [midjourney-discord](https://github.com/erictik/midjourney-discord)-bot 221 | - [phrame](https://github.com/jakowenko/phrame) 222 | - [guapitu](https://www.guapitu.com/zh/draw?code=RRXQNF) 223 | 224 | --- 225 | 226 | ## 支持一下我吧 227 | 228 | 如果您觉得它很有价值,可以通过以下方式支持作者 229 | 230 | 231 |    232 | 233 | Buy Me a Coffee 234 | 235 | 236 | ## Star History 237 | 238 | [![Star History Chart](https://api.star-history.com/svg?repos=erictik/midjourney-api&type=Date)](https://star-history.com/#erictik/midjourney-api&Date) 239 | -------------------------------------------------------------------------------- /custompan.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | /** 4 | * 5 | * a simple example of how to use the (custom pan) options with ws command 6 | * keep remix turned off in your settings for this to work 7 | * ``` 8 | * npx tsx example/custompan.ts 9 | * ``` 10 | */ 11 | async function main() { 12 | const client = new Midjourney({ 13 | ServerId: process.env.SERVER_ID, 14 | ChannelId: process.env.CHANNEL_ID, 15 | SalaiToken: process.env.SALAI_TOKEN, 16 | Debug: true, 17 | Ws: true, //enable ws is required for custom pan 18 | }); 19 | await client.init(); 20 | const prompt = 21 | "Christmas dinner with spaghetti with family in a cozy house, we see interior details , simple blue&white illustration"; 22 | //imagine 23 | const Imagine = await client.Imagine( 24 | prompt, 25 | (uri: string, progress: string) => { 26 | console.log("loading", uri, "progress", progress); 27 | } 28 | ); 29 | console.log(Imagine); 30 | if (!Imagine) { 31 | console.log("no message"); 32 | return; 33 | } 34 | //U1 U2 U3 U4 V1 V2 V3 V4 "Vary (Strong)" ... 35 | const V1CustomID = Imagine.options?.find((o) => o.label === "V1")?.custom; 36 | if (!V1CustomID) { 37 | console.log("no V1"); 38 | return; 39 | } 40 | // Varition V1 41 | const Varition = await client.Custom({ 42 | msgId: Imagine.id, 43 | flags: Imagine.flags, 44 | customId: V1CustomID, 45 | loading: (uri: string, progress: string) => { 46 | console.log("loading", uri, "progress", progress); 47 | }, 48 | }); 49 | console.log(Varition); 50 | const U1CustomID = Imagine.options?.find((o) => o.label === "U1")?.custom; 51 | if (!U1CustomID) { 52 | console.log("no U1"); 53 | return; 54 | } 55 | // Upscale U1 56 | const Upscale = await client.Custom({ 57 | msgId: Imagine.id, 58 | flags: Imagine.flags, 59 | customId: U1CustomID, 60 | loading: (uri: string, progress: string) => { 61 | console.log("loading", uri, "progress", progress); 62 | }, 63 | }); 64 | if (!Upscale) { 65 | console.log("no Upscale"); 66 | return; 67 | } 68 | console.log(Upscale); 69 | const panright = Upscale?.options?.find((o) => o.label === "➡️"); // keep remix turned off in your settings for this to work 70 | if (!panright) { 71 | console.log("no panright"); 72 | return; 73 | } 74 | // Custom Pan 75 | const CustomPanRight = await client.Custom({ 76 | msgId: Upscale.id, 77 | flags: Upscale.flags, 78 | content: `${prompt} --pan_right 2`, 79 | customId: panright.custom, 80 | loading: (uri: string, progress: string) => { 81 | console.log("loading", uri, "progress", progress); 82 | }, 83 | }); 84 | console.log("Custom Pan", CustomPanRight); 85 | client.Close(); 86 | } 87 | main() 88 | .then(() => { 89 | console.log("done"); 90 | }) 91 | .catch((err) => { 92 | console.error(err); 93 | process.exit(1); 94 | }); 95 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: MidJourney API. Unofficial Node.js client 3 | --- 4 | 5 | # 👋 MidJourney Node Client 6 | 7 | [![Discord server](https://img.shields.io/discord/1082500871478329374?color=5865F2\&logo=discord\&logoColor=white)](https://discord.gg/GavuGHQbV4) [![npm version](https://img.shields.io/npm/v/midjourney.svg?maxAge=3600)](https://www.npmjs.com/package/midjourney) 8 | 9 | ## Get Started 10 | 11 | 12 | 13 | {% content-ref url="getting-started/quickstart.md" %} 14 | [quickstart.md](getting-started/quickstart.md) 15 | {% endcontent-ref %} 16 | -------------------------------------------------------------------------------- /doc/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [👋 MidJourney Node Client](README.md) 4 | 5 | ## 📓 Getting started 6 | 7 | * [Quickstart](getting-started/quickstart.md) 8 | * [Install](getting-started/install.md) 9 | * [Config](getting-started/config.md) 10 | * [API Reference](getting-started/api-reference/README.md) 11 | * [imagine](getting-started/api-reference/imagine.md) 12 | * [variation](getting-started/api-reference/variation.md) 13 | * [upscale](getting-started/api-reference/upscale.md) 14 | * [reroll](getting-started/api-reference/reroll.md) 15 | * [blend](getting-started/api-reference/blend.md) 16 | * [describe](getting-started/api-reference/describe.md) 17 | * [info](getting-started/api-reference/info.md) 18 | * [fast](getting-started/api-reference/fast.md) 19 | * [relax](getting-started/api-reference/relax.md) 20 | * [setting](getting-started/api-reference/setting.md) 21 | * [reset](getting-started/api-reference/reset.md) 22 | * [Example](getting-started/example/README.md) 23 | * [Imagine](getting-started/example/imagine.md) 24 | 25 | ## FAQ 26 | 27 | * [vercel](faq/vercel.md) 28 | * [node.js <18](faq/node.js-less-than-18.md) 29 | * [proxy](faq/proxy.md) 30 | * [How to get your Discord Token](https://www.androidauthority.com/get-discord-token-3149920/) 31 | * [Invite Midjourney Bot to Your Server](https://docs.midjourney.com/docs/invite-the-bot) 32 | 33 | *** 34 | 35 | * [Join the Beta](https://discord.com/invite/GavuGHQbV4) 36 | 37 | ## Use Cases 38 | 39 | * [🎨 Web UI](use-cases/web-ui.md) 40 | * [🤖 Discord Bot](use-cases/discord-bot.md) 41 | -------------------------------------------------------------------------------- /doc/faq/node.js-less-than-18.md: -------------------------------------------------------------------------------- 1 | # node.js <18 2 | 3 | -------------------------------------------------------------------------------- /doc/faq/proxy.md: -------------------------------------------------------------------------------- 1 | # proxy 2 | 3 | -------------------------------------------------------------------------------- /doc/faq/vercel.md: -------------------------------------------------------------------------------- 1 | # vercel 2 | 3 | -------------------------------------------------------------------------------- /doc/getting-started/api-reference/README.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | -------------------------------------------------------------------------------- /doc/getting-started/api-reference/blend.md: -------------------------------------------------------------------------------- 1 | # blend 2 | 3 | -------------------------------------------------------------------------------- /doc/getting-started/api-reference/describe.md: -------------------------------------------------------------------------------- 1 | # describe 2 | 3 | -------------------------------------------------------------------------------- /doc/getting-started/api-reference/fast.md: -------------------------------------------------------------------------------- 1 | # fast 2 | 3 | -------------------------------------------------------------------------------- /doc/getting-started/api-reference/imagine.md: -------------------------------------------------------------------------------- 1 | # imagine 2 | 3 | 4 | 5 |
import { Midjourney } from "midjourney";
 6 | async function main() {
 7 |   const client = new Midjourney({
 8 |     ServerId: "1082500871478329374",
 9 |     ChannelId: "1094892992281718894",
10 |     SalaiToken: "your discord token",
11 |   });
12 |   await client.Connect();
13 |   const Imagine = await client.Imagine(
14 |     "Red hamster smoking a cigaret",
15 |     (uri: string, progress: string) => {
16 |       console.log("Imagine.loading", uri, "progress", progress);
17 |     }
18 |   );
19 |   console.log( Imagine );
20 |   client.Close();
21 | }
22 | 
23 | 
24 | -------------------------------------------------------------------------------- /doc/getting-started/api-reference/info.md: -------------------------------------------------------------------------------- 1 | # info 2 | 3 | -------------------------------------------------------------------------------- /doc/getting-started/api-reference/relax.md: -------------------------------------------------------------------------------- 1 | # relax 2 | 3 | -------------------------------------------------------------------------------- /doc/getting-started/api-reference/reroll.md: -------------------------------------------------------------------------------- 1 | # reroll 2 | 3 | -------------------------------------------------------------------------------- /doc/getting-started/api-reference/reset.md: -------------------------------------------------------------------------------- 1 | # reset 2 | 3 | -------------------------------------------------------------------------------- /doc/getting-started/api-reference/setting.md: -------------------------------------------------------------------------------- 1 | # setting 2 | 3 | -------------------------------------------------------------------------------- /doc/getting-started/api-reference/upscale.md: -------------------------------------------------------------------------------- 1 | # upscale 2 | 3 | -------------------------------------------------------------------------------- /doc/getting-started/api-reference/variation.md: -------------------------------------------------------------------------------- 1 | # variation 2 | 3 | -------------------------------------------------------------------------------- /doc/getting-started/config.md: -------------------------------------------------------------------------------- 1 | # Config 2 | 3 |
PropertyTypeDescriptionRequiredDefault
SalaiTokenstringDiscord Token true
ServerIdstringDiscord server Idfalsenull ( DM Midjourney bot)
ChannelIdstringDiscord Channel Idtrue1077800642086703114(Midjourney bot)
SessionIdstringDiscord Session Idfalse8bb7f5b79c7a49f7d0824ab4b8773a81
Debugbooleanprint logfalsefalse
Wsbooleanwebsocket get messagefalsefalse
HuggingFaceTokenstringverify humanfalse
DiscordBaseUrlstringProxy Discord Urlfalse
WsBaseUrlstringProxy Discord Wsebsocket Urlfalse
Limitnumberfalse50
MaxWaitnumberfalse200
fetchfetchnode <18 OR proxyfalse
websocketwebsocketproxyfalse
4 | -------------------------------------------------------------------------------- /doc/getting-started/example/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | -------------------------------------------------------------------------------- /doc/getting-started/example/image.md: -------------------------------------------------------------------------------- 1 | # Image 2 | 3 | -------------------------------------------------------------------------------- /doc/getting-started/example/imagine.md: -------------------------------------------------------------------------------- 1 | # Imagine 2 | 3 | -------------------------------------------------------------------------------- /doc/getting-started/install.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Install MidJourney , the unofficial Node.js client 3 | --- 4 | 5 | # Install 6 | 7 | [![npm version](https://img.shields.io/npm/v/midjourney.svg?maxAge=3600)](https://www.npmjs.com/package/midjourney) 8 | 9 | {% hint style="info" %} 10 | Make sure you're using `node >= 18` so `fetch` is available 11 | {% endhint %} 12 | 13 | ### npm 14 | 15 | ``` 16 | npm i midjourney 17 | ``` 18 | 19 | ### yarn 20 | 21 | ``` 22 | yarn add midjourney 23 | ``` 24 | -------------------------------------------------------------------------------- /doc/getting-started/quickstart.md: -------------------------------------------------------------------------------- 1 | # Quickstart 2 | 3 | > #### **Step1: Get your Discord Token**[Join the Beta](https://discord.com/invite/GavuGHQbV4) 4 | 5 | {% content-ref url="broken-reference" %} 6 | [Broken link](broken-reference) 7 | {% endcontent-ref %} 8 | 9 | > #### **Step2: Join Discord Server** 10 | 11 | {% content-ref url="broken-reference" %} 12 | [Broken link](broken-reference) 13 | {% endcontent-ref %} 14 | 15 | > #### **Step3: Install** midjourney 16 | 17 | {% content-ref url="install.md" %} 18 | [install.md](install.md) 19 | {% endcontent-ref %} 20 | 21 | > #### **Step3:** Use the imagine api 22 | 23 | {% tabs %} 24 | {% tab title="Typescirpt" %} 25 | {% code overflow="wrap" lineNumbers="true" %} 26 | ```typescript 27 | import { Midjourney } from "midjourney"; 28 | 29 | const client = new Midjourney({ 30 | ServerId: "1082500871478329374", 31 | ChannelId: "1094892992281718894", 32 | SalaiToken: "your discord token", 33 | Debug: true, 34 | }); 35 | 36 | const msg = await client.Imagine( 37 | "A little white elephant", 38 | (uri: string, progress: string) => { 39 | console.log("loading:", uri, "progress:", progress); 40 | } 41 | ); 42 | console.log({ msg }); 43 | ``` 44 | {% endcode %} 45 | {% endtab %} 46 | 47 | {% tab title="Javascirpt" %} 48 | {% code overflow="wrap" lineNumbers="true" %} 49 | ```javascript 50 | const { Midjourney } = require("midjourney"); 51 | 52 | const client = new Midjourney({ 53 | ServerId: "1082500871478329374", 54 | ChannelId: "1094892992281718894", 55 | SalaiToken: "your discord token", 56 | Debug: true, 57 | Ws:true, 58 | }); 59 | const msg = await client.Imagine("A little pink elephant", (uri, progress) => { 60 | console.log("loading:", uri, "progress:", progress); 61 | }); 62 | console.log({ msg }); 63 | 64 | 65 | ``` 66 | {% endcode %} 67 | {% endtab %} 68 | {% endtabs %} 69 | -------------------------------------------------------------------------------- /doc/use-cases/discord-bot.md: -------------------------------------------------------------------------------- 1 | # 🤖 Discord Bot 2 | 3 | [https://github.com/erictik/midjourney-discord/issues](https://github.com/erictik/midjourney-discord/issues) 4 | -------------------------------------------------------------------------------- /doc/use-cases/web-ui.md: -------------------------------------------------------------------------------- 1 | # 🎨 Web UI 2 | 3 | ## [https://github.com/erictik/midjourney-ui](https://github.com/erictik/midjourney-ui) 4 | -------------------------------------------------------------------------------- /example/banend-words.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney, detectBannedWords } from "../src"; 3 | /** 4 | * 5 | * a simple example of how to use the banned words 6 | * ``` 7 | * npx tsx example/banend-words.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | var prompt = "horny girl"; 12 | var message = detectBannedWords(prompt); 13 | if (message.length > 0) { 14 | console.error("banned words detected"); 15 | } 16 | console.log(message); 17 | } 18 | main().catch((err) => { 19 | console.error(err); 20 | process.exit(1); 21 | }); 22 | -------------------------------------------------------------------------------- /example/customzoom.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | /** 4 | * 5 | * a simple example of how to use the (custom zoom) options with ws command 6 | * ``` 7 | * npx tsx example/customzoom.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | ServerId: process.env.SERVER_ID, 13 | ChannelId: process.env.CHANNEL_ID, 14 | SalaiToken: process.env.SALAI_TOKEN, 15 | Debug: true, 16 | Ws: true, //enable ws is required for custom zoom 17 | }); 18 | await client.init(); 19 | const prompt = 20 | "Christmas dinner with spaghetti with family in a cozy house, we see interior details , simple blue&white illustration"; 21 | //imagine 22 | const Imagine = await client.Imagine( 23 | prompt, 24 | (uri: string, progress: string) => { 25 | console.log("loading", uri, "progress", progress); 26 | } 27 | ); 28 | console.log(Imagine); 29 | if (!Imagine) { 30 | console.log("no message"); 31 | return; 32 | } 33 | //U1 U2 U3 U4 V1 V2 V3 V4 "Vary (Strong)" ... 34 | const V1CustomID = Imagine.options?.find((o) => o.label === "V1")?.custom; 35 | if (!V1CustomID) { 36 | console.log("no V1"); 37 | return; 38 | } 39 | // Varition V1 40 | const Varition = await client.Custom({ 41 | msgId: Imagine.id, 42 | flags: Imagine.flags, 43 | customId: V1CustomID, 44 | loading: (uri: string, progress: string) => { 45 | console.log("loading", uri, "progress", progress); 46 | }, 47 | }); 48 | console.log(Varition); 49 | const U1CustomID = Imagine.options?.find((o) => o.label === "U1")?.custom; 50 | if (!U1CustomID) { 51 | console.log("no U1"); 52 | return; 53 | } 54 | // Upscale U1 55 | const Upscale = await client.Custom({ 56 | msgId: Imagine.id, 57 | flags: Imagine.flags, 58 | customId: U1CustomID, 59 | loading: (uri: string, progress: string) => { 60 | console.log("loading", uri, "progress", progress); 61 | }, 62 | }); 63 | if (!Upscale) { 64 | console.log("no Upscale"); 65 | return; 66 | } 67 | console.log(Upscale); 68 | const zoomout = Upscale?.options?.find((o) => o.label === "Custom Zoom"); 69 | if (!zoomout) { 70 | console.log("no zoomout"); 71 | return; 72 | } 73 | // Custom Zoom 74 | const CustomZoomout = await client.Custom({ 75 | msgId: Upscale.id, 76 | flags: Upscale.flags, 77 | content: `${prompt} --zoom 2`, 78 | customId: zoomout.custom, 79 | loading: (uri: string, progress: string) => { 80 | console.log("loading", uri, "progress", progress); 81 | }, 82 | }); 83 | console.log("Custom Zoom", CustomZoomout); 84 | client.Close(); 85 | } 86 | main() 87 | .then(() => { 88 | console.log("done"); 89 | }) 90 | .catch((err) => { 91 | console.error(err); 92 | process.exit(1); 93 | }); 94 | -------------------------------------------------------------------------------- /example/describe.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | /** 4 | * 5 | * a simple example of using the describe api 6 | * ``` 7 | * npx tsx example/describe.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | ServerId: process.env.SERVER_ID, 13 | ChannelId: process.env.CHANNEL_ID, 14 | SalaiToken: process.env.SALAI_TOKEN, 15 | Debug: true, 16 | Ws: true, 17 | }); 18 | await client.Connect(); 19 | const Describe = await client.Describe( 20 | "https://cdn.discordapp.com/attachments/1107965981839605792/1119977411631652914/Soga_a_cool_cat_blue_ears_yellow_hat_02afd1ed-17eb-4a61-9101-7a99b105e4cc.png" 21 | ); 22 | console.log(Describe); 23 | if (!Describe) { 24 | console.log("failed to describe"); 25 | } 26 | } 27 | main().catch((err) => { 28 | console.log("finished"); 29 | console.error(err); 30 | process.exit(1); 31 | }); 32 | -------------------------------------------------------------------------------- /example/faceswap.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney, detectBannedWords } from "../src"; 3 | import { url } from "inspector"; 4 | /** 5 | * 6 | * a simple example of how to use faceSwap 7 | * ``` 8 | * npx tsx example/faceswap.ts 9 | * ``` 10 | */ 11 | async function main() { 12 | const source = `https://cdn.discordapp.com/attachments/1107965981839605792/1129362418775113789/3829c5d7-3e7e-473c-9c7b-b858e3ec97bc.jpeg`; 13 | // const source = `https://cdn.discordapp.com/attachments/1108587422389899304/1129321826804306031/guapitu006_Cute_warrior_girl_in_the_style_of_Baten_Kaitos__111f39bc-329e-4fab-9af7-ee219fedf260.png`; 14 | const target = `https://cdn.discordapp.com/attachments/1108587422389899304/1129321837042602016/guapitu006_a_girls_face_with_david_bowies_thunderbolt_71ee5899-bd45-4fc4-8c9d-92f19ddb0a03.png`; 15 | const client = new Midjourney({ 16 | ServerId: process.env.SERVER_ID, 17 | ChannelId: process.env.CHANNEL_ID, 18 | SalaiToken: process.env.SALAI_TOKEN, 19 | Debug: true, 20 | HuggingFaceToken: process.env.HUGGINGFACE_TOKEN, 21 | }); 22 | const info = await client.FaceSwap(target, source); 23 | console.log(info?.uri); 24 | } 25 | main() 26 | .then(() => { 27 | console.log("finished"); 28 | process.exit(0); 29 | }) 30 | .catch((err) => { 31 | console.error(err); 32 | process.exit(1); 33 | }); 34 | -------------------------------------------------------------------------------- /example/fast.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | /** 4 | * 5 | * a simple example of using the fast api 6 | * ``` 7 | * npx tsx example/fast.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | ServerId: process.env.SERVER_ID, 13 | ChannelId: process.env.CHANNEL_ID, 14 | SalaiToken: process.env.SALAI_TOKEN, 15 | Debug: true, 16 | Ws: true, 17 | }); 18 | await client.Connect(); 19 | await client.Fast(); 20 | const msg = await client.Info(); 21 | console.log({ msg }); 22 | client.Close(); 23 | } 24 | main() 25 | .then(() => { 26 | console.log("finished"); 27 | process.exit(0); 28 | }) 29 | .catch((err) => { 30 | console.log("finished"); 31 | console.error(err); 32 | process.exit(1); 33 | }); 34 | -------------------------------------------------------------------------------- /example/imagine-blend.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | /** 4 | * 5 | * a simple example of how to use the blend 6 | * ``` 7 | * npx tsx example/blend.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | ServerId: process.env.SERVER_ID, 13 | ChannelId: process.env.CHANNEL_ID, 14 | SalaiToken: process.env.SALAI_TOKEN, 15 | Debug: true, 16 | }); 17 | await client.Connect(); 18 | const msg = await client.Imagine( 19 | "https://media.discordapp.net/attachments/1094892992281718894/1106660210380132503/Soga_A_Greek_man_with_mustache_in_national_costume_riding_a_don_3255e7c1-38ee-4892-b7c7-9f0dc3f2786d.png?width=1040&height=1040 https://cdn.discordapp.com/attachments/1094892992281718894/1106798152188702720/Soga__489d80b2-db74-4a93-a998-881a9542abbe.png", 20 | (uri: string, progress: string) => { 21 | console.log("loading", uri, "progress", progress); 22 | } 23 | ); 24 | console.log({ msg }); 25 | if (!msg) { 26 | console.log("no message"); 27 | return; 28 | } 29 | const msg2 = await client.Upscale({ 30 | index: 2, 31 | msgId: msg.id, 32 | hash: msg.hash, 33 | flags: msg.flags, 34 | content: msg.content, 35 | loading: (uri: string, progress: string) => { 36 | console.log("loading", uri, "progress", progress); 37 | }, 38 | }); 39 | console.log({ msg2 }); 40 | } 41 | main().catch((err) => { 42 | console.error(err); 43 | process.exit(1); 44 | }); 45 | -------------------------------------------------------------------------------- /example/imagine-dm.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | /** 4 | * 5 | * a simple example of using the imagine api via DM Midjourney Bot 6 | * ``` 7 | * npx tsx example/imagine-dm.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | SalaiToken: process.env.SALAI_TOKEN, 13 | Debug: true, 14 | Ws: true, 15 | }); 16 | await client.Connect(); 17 | // const info = await client.Info(); 18 | // console.log(info); 19 | // return 20 | const msg = await client.Imagine( 21 | "A little white dog", 22 | (uri: string, progress: string) => { 23 | console.log("loading", uri, "progress", progress); 24 | } 25 | ); 26 | console.log({ msg }); 27 | } 28 | main() 29 | .then(() => { 30 | console.log("finished"); 31 | process.exit(0); 32 | }) 33 | .catch((err) => { 34 | console.log("finished"); 35 | console.error(err); 36 | process.exit(1); 37 | }); 38 | -------------------------------------------------------------------------------- /example/imagine-err.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | /** 4 | * 5 | * a simple example of how to use the imagine command 6 | * ``` 7 | * npx tsx example/imagine-err.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | ServerId: process.env.SERVER_ID, 13 | ChannelId: process.env.CHANNEL_ID, 14 | SalaiToken: process.env.SALAI_TOKEN, 15 | Debug: true, 16 | }); 17 | await client.init(); 18 | // `https://images.guapitu.com/chatgpt/5b9b907a/d3297338-ae3e-4276-9bd9-3b6ca27cedcf.png 19 | // https://images.guapitu.com/chatgpt/762a2db4/459d52f1-23fd-41c3-a912-317e65155fcc.png 20 | // https://images.guapitu.com/chatgpt/f86613ac/2e2497ae-9906-44d9-8396-e41abab2f47b.png 21 | // cat` 22 | const prompt = `%s %sTiny cute isometric Hcia illustration, a girl with long white hair, smile, seawater, colorful bubbles, dreamy portrait, Teana punk, more details, fiber tracking, snail core, Kuvshinov Ilya, yakamoz emoji, soft lighting, soft colors, matte clay, blender 3d, pastel background --v 5.1 --ar 1:1 --s 350 --q 1`; 23 | const msg = await client.Imagine(prompt, (uri: string, progress: string) => { 24 | console.log("loading", uri, "progress", progress); 25 | }); 26 | console.log({ msg }); 27 | } 28 | main().catch((err) => { 29 | console.error(err); 30 | process.exit(1); 31 | }); 32 | -------------------------------------------------------------------------------- /example/imagine-niji.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney, NijiBot } from "../src"; 3 | /** 4 | * 5 | * a simple example of using the imagine api via DM Niji Bot 6 | * ``` 7 | * npx tsx example/imagine-niji.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | SalaiToken: process.env.SALAI_TOKEN, 13 | BotId: NijiBot, // NijiBot 14 | ChannelId: "1125452970276954204", 15 | Debug: true, 16 | Ws: true, 17 | }); 18 | await client.Connect(); 19 | const info = await client.Info(); 20 | console.log(info); 21 | const msg = await client.Imagine( 22 | "A little white dog", 23 | (uri: string, progress: string) => { 24 | console.log("loading", uri, "progress", progress); 25 | } 26 | ); 27 | console.log( msg ); 28 | client.Close(); 29 | } 30 | main() 31 | .then(() => { 32 | // console.log("finished"); 33 | // process.exit(0); 34 | }) 35 | .catch((err) => { 36 | console.log("finished"); 37 | console.error(err); 38 | process.exit(1); 39 | }); 40 | -------------------------------------------------------------------------------- /example/imagine-ws-m.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | /** 4 | * 5 | * a simple example of using the imagine api with ws 6 | * ``` 7 | * npx tsx example/imagine-ws-m.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | ServerId: process.env.SERVER_ID, 13 | ChannelId: process.env.CHANNEL_ID, 14 | SalaiToken: process.env.SALAI_TOKEN, 15 | HuggingFaceToken: process.env.HUGGINGFACE_TOKEN, 16 | Debug: true, 17 | }); 18 | await client.Connect(); 19 | client 20 | .Imagine("A little pink elephant", (uri) => { 21 | console.log("loading123---", uri); 22 | }) 23 | .then(function (msg) { 24 | console.log("msg123", msg); 25 | }); 26 | 27 | client 28 | .Imagine("A little pink dog", (uri) => { 29 | console.log("loading234---", uri); 30 | }) 31 | .then(function (msg) { 32 | console.log("msg234", msg); 33 | }); 34 | } 35 | main() 36 | .then(() => { 37 | console.log("finished"); 38 | // process.exit(0); 39 | }) 40 | .catch((err) => { 41 | console.log("finished"); 42 | console.error(err); 43 | process.exit(1); 44 | }); 45 | -------------------------------------------------------------------------------- /example/imagine-ws.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | /** 4 | * 5 | * a simple example of using the imagine api with ws 6 | * ``` 7 | * npx tsx example/imagine-ws.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | ServerId: process.env.SERVER_ID, 13 | ChannelId: process.env.CHANNEL_ID, 14 | SalaiToken: process.env.SALAI_TOKEN, 15 | HuggingFaceToken: process.env.HUGGINGFACE_TOKEN, 16 | Debug: true, 17 | Ws: true, // required `Only you can see this` 18 | }); 19 | await client.Connect(); // required 20 | const Imagine = await client.Imagine( 21 | "Red hamster smoking a cigaret --fast", 22 | (uri: string, progress: string) => { 23 | console.log("Imagine.loading", uri, "progress", progress); 24 | } 25 | ); 26 | console.log({ Imagine }); 27 | if (!Imagine) { 28 | return; 29 | } 30 | const reroll = await client.Reroll({ 31 | msgId: Imagine.id, 32 | hash: Imagine.hash, 33 | flags: Imagine.flags, 34 | loading: (uri: string, progress: string) => { 35 | console.log("Reroll.loading", uri, "progress", progress); 36 | }, 37 | }); 38 | console.log({ reroll }); 39 | 40 | const Variation = await client.Variation({ 41 | index: 2, 42 | msgId: Imagine.id, 43 | hash: Imagine.hash, 44 | flags: Imagine.flags, 45 | loading: (uri: string, progress: string) => { 46 | console.log("Variation.loading", uri, "progress", progress); 47 | }, 48 | }); 49 | 50 | console.log({ Variation }); 51 | if (!Variation) { 52 | return; 53 | } 54 | const Upscale = await client.Upscale({ 55 | index: 2, 56 | msgId: Variation.id, 57 | hash: Variation.hash, 58 | flags: Variation.flags, 59 | loading: (uri: string, progress: string) => { 60 | console.log("Upscale.loading", uri, "progress", progress); 61 | }, 62 | }); 63 | console.log({ Upscale }); 64 | 65 | client.Close(); 66 | } 67 | main() 68 | .then(() => { 69 | // console.log("finished"); 70 | // process.exit(0); 71 | }) 72 | .catch((err) => { 73 | console.log("finished"); 74 | console.error(err); 75 | process.exit(1); 76 | }); 77 | -------------------------------------------------------------------------------- /example/imagine.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const { Midjourney } = require("../libs"); 3 | /** 4 | * 5 | * a simple example of how to use the imagine command 6 | * ``` 7 | * node example/imagine.js 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | ServerId: process.env.SERVER_ID, 13 | ChannelId: process.env.CHANNEL_ID, 14 | SalaiToken: process.env.SALAI_TOKEN, 15 | Debug: true, 16 | Ws: true, 17 | SessionId: process.env.SALAI_TOKEN || "8bb7f5b79c7a49f7d0824ab4b8773a81", 18 | }); 19 | await client.init(); 20 | const msg = await client.Imagine("A little pink elephant", (uri) => { 21 | console.log("loading", uri); 22 | }); 23 | console.log({ msg }); 24 | } 25 | main().catch((err) => { 26 | console.error(err); 27 | process.exit(1); 28 | }); 29 | -------------------------------------------------------------------------------- /example/imagine.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | /** 4 | * 5 | * a simple example of how to use the imagine command 6 | * ``` 7 | * npx tsx example/imagine.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | ServerId: process.env.SERVER_ID, 13 | ChannelId: process.env.CHANNEL_ID, 14 | SalaiToken: process.env.SALAI_TOKEN, 15 | Debug: true, 16 | Ws: false, 17 | }); 18 | 19 | const msg = await client.Imagine( 20 | "Red hamster", 21 | (uri: string, progress: string) => { 22 | console.log("loading", uri, "progress", progress); 23 | } 24 | ); 25 | console.log(JSON.stringify(msg)); 26 | } 27 | main().catch((err) => { 28 | console.error(err); 29 | process.exit(1); 30 | }); 31 | -------------------------------------------------------------------------------- /example/info.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | /** 4 | * 5 | * a simple example of using the info api 6 | * ``` 7 | * npx tsx example/info.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | ServerId: process.env.SERVER_ID, 13 | ChannelId: process.env.CHANNEL_ID, 14 | SalaiToken: process.env.SALAI_TOKEN, 15 | Debug: true, 16 | Ws: true, 17 | }); 18 | await client.Connect(); 19 | const msg = await client.Info(); 20 | console.log({ msg }); 21 | client.Close(); 22 | } 23 | main() 24 | .then(() => { 25 | console.log("finished"); 26 | process.exit(0); 27 | }) 28 | .catch((err) => { 29 | console.log("finished"); 30 | console.error(err); 31 | process.exit(1); 32 | }); 33 | -------------------------------------------------------------------------------- /example/prefer-remix.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | /** 4 | * 5 | * a simple example of using the prefer remix api 6 | * ``` 7 | * npx tsx example/prefer-remix.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | ServerId: process.env.SERVER_ID, 13 | ChannelId: process.env.CHANNEL_ID, 14 | SalaiToken: process.env.SALAI_TOKEN, 15 | Debug: true, 16 | Ws: true, //enable ws is required for prefer remix 17 | }); 18 | await client.Connect(); 19 | const msg = await client.SwitchRemix(); 20 | console.log(msg); 21 | client.Close(); 22 | } 23 | main() 24 | .then(() => { 25 | console.log("finished"); 26 | process.exit(0); 27 | }) 28 | .catch((err) => { 29 | console.log("finished"); 30 | console.error(err); 31 | process.exit(1); 32 | }); 33 | -------------------------------------------------------------------------------- /example/relax.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | /** 4 | * 5 | * a simple example of using the relax api 6 | * ``` 7 | * npx tsx example/relax.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | ServerId: process.env.SERVER_ID, 13 | ChannelId: process.env.CHANNEL_ID, 14 | SalaiToken: process.env.SALAI_TOKEN, 15 | Debug: true, 16 | Ws: true, 17 | }); 18 | await client.Connect(); 19 | await client.Relax(); 20 | const msg = await client.Info(); 21 | console.log({ msg }); 22 | client.Close(); 23 | } 24 | main() 25 | .then(() => { 26 | console.log("finished"); 27 | process.exit(0); 28 | }) 29 | .catch((err) => { 30 | console.log("finished"); 31 | console.error(err); 32 | process.exit(1); 33 | }); 34 | -------------------------------------------------------------------------------- /example/reset.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | /** 4 | * 5 | * a simple example of using the reset api 6 | * ``` 7 | * npx tsx example/reset.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | ServerId: process.env.SERVER_ID, 13 | ChannelId: process.env.CHANNEL_ID, 14 | SalaiToken: process.env.SALAI_TOKEN, 15 | Debug: true, 16 | Ws: true, 17 | }); 18 | await client.Connect(); 19 | await client.Reset(); 20 | client.Close(); 21 | } 22 | main() 23 | .then(() => { 24 | console.log("finished"); 25 | process.exit(0); 26 | }) 27 | .catch((err) => { 28 | console.log("finished"); 29 | console.error(err); 30 | process.exit(1); 31 | }); 32 | -------------------------------------------------------------------------------- /example/settings.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | /** 4 | * 5 | * a simple example of using the settings api 6 | * ``` 7 | * npx tsx example/settings.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | ServerId: process.env.SERVER_ID, 13 | ChannelId: process.env.CHANNEL_ID, 14 | SalaiToken: process.env.SALAI_TOKEN, 15 | Debug: true, 16 | Ws: true, //Important 17 | }); 18 | await client.Connect(); 19 | const msg = await client.Settings(); 20 | console.log(msg); 21 | if (!msg) { 22 | return; 23 | } 24 | // //niji5 25 | const niji5 = msg.options.filter((x) => { 26 | return x.label === "Niji version 5"; 27 | })[0]; 28 | console.log(niji5); 29 | // const httpstatus = await client.MJApi.CustomApi({ 30 | // msgId: msg.id, 31 | // customId: niji5.custom, 32 | // flags: msg.flags, 33 | // }); 34 | // console.log({ httpstatus }); 35 | // const setting = await client.Settings(); 36 | // console.log({ setting }); 37 | //reset settings 38 | 39 | client.Close(); 40 | } 41 | main() 42 | .then(() => { 43 | console.log("finished"); 44 | process.exit(0); 45 | }) 46 | .catch((err) => { 47 | console.log("finished"); 48 | console.error(err); 49 | process.exit(1); 50 | }); 51 | -------------------------------------------------------------------------------- /example/shorten.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | import { sleep } from "../src/utls"; 4 | /** 5 | * 6 | * a simple example of using the shorten api 7 | * ``` 8 | * npx tsx example/shorten.ts 9 | * ``` 10 | */ 11 | async function main() { 12 | const client = new Midjourney({ 13 | ServerId: process.env.SERVER_ID, 14 | ChannelId: process.env.CHANNEL_ID, 15 | SalaiToken: process.env.SALAI_TOKEN, 16 | Debug: true, 17 | Ws: true, 18 | }); 19 | await client.Connect(); 20 | const Shorten = await client.Shorten( 21 | "Peeking out from the bushes, masterpiece, octane rendering, focus, realistic photography, colorful background, detailed, intricate details, rich colors, realistic style" 22 | ); 23 | console.log(Shorten); 24 | if (!Shorten) { 25 | console.log("no message"); 26 | return; 27 | } 28 | const prompt = Shorten.options.find((o) => o.label === `1️⃣`); 29 | if (!prompt) { 30 | console.log("no prompt"); 31 | return; 32 | } 33 | await sleep(1400); 34 | //shorten click 35 | // const imagine = await client.Custom({ 36 | // msgId: Shorten.id, 37 | // flags: Shorten.flags, 38 | // content: Shorten.prompts[0], 39 | // customId: prompt.custom, 40 | // loading: (uri: string, progress: string) => { 41 | // console.log("loading", uri, "progress", progress); 42 | // }, 43 | // }); 44 | // console.log("Custom Zoom", zoomout2x); 45 | 46 | client.Close(); 47 | } 48 | main().catch((err) => { 49 | console.log("finished"); 50 | console.error(err); 51 | process.exit(1); 52 | }); 53 | -------------------------------------------------------------------------------- /example/upscale-ws.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | /** 4 | * 5 | * a simple example of how to use the Upscale with ws command 6 | * ``` 7 | * npx tsx example/upscale-ws.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | ServerId: process.env.SERVER_ID, 13 | ChannelId: process.env.CHANNEL_ID, 14 | SalaiToken: process.env.SALAI_TOKEN, 15 | // Debug: true, 16 | Ws: true, 17 | }); 18 | await client.Connect(); 19 | const Imagine = await client.Imagine( 20 | "a cool cat, blue ears, yellow hat --v 4", 21 | (uri: string, progress: string) => { 22 | console.log("loading", uri, "progress", progress); 23 | } 24 | ); 25 | console.log(Imagine); 26 | if (!Imagine) { 27 | console.log("no message"); 28 | return; 29 | } 30 | const Upscale = await client.Upscale({ 31 | index: 2, 32 | msgId: Imagine.id, 33 | hash: Imagine.hash, 34 | flags: Imagine.flags, 35 | loading: (uri: string, progress: string) => { 36 | console.log("loading", uri, "progress", progress); 37 | }, 38 | }); 39 | console.log(Upscale); 40 | client.Close(); 41 | } 42 | main().catch((err) => { 43 | console.error(err); 44 | process.exit(1); 45 | }); 46 | -------------------------------------------------------------------------------- /example/upscale.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | /** 4 | * 5 | * a simple example of how to use the Upscale command 6 | * ``` 7 | * npx tsx example/upscale.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | ServerId: process.env.SERVER_ID, 13 | ChannelId: process.env.CHANNEL_ID, 14 | SalaiToken: process.env.SALAI_TOKEN, 15 | Debug: true, 16 | }); 17 | const msg = await client.Imagine("a cool cat, blue ears, yellow hat"); 18 | console.log({ msg }); 19 | if (!msg) { 20 | console.log("no message"); 21 | return; 22 | } 23 | const msg2 = await client.Upscale({ 24 | index: 2, 25 | msgId: msg.id, 26 | hash: msg.hash, 27 | flags: msg.flags, 28 | content: msg.content, 29 | loading: (uri: string, progress: string) => { 30 | console.log("loading", uri, "progress", progress); 31 | }, 32 | }); 33 | console.log({ msg2 }); 34 | } 35 | main().catch((err) => { 36 | console.error(err); 37 | process.exit(1); 38 | }); 39 | -------------------------------------------------------------------------------- /example/variation-ws.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | /** 4 | * 5 | * a simple example of how to use the Variation (remix mode) with ws command 6 | * ``` 7 | * npx tsx example/variation-ws.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | ServerId: process.env.SERVER_ID, 13 | ChannelId: process.env.CHANNEL_ID, 14 | SalaiToken: process.env.SALAI_TOKEN, 15 | Debug: true, 16 | Ws: true, //enable ws is required for remix mode 17 | }); 18 | await client.init(); //init auto enable remix mode 19 | const prompt = 20 | "48 year old woman with auburn hair plays video games on a tablet in her bedroom and is a chemist. Engaged. Happy. Evening. Silver blue walls in room. In the style of anime. does not exceed 10 MB."; 21 | const Imagine = await client.Imagine( 22 | prompt, 23 | (uri: string, progress: string) => { 24 | console.log("Imagine.loading", uri, "progress", progress); 25 | } 26 | ); 27 | console.log(Imagine); 28 | if (!Imagine) { 29 | console.log("no message"); 30 | return; 31 | } 32 | const Variation = await client.Variation({ 33 | index: 1, 34 | msgId: Imagine.id, 35 | hash: Imagine.hash, 36 | flags: Imagine.flags, 37 | content: prompt, 38 | loading: (uri: string, progress: string) => { 39 | console.log("Variation1.loading", uri, "progress", progress); 40 | }, 41 | }); 42 | console.log("Variation", Variation); 43 | if (!Variation) { 44 | console.log("no Variation"); 45 | return; 46 | } 47 | 48 | const Upscale = await client.Upscale({ 49 | index: 2, 50 | msgId: Variation.id, 51 | hash: Variation.hash, 52 | flags: Variation.flags, 53 | content: prompt, 54 | loading: (uri: string, progress: string) => { 55 | console.log("Upscale.loading", uri, "progress", progress); 56 | }, 57 | }); 58 | console.log("Upscale", Upscale); 59 | // client 60 | // .Variation({ 61 | // index: 2, 62 | // msgId: Imagine.id, 63 | // hash: Imagine.hash, 64 | // flags: Imagine.flags, 65 | // loading: (uri: string, progress: string) => { 66 | // console.log("Variation2.loading", uri, "progress", progress); 67 | // }, 68 | // }) 69 | // .then((msg2) => { 70 | // console.log({ msg2 }); 71 | // }); 72 | // client 73 | // .Variation({ 74 | // index: 3, 75 | // msgId: Imagine.id, 76 | // hash: Imagine.hash, 77 | // flags: Imagine.flags, 78 | // loading: (uri: string, progress: string) => { 79 | // console.log("Variation3.loading", uri, "progress", progress); 80 | // }, 81 | // }) 82 | // .then((msg3) => { 83 | // console.log({ msg3 }); 84 | // }); 85 | // client 86 | // .Variation({ 87 | // index: 4, 88 | // msgId: Imagine.id, 89 | // hash: Imagine.hash, 90 | // flags: Imagine.flags, 91 | // loading: (uri: string, progress: string) => { 92 | // console.log("Variation4.loading", uri, "progress", progress); 93 | // }, 94 | // }) 95 | // .then((msg4) => { 96 | // console.log({ msg4 }); 97 | // }); 98 | } 99 | main().catch((err) => { 100 | console.error(err); 101 | process.exit(1); 102 | }); 103 | -------------------------------------------------------------------------------- /example/variation.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | /** 4 | * 5 | * a simple example of how to use the Variation command 6 | * ``` 7 | * npx tsx example/variation.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | ServerId: process.env.SERVER_ID, 13 | ChannelId: process.env.CHANNEL_ID, 14 | SalaiToken: process.env.SALAI_TOKEN, 15 | Debug: true, 16 | }); 17 | const msg = await client.Imagine( 18 | "crazy donkey pilot, hyperrealism, rembrandt lighting, 32k, volumetric lighting, air, tonal perspective, sharp focus https://media.discordapp.net/attachments/1108515696385720410/1118385339732590682/DanielH_A_giant_hamster_monster._Friendly_in_a_business_suit_si_d4be1836-a4e1-41a8-b1d7-99eebc521220.png?width=1878&height=1878 ", 19 | (uri: string, progress: string) => { 20 | console.log("Imagine.loading", uri, "progress", progress); 21 | } 22 | ); 23 | console.log({ msg }); 24 | if (!msg) { 25 | console.log("no message"); 26 | return; 27 | } 28 | const Variation = await client.Variation({ 29 | index: 1, 30 | msgId: msg.id, 31 | hash: msg.hash, 32 | flags: msg.flags, 33 | content: msg.content, 34 | loading: (uri: string, progress: string) => { 35 | console.log("Variation.loading", uri, "progress", progress); 36 | }, 37 | }); 38 | console.log({ Variation }); 39 | const Variation2 = await client.Variation({ 40 | index: 2, 41 | msgId: msg.id, 42 | hash: msg.hash, 43 | flags: msg.flags, 44 | content: msg.content, 45 | loading: (uri: string, progress: string) => { 46 | console.log("Variation2.loading", uri, "progress", progress); 47 | }, 48 | }); 49 | console.log({ Variation2 }); 50 | } 51 | main().catch((err) => { 52 | console.error(err); 53 | process.exit(1); 54 | }); 55 | -------------------------------------------------------------------------------- /example/vary.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | /** 4 | * 5 | * a simple example of how to use the vary 6 | * ``` 7 | * npx tsx example/vary.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | ServerId: process.env.SERVER_ID, 13 | ChannelId: process.env.CHANNEL_ID, 14 | SalaiToken: process.env.SALAI_TOKEN, 15 | Debug: true, 16 | Ws: true, //enable ws is required for custom zoom 17 | }); 18 | await client.init(); 19 | const prompt = 20 | "Christmas dinner with spaghetti with family in a cozy house, we see interior details , simple blue&white illustration"; 21 | const Imagine = await client.Imagine( 22 | prompt, 23 | (uri: string, progress: string) => { 24 | console.log("loading", uri, "progress", progress); 25 | } 26 | ); 27 | console.log(Imagine); 28 | if (!Imagine) { 29 | console.log("no message"); 30 | return; 31 | } 32 | const Upscale = await client.Upscale({ 33 | index: 2, 34 | msgId: Imagine.id, 35 | hash: Imagine.hash, 36 | flags: Imagine.flags, 37 | loading: (uri: string, progress: string) => { 38 | console.log("loading", uri, "progress", progress); 39 | }, 40 | }); 41 | if (!Upscale) { 42 | console.log("no message"); 43 | return; 44 | } 45 | console.log(Upscale); 46 | 47 | const vary = Upscale?.options?.find((o) => o.label === "Vary (Strong)"); 48 | if (!vary) { 49 | console.log("no zoomout"); 50 | return; 51 | } 52 | const varyCustom = await client.Custom({ 53 | msgId: Upscale.id, 54 | flags: Upscale.flags, 55 | content: `${prompt} --zoom 2`, 56 | customId: vary.custom, 57 | loading: (uri: string, progress: string) => { 58 | console.log("loading", uri, "progress", progress); 59 | }, 60 | }); 61 | console.log("vary (Strong)", varyCustom); 62 | client.Close(); 63 | } 64 | main() 65 | .then(() => { 66 | console.log("done"); 67 | }) 68 | .catch((err) => { 69 | console.error(err); 70 | process.exit(1); 71 | }); 72 | -------------------------------------------------------------------------------- /example/zoomout.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Midjourney } from "../src"; 3 | /** 4 | * 5 | * a simple example of how to use the zoomout with ws command 6 | * ``` 7 | * npx tsx example/zoomout.ts 8 | * ``` 9 | */ 10 | async function main() { 11 | const client = new Midjourney({ 12 | ServerId: process.env.SERVER_ID, 13 | ChannelId: process.env.CHANNEL_ID, 14 | SalaiToken: process.env.SALAI_TOKEN, 15 | Debug: true, 16 | Ws: true, 17 | }); 18 | await client.Connect(); 19 | const Imagine = await client.Imagine("a cool cat, blue ears, yellow hat"); 20 | console.log(Imagine); 21 | if (!Imagine) { 22 | console.log("no message"); 23 | return; 24 | } 25 | const Upscale = await client.Upscale({ 26 | index: 2, 27 | msgId: Imagine.id, 28 | hash: Imagine.hash, 29 | flags: Imagine.flags, 30 | loading: (uri: string, progress: string) => { 31 | console.log("loading", uri, "progress", progress); 32 | }, 33 | }); 34 | if (!Upscale) { 35 | console.log("no message"); 36 | return; 37 | } 38 | console.log(Upscale); 39 | const Zoomout = await client.ZoomOut({ 40 | level: "2x", 41 | msgId: Upscale.id, 42 | hash: Upscale.hash, 43 | flags: Upscale.flags, 44 | loading: (uri: string, progress: string) => { 45 | console.log("loading", uri, "progress", progress); 46 | }, 47 | }); 48 | console.log(Zoomout); 49 | 50 | client.Close(); 51 | } 52 | main().catch((err) => { 53 | console.error(err); 54 | process.exit(1); 55 | }); 56 | -------------------------------------------------------------------------------- /images/ali.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erictik/midjourney-api/9f78a5b9a9e4d699add1b27c5de7d33142f5aca5/images/ali.png -------------------------------------------------------------------------------- /images/wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erictik/midjourney-api/9f78a5b9a9e4d699add1b27c5de7d33142f5aca5/images/wechat.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "midjourney", 3 | "version": "4.0.0", 4 | "description": "Node.js client for the unofficial MidJourney API.", 5 | "main": "libs/index.js", 6 | "types": "libs/index.d.ts", 7 | "source": "./src/index.ts", 8 | "scripts": { 9 | "test": "echo \"Error: no test specified\" && exit 1", 10 | "build": "tsc", 11 | "prettier": "prettier --write .", 12 | "prettier:check": "prettier --check .", 13 | "example:imagine": "tsx ./examples/imagine-ws.ts" 14 | }, 15 | "files": [ 16 | "libs/*", 17 | "src/*", 18 | "*.md" 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/erictik/midjourney-client.git" 23 | }, 24 | "keywords": [ 25 | "midjourney-client", 26 | "midjourney" 27 | ], 28 | "engines": { 29 | "node": ">=18" 30 | }, 31 | "author": "Eric Yang ", 32 | "license": "ISC", 33 | "bugs": { 34 | "url": "https://github.com/erictik/midjourney-client/issues" 35 | }, 36 | "homepage": "https://github.com/erictik/midjourney-client#readme", 37 | "devDependencies": { 38 | "@types/async": "^3.2.20", 39 | "@types/node": "^18.16.0", 40 | "@types/ws": "^8.5.4", 41 | "dotenv": "^16.0.3", 42 | "prettier": "^2.8.8", 43 | "ts-node": "^10.9.1", 44 | "tsx": "^3.12.6", 45 | "typescript": "^5.0.4" 46 | }, 47 | "dependencies": { 48 | "@huggingface/inference": "^2.5.0", 49 | "async": "^3.2.4", 50 | "isomorphic-ws": "^5.0.0", 51 | "semiver": "^1.1.0", 52 | "snowyflake": "^2.0.0", 53 | "tslib": "^2.5.0", 54 | "ws": "^8.13.0" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/banned.words.ts: -------------------------------------------------------------------------------- 1 | // https://tokenizedhq.com/list-of-banned-words-in-midjourney/ 2 | // https://www.greataiprompts.com/imageprompt/list-of-banned-words-in-midjourney/ 3 | export const bannedWords = [ 4 | "hot slut", 5 | "blood", 6 | "twerk", 7 | "making love", 8 | "voluptuous", 9 | "naughty", 10 | "wincest", 11 | "orgy", 12 | "no clothes", 13 | "au naturel", 14 | "no shirt", 15 | "decapitate", 16 | "bare", 17 | "nude", 18 | "barely dressed", 19 | "nude", 20 | "bra", 21 | "risque", 22 | "scantily clad", 23 | "cleavage", 24 | "stripped", 25 | "infested", 26 | "full frontal", 27 | "unclothed", 28 | "invisible clothes", 29 | "wearing nothing", 30 | "lingerie", 31 | "with no shirt", 32 | "naked", 33 | "without clothes on", 34 | "negligee", 35 | "zero clothes", 36 | "gruesome", 37 | "fascist", 38 | "nazi", 39 | "prophet mohammed", 40 | "slave", 41 | "coon", 42 | "honkey", 43 | "cocaine", 44 | "heroin", 45 | "meth", 46 | "crack", 47 | "kill", 48 | "belle delphine", 49 | "hitler", 50 | "jinping", 51 | "lolita", 52 | "president xi", 53 | "torture", 54 | "disturbing", 55 | "farts", 56 | "fart", 57 | "poop", 58 | "infected", 59 | "warts", 60 | "shit", 61 | "brown pudding", 62 | "bunghole", 63 | "vomit", 64 | "voluptuous", 65 | "seductive", 66 | "sperm", 67 | "sexy", 68 | "sadist", 69 | "sensored", 70 | "censored", 71 | "silenced", 72 | "deepfake", 73 | "inappropriate", 74 | "waifu", 75 | "succubus", 76 | "slaughter", 77 | "surgery", 78 | "reproduce", 79 | "crucified", 80 | "seductively", 81 | "explicit", 82 | "inappropriate", 83 | "large bust", 84 | "explicit", 85 | "wang", 86 | "inappropriate", 87 | "teratoma", 88 | "intimate", 89 | "see through", 90 | "tryphophobia", 91 | "bloodbath", 92 | "wound", 93 | "cronenberg", 94 | "khorne", 95 | "cannibal", 96 | "cannibalism", 97 | "visceral", 98 | "guts", 99 | "bloodshot", 100 | "gory", 101 | "killing", 102 | "crucifixion", 103 | "surgery", 104 | "vivisection", 105 | "massacre", 106 | "hemoglobin", 107 | "suicide", 108 | "arse", 109 | "labia", 110 | "ass", 111 | "mammaries", 112 | "badonkers", 113 | "bloody", 114 | "minge", 115 | "big ass", 116 | "mommy milker", 117 | "booba", 118 | "nipple", 119 | "oppai", 120 | "booty", 121 | "organs", 122 | "bosom", 123 | "ovaries", 124 | "flesh", 125 | "breasts", 126 | "penis", 127 | "busty", 128 | "phallus", 129 | "clunge", 130 | "sexy female", 131 | "crotch", 132 | "skimpy", 133 | "dick", 134 | "thick", 135 | "bruises", 136 | "girth", 137 | "titty", 138 | "honkers", 139 | "vagina", 140 | "hooters", 141 | "veiny", 142 | "knob", 143 | "ahegao", 144 | "pinup", 145 | "ballgag", 146 | "car crash", 147 | "playboy", 148 | "bimbo", 149 | "pleasure", 150 | "bodily fluids", 151 | "pleasures", 152 | "boudoir", 153 | "rule34", 154 | "brothel", 155 | "seducing", 156 | "dominatrix", 157 | "corpse", 158 | "seductive", 159 | "erotic", 160 | "seductive", 161 | "fuck", 162 | "sensual", 163 | "hardcore", 164 | "sexy", 165 | "hentai", 166 | "shag", 167 | "horny", 168 | "crucified", 169 | "shibari", 170 | "incest", 171 | "smut", 172 | "jav", 173 | "succubus", 174 | "jerk off king at pic", 175 | "thot", 176 | "kinbaku", 177 | "legs spread", 178 | "sensuality", 179 | "belly button", 180 | "porn", 181 | "patriotic", 182 | "bleed", 183 | "excrement", 184 | "petite", 185 | "seduction", 186 | "mccurry", 187 | "provocative", 188 | "sultry", 189 | "erected", 190 | "camisole", 191 | "tight white", 192 | "arrest", 193 | "see-through", 194 | "feces", 195 | "anus", 196 | "revealing clothing", 197 | "vein", 198 | "loli", 199 | "-edge", 200 | "boobs", 201 | "-backed", 202 | "tied up", 203 | "zedong", 204 | "bathing", 205 | "jail", 206 | "reticulum", 207 | "rear end", 208 | "sakimichan", 209 | "behind bars", 210 | "shirtless", 211 | "sakimichan", 212 | "seductive", 213 | "sexi", 214 | "sexualiz", 215 | "sexual", 216 | ]; 217 | export function detectBannedWords(prompt: string): string[] { 218 | const matches: string[] = []; 219 | bannedWords.forEach((word) => { 220 | const regex = new RegExp(`\\b${word}\\b`, "gi"); 221 | const wordMatches = prompt.match(regex); 222 | if (wordMatches) { 223 | wordMatches.forEach((match) => { 224 | matches.push(match); 225 | }); 226 | } 227 | }); 228 | return matches; 229 | } 230 | -------------------------------------------------------------------------------- /src/command.ts: -------------------------------------------------------------------------------- 1 | import { DiscordImage, MJConfig } from "./interfaces"; 2 | import async from "async"; 3 | import { sleep } from "./utils"; 4 | export const Commands = [ 5 | "ask", 6 | "blend", 7 | "describe", 8 | "fast", 9 | "help", 10 | "imagine", 11 | "info", 12 | "prefer", 13 | "private", 14 | "public", 15 | "relax", 16 | "settings", 17 | "show", 18 | "stealth", 19 | "shorten", 20 | "subscribe", 21 | ] as const; 22 | export type CommandName = (typeof Commands)[number]; 23 | function getCommandName(name: string): CommandName | undefined { 24 | for (const command of Commands) { 25 | if (command === name) { 26 | return command; 27 | } 28 | } 29 | } 30 | 31 | export class Command { 32 | constructor(public config: MJConfig) {} 33 | cache: Partial> = {}; 34 | 35 | async cacheCommand(name: CommandName) { 36 | if (this.cache[name] !== undefined) { 37 | return this.cache[name]; 38 | } 39 | const command = await this.getCommand(name); 40 | console.log("=========", { command }); 41 | this.cache[name] = command; 42 | return command; 43 | this.allCommand(); 44 | return this.cache[name]; 45 | } 46 | async allCommand() { 47 | let serverId = this.config.ServerId; 48 | if (!serverId) { 49 | serverId = this.config.ChannelId; 50 | } 51 | const url = `${this.config.DiscordBaseUrl}/api/v9/guilds/${serverId}/application-command-index`; 52 | const response = await this.safeFetch(url, { 53 | headers: { authorization: this.config.SalaiToken }, 54 | }); 55 | 56 | const data = await response.json(); 57 | if (data?.application_commands) { 58 | data.application_commands.forEach((command: any) => { 59 | const name = getCommandName(command.name); 60 | if (name) { 61 | this.cache[name] = command; 62 | } 63 | }); 64 | } 65 | } 66 | 67 | async getCommand(name: CommandName) { 68 | let serverId = this.config.ServerId; 69 | if (!serverId) { 70 | serverId = this.config.ChannelId; 71 | } 72 | const url = `${this.config.DiscordBaseUrl}/api/v9/guilds/${serverId}/application-command-index`; 73 | const response = await this.safeFetch(url, { 74 | headers: { authorization: this.config.SalaiToken }, 75 | }); 76 | const data = await response.json(); 77 | if (data?.application_commands?.[0]) { 78 | return data.application_commands[0]; 79 | } 80 | throw new Error(`Failed to get application_commands for command ${name}`); 81 | } 82 | private safeFetch(input: RequestInfo | URL, init?: RequestInit | undefined) { 83 | const request = this.config.fetch.bind(this, input, init); 84 | return new Promise((resolve, reject) => { 85 | this.fetchQueue.push( 86 | { 87 | request, 88 | callback: (res: Response) => { 89 | resolve(res); 90 | }, 91 | }, 92 | (error: any, result: any) => { 93 | if (error) { 94 | reject(error); 95 | } else { 96 | resolve(result); 97 | } 98 | } 99 | ); 100 | }); 101 | } 102 | private async processFetchRequest({ 103 | request, 104 | callback, 105 | }: { 106 | request: () => Promise; 107 | callback: (res: Response) => void; 108 | }) { 109 | const res = await request(); 110 | callback(res); 111 | await sleep(1000 * 4); 112 | } 113 | private fetchQueue = async.queue(this.processFetchRequest, 1); 114 | 115 | async imaginePayload(prompt: string, nonce?: string) { 116 | const data = await this.commandData("imagine", [ 117 | { 118 | type: 3, 119 | name: "prompt", 120 | value: prompt, 121 | }, 122 | ]); 123 | return this.data2Paylod(data, nonce); 124 | } 125 | async PreferPayload(nonce?: string) { 126 | const data = await this.commandData("prefer", [ 127 | { 128 | type: 1, 129 | name: "remix", 130 | options: [], 131 | }, 132 | ]); 133 | return this.data2Paylod(data, nonce); 134 | } 135 | 136 | async shortenPayload(prompt: string, nonce?: string) { 137 | const data = await this.commandData("shorten", [ 138 | { 139 | type: 3, 140 | name: "prompt", 141 | value: prompt, 142 | }, 143 | ]); 144 | return this.data2Paylod(data, nonce); 145 | } 146 | async infoPayload(nonce?: string) { 147 | const data = await this.commandData("info"); 148 | return this.data2Paylod(data, nonce); 149 | } 150 | async fastPayload(nonce?: string) { 151 | const data = await this.commandData("fast"); 152 | return this.data2Paylod(data, nonce); 153 | } 154 | async relaxPayload(nonce?: string) { 155 | const data = await this.commandData("relax"); 156 | return this.data2Paylod(data, nonce); 157 | } 158 | async settingsPayload(nonce?: string) { 159 | const data = await this.commandData("settings"); 160 | return this.data2Paylod(data, nonce); 161 | } 162 | async describePayload(image: DiscordImage, nonce?: string) { 163 | const data = await this.commandData( 164 | "describe", 165 | [ 166 | { 167 | type: 11, 168 | name: "image", 169 | value: image.id, 170 | }, 171 | ], 172 | [ 173 | { 174 | id: image.id, 175 | filename: image.filename, 176 | uploaded_filename: image.upload_filename, 177 | }, 178 | ] 179 | ); 180 | return this.data2Paylod(data, nonce); 181 | } 182 | 183 | protected async commandData( 184 | name: CommandName, 185 | options: any[] = [], 186 | attachments: any[] = [] 187 | ) { 188 | const command = await this.cacheCommand(name); 189 | const data = { 190 | version: command.version, 191 | id: command.id, 192 | name: command.name, 193 | type: command.type, 194 | options, 195 | application_command: command, 196 | attachments, 197 | }; 198 | return data; 199 | } 200 | //TODO data type 201 | protected data2Paylod(data: any, nonce?: string) { 202 | const payload = { 203 | type: 2, 204 | application_id: data.application_command.application_id, 205 | guild_id: this.config.ServerId, 206 | channel_id: this.config.ChannelId, 207 | session_id: this.config.SessionId, 208 | nonce, 209 | data, 210 | }; 211 | return payload; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/discord.message.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DefaultMJConfig, 3 | LoadingHandler, 4 | MJMessage, 5 | MJConfig, 6 | MJConfigParam, 7 | } from "./interfaces"; 8 | import { formatOptions, sleep } from "./utils"; 9 | import async from "async"; 10 | 11 | export class MidjourneyMessage { 12 | public config: MJConfig; 13 | constructor(defaults: MJConfigParam) { 14 | const { SalaiToken } = defaults; 15 | if (!SalaiToken) { 16 | throw new Error("SalaiToken are required"); 17 | } 18 | this.config = { 19 | ...DefaultMJConfig, 20 | ...defaults, 21 | }; 22 | } 23 | private safeRetrieveMessages = (request = 50) => { 24 | return new Promise((resolve, reject) => { 25 | this.queue.push( 26 | { 27 | request, 28 | callback: (any: any) => { 29 | resolve(any); 30 | }, 31 | }, 32 | (error: any, result: any) => { 33 | if (error) { 34 | reject(error); 35 | } else { 36 | resolve(result); 37 | } 38 | } 39 | ); 40 | }); 41 | }; 42 | private processRequest = async ({ 43 | request, 44 | callback, 45 | }: { 46 | request: any; 47 | callback: (any: any) => void; 48 | }) => { 49 | const httpStatus = await this.RetrieveMessages(request); 50 | callback(httpStatus); 51 | await sleep(this.config.ApiInterval); 52 | }; 53 | private queue = async.queue(this.processRequest, 1); 54 | 55 | protected log(...args: any[]) { 56 | this.config.Debug && console.log(...args, new Date().toISOString()); 57 | } 58 | async FilterMessages( 59 | timestamp: number, 60 | prompt: string, 61 | loading?: LoadingHandler 62 | ) { 63 | const seed = prompt.match(/\[(.*?)\]/)?.[1]; 64 | this.log(`seed:`, seed); 65 | const data = await this.safeRetrieveMessages(this.config.Limit); 66 | for (let i = 0; i < data.length; i++) { 67 | const item = data[i]; 68 | if ( 69 | item.author.id === this.config.BotId && 70 | item.content.includes(`${seed}`) 71 | ) { 72 | const itemTimestamp = new Date(item.timestamp).getTime(); 73 | if (itemTimestamp < timestamp) { 74 | this.log("old message"); 75 | continue; 76 | } 77 | if (item.attachments.length === 0) { 78 | this.log("no attachment"); 79 | break; 80 | } 81 | let uri = item.attachments[0].url; 82 | if (this.config.ImageProxy !== "") { 83 | uri = uri.replace( 84 | "https://cdn.discordapp.com/", 85 | this.config.ImageProxy 86 | ); 87 | } //waiting 88 | if ( 89 | item.attachments[0].filename.startsWith("grid") || 90 | item.components.length === 0 91 | ) { 92 | this.log(`content`, item.content); 93 | const progress = this.content2progress(item.content); 94 | loading?.(uri, progress); 95 | break; 96 | } 97 | //finished 98 | const content = item.content.split("**")[1]; 99 | const { proxy_url, width, height } = item.attachments[0]; 100 | const msg: MJMessage = { 101 | content, 102 | id: item.id, 103 | uri, 104 | proxy_url, 105 | flags: item.flags, 106 | hash: this.UriToHash(uri), 107 | progress: "done", 108 | options: formatOptions(item.components), 109 | width, 110 | height, 111 | }; 112 | return msg; 113 | } 114 | } 115 | return null; 116 | } 117 | protected content2progress(content: string) { 118 | const spcon = content.split("**"); 119 | if (spcon.length < 3) { 120 | return ""; 121 | } 122 | content = spcon[2]; 123 | const regex = /\(([^)]+)\)/; // matches the value inside the first parenthesis 124 | const match = content.match(regex); 125 | let progress = ""; 126 | if (match) { 127 | progress = match[1]; 128 | } 129 | return progress; 130 | } 131 | UriToHash(uri: string) { 132 | return uri.split("_").pop()?.split(".")[0] ?? ""; 133 | } 134 | async WaitMessage( 135 | prompt: string, 136 | loading?: LoadingHandler, 137 | timestamp?: number 138 | ) { 139 | timestamp = timestamp ?? Date.now(); 140 | for (let i = 0; i < this.config.MaxWait; i++) { 141 | const msg = await this.FilterMessages(timestamp, prompt, loading); 142 | if (msg !== null) { 143 | return msg; 144 | } 145 | this.log(i, "wait no message found"); 146 | await sleep(1000 * 2); 147 | } 148 | return null; 149 | } 150 | 151 | async RetrieveMessages(limit = this.config.Limit) { 152 | const headers = { 153 | "Content-Type": "application/json", 154 | Authorization: this.config.SalaiToken, 155 | }; 156 | const response = await this.config.fetch( 157 | `${this.config.DiscordBaseUrl}/api/v10/channels/${this.config.ChannelId}/messages?limit=${limit}`, 158 | { 159 | headers, 160 | } 161 | ); 162 | if (!response.ok) { 163 | this.log("error config", { config: this.config }); 164 | this.log(`HTTP error! status: ${response.status}`); 165 | } 166 | const data: any = await response.json(); 167 | return data; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/discord.ws.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MJConfig, 3 | WaitMjEvent, 4 | MJMessage, 5 | LoadingHandler, 6 | MJEmit, 7 | MJInfo, 8 | MJSettings, 9 | MJOptions, 10 | OnModal, 11 | MJShorten, 12 | MJDescribe, 13 | } from "./interfaces"; 14 | import { MidjourneyApi } from "./midjourney.api"; 15 | import { 16 | content2progress, 17 | content2prompt, 18 | formatInfo, 19 | formatOptions, 20 | formatPrompts, 21 | nextNonce, 22 | uriToHash, 23 | } from "./utils"; 24 | import { VerifyHuman } from "./verify.human"; 25 | import WebSocket from "isomorphic-ws"; 26 | export class WsMessage { 27 | ws: WebSocket; 28 | private closed = false; 29 | private event: Array<{ event: string; callback: (message: any) => void }> = 30 | []; 31 | private waitMjEvents: Map = new Map(); 32 | private skipMessageId: string[] = []; 33 | private reconnectTime: boolean[] = []; 34 | private heartbeatInterval = 0; 35 | public UserId = ""; 36 | constructor(public config: MJConfig, public MJApi: MidjourneyApi) { 37 | this.ws = new this.config.WebSocket(this.config.WsBaseUrl); 38 | this.ws.addEventListener("open", this.open.bind(this)); 39 | this.onSystem("messageCreate", this.onMessageCreate.bind(this)); 40 | this.onSystem("messageUpdate", this.onMessageUpdate.bind(this)); 41 | this.onSystem("messageDelete", this.onMessageDelete.bind(this)); 42 | this.onSystem("ready", this.onReady.bind(this)); 43 | this.onSystem("interactionSuccess", this.onInteractionSuccess.bind(this)); 44 | } 45 | 46 | private async heartbeat(num: number) { 47 | if (this.reconnectTime[num]) return; 48 | //check if ws is closed 49 | if (this.closed) return; 50 | if (this.ws.readyState !== this.ws.OPEN) { 51 | this.reconnect(); 52 | return; 53 | } 54 | this.log("heartbeat", this.heartbeatInterval); 55 | this.heartbeatInterval++; 56 | this.ws.send( 57 | JSON.stringify({ 58 | op: 1, 59 | d: this.heartbeatInterval, 60 | }) 61 | ); 62 | await this.timeout(1000 * 40); 63 | this.heartbeat(num); 64 | } 65 | close() { 66 | this.closed = true; 67 | this.ws.close(); 68 | } 69 | async checkWs() { 70 | if (this.closed) return; 71 | if (this.ws.readyState !== this.ws.OPEN) { 72 | this.reconnect(); 73 | await this.onceReady(); 74 | } 75 | } 76 | 77 | async onceReady() { 78 | return new Promise((resolve) => { 79 | this.once("ready", (user) => { 80 | //print user nickname 81 | console.log(`🎊 ws ready!!! Hi: ${user.global_name}`); 82 | resolve(this); 83 | }); 84 | }); 85 | } 86 | //try reconnect 87 | reconnect() { 88 | if (this.closed) return; 89 | this.ws = new this.config.WebSocket(this.config.WsBaseUrl); 90 | this.heartbeatInterval = 0; 91 | this.ws.addEventListener("open", this.open.bind(this)); 92 | } 93 | // After opening ws 94 | private async open() { 95 | const num = this.reconnectTime.length; 96 | this.log("open.time", num); 97 | this.reconnectTime.push(false); 98 | this.auth(); 99 | this.ws.addEventListener("message", (event) => { 100 | this.parseMessage(event.data as string); 101 | }); 102 | this.ws.addEventListener("error", (event) => { 103 | this.reconnectTime[num] = true; 104 | this.reconnect(); 105 | }); 106 | this.ws.addEventListener("close", (event) => { 107 | this.reconnectTime[num] = true; 108 | this.reconnect(); 109 | }); 110 | setTimeout(() => { 111 | this.heartbeat(num); 112 | }, 1000 * 10); 113 | } 114 | // auth 115 | private auth() { 116 | this.ws.send( 117 | JSON.stringify({ 118 | op: 2, 119 | d: { 120 | token: this.config.SalaiToken, 121 | capabilities: 8189, 122 | properties: { 123 | os: "Mac OS X", 124 | browser: "Chrome", 125 | device: "", 126 | }, 127 | compress: false, 128 | }, 129 | }) 130 | ); 131 | } 132 | async timeout(ms: number) { 133 | return new Promise((resolve) => setTimeout(resolve, ms)); 134 | } 135 | private async messageCreate(message: any) { 136 | const { embeds, id, nonce, components, attachments } = message; 137 | if (nonce) { 138 | // this.log("waiting start image or info or error"); 139 | this.updateMjEventIdByNonce(id, nonce); 140 | if (embeds?.[0]) { 141 | const { color, description, title } = embeds[0]; 142 | this.log("embeds[0].color", color); 143 | switch (color) { 144 | case 16711680: //error 145 | if (title == "Action needed to continue") { 146 | return this.continue(message); 147 | } else if (title == "Pending mod message") { 148 | return this.continue(message); 149 | } 150 | 151 | const error = new Error(description); 152 | this.EventError(id, error); 153 | return; 154 | 155 | case 16776960: //warning 156 | console.warn(description); 157 | break; 158 | 159 | default: 160 | if ( 161 | title?.includes("continue") && 162 | description?.includes("verify you're human") 163 | ) { 164 | //verify human 165 | await this.verifyHuman(message); 166 | return; 167 | } 168 | 169 | if (title?.includes("Invalid")) { 170 | //error 171 | const error = new Error(description); 172 | this.EventError(id, error); 173 | return; 174 | } 175 | } 176 | } 177 | } 178 | 179 | if (attachments?.length > 0 && components?.length > 0) { 180 | this.done(message); 181 | return; 182 | } 183 | 184 | this.messageUpdate(message); 185 | } 186 | 187 | private messageUpdate(message: any) { 188 | // this.log("messageUpdate", message); 189 | const { 190 | content, 191 | embeds, 192 | interaction = {}, 193 | nonce, 194 | id, 195 | components, 196 | } = message; 197 | 198 | if (!nonce) { 199 | const { name } = interaction; 200 | 201 | switch (name) { 202 | case "settings": 203 | this.emit("settings", message); 204 | return; 205 | case "describe": 206 | let uri = embeds?.[0]?.image?.url; 207 | if (this.config.ImageProxy !== "") { 208 | uri = uri.replace( 209 | "https://cdn.discordapp.com/", 210 | this.config.ImageProxy 211 | ); 212 | } 213 | const describe: MJDescribe = { 214 | id: id, 215 | flags: message.flags, 216 | descriptions: embeds?.[0]?.description.split("\n\n"), 217 | uri: uri, 218 | proxy_url: embeds?.[0]?.image?.proxy_url, 219 | options: formatOptions(components), 220 | }; 221 | this.emitMJ(id, describe); 222 | break; 223 | case "prefer remix": 224 | if (content != "") { 225 | this.emit("prefer-remix", content); 226 | } 227 | break; 228 | case "shorten": 229 | const shorten: MJShorten = { 230 | description: embeds?.[0]?.description, 231 | prompts: formatPrompts(embeds?.[0]?.description as string), 232 | options: formatOptions(components), 233 | id, 234 | flags: message.flags, 235 | }; 236 | this.emitMJ(id, shorten); 237 | break; 238 | case "info": 239 | this.emit("info", embeds?.[0]?.description); 240 | return; 241 | } 242 | } 243 | if (embeds?.[0]) { 244 | var { description, title } = embeds[0]; 245 | if (title === "Duplicate images detected") { 246 | const error = new Error(description); 247 | this.EventError(id, error); 248 | return; 249 | } 250 | } 251 | 252 | if (content) { 253 | this.processingImage(message); 254 | } 255 | } 256 | 257 | //interaction success 258 | private async onInteractionSuccess({ 259 | nonce, 260 | id, 261 | }: { 262 | nonce: string; 263 | id: string; 264 | }) { 265 | // this.log("interactionSuccess", nonce, id); 266 | const event = this.getEventByNonce(nonce); 267 | if (!event) { 268 | return; 269 | } 270 | event.onmodal && event.onmodal(nonce, id); 271 | } 272 | private async onReady(user: any) { 273 | this.UserId = user.id; 274 | } 275 | private async onMessageCreate(message: any) { 276 | const { channel_id, author, interaction } = message; 277 | if (channel_id !== this.config.ChannelId) return; 278 | if (author?.id !== this.config.BotId) return; 279 | if (interaction && interaction.user.id !== this.UserId) return; 280 | // this.log("[messageCreate]", JSON.stringify(message)); 281 | this.messageCreate(message); 282 | } 283 | 284 | private async onMessageUpdate(message: any) { 285 | const { channel_id, author, interaction } = message; 286 | if (channel_id !== this.config.ChannelId) return; 287 | if (author?.id !== this.config.BotId) return; 288 | if (interaction && interaction.user.id !== this.UserId) return; 289 | // this.log("[messageUpdate]", JSON.stringify(message)); 290 | this.messageUpdate(message); 291 | } 292 | private async onMessageDelete(message: any) { 293 | const { channel_id, id } = message; 294 | if (channel_id !== this.config.ChannelId) return; 295 | for (const [key, value] of this.waitMjEvents.entries()) { 296 | if (value.id === id) { 297 | this.waitMjEvents.set(key, { ...value, del: true }); 298 | } 299 | } 300 | } 301 | 302 | // parse message from ws 303 | private parseMessage(data: string) { 304 | const msg = JSON.parse(data); 305 | if (!msg.t) { 306 | return; 307 | } 308 | const message = msg.d; 309 | if (message.channel_id === this.config.ChannelId) { 310 | this.log(data); 311 | } 312 | this.log("event", msg.t); 313 | // console.log(data); 314 | switch (msg.t) { 315 | case "READY": 316 | this.emitSystem("ready", message.user); 317 | break; 318 | case "MESSAGE_CREATE": 319 | this.emitSystem("messageCreate", message); 320 | break; 321 | case "MESSAGE_UPDATE": 322 | this.emitSystem("messageUpdate", message); 323 | break; 324 | case "MESSAGE_DELETE": 325 | this.emitSystem("messageDelete", message); 326 | case "INTERACTION_SUCCESS": 327 | if (message.nonce) { 328 | this.emitSystem("interactionSuccess", message); 329 | } 330 | break; 331 | case "INTERACTION_CREATE": 332 | if (message.nonce) { 333 | this.emitSystem("interactionCreate", message); 334 | } 335 | } 336 | } 337 | //continue click appeal or Acknowledged 338 | private async continue(message: any) { 339 | const { components, id, flags, nonce } = message; 340 | const appeal = components[0]?.components[0]; 341 | this.log("appeal", appeal); 342 | if (appeal) { 343 | var newnonce = nextNonce(); 344 | const httpStatus = await this.MJApi.CustomApi({ 345 | msgId: id, 346 | customId: appeal.custom_id, 347 | flags, 348 | nonce: newnonce, 349 | }); 350 | this.log("appeal.httpStatus", httpStatus); 351 | if (httpStatus == 204) { 352 | //todo 353 | this.on(newnonce, (data) => { 354 | this.emit(nonce, data); 355 | }); 356 | } 357 | } 358 | } 359 | private async verifyHuman(message: any) { 360 | const { HuggingFaceToken } = this.config; 361 | if (HuggingFaceToken === "" || !HuggingFaceToken) { 362 | this.log("HuggingFaceToken is empty"); 363 | return; 364 | } 365 | const { embeds, components, id, flags, nonce } = message; 366 | const uri = embeds[0].image.url; 367 | const categories = components[0].components; 368 | const classify = categories.map((c: any) => c.label); 369 | const verifyClient = new VerifyHuman(this.config); 370 | const category = await verifyClient.verify(uri, classify); 371 | if (category) { 372 | const custom_id = categories.find( 373 | (c: any) => c.label === category 374 | ).custom_id; 375 | var newnonce = nextNonce(); 376 | const httpStatus = await this.MJApi.CustomApi({ 377 | msgId: id, 378 | customId: custom_id, 379 | flags, 380 | nonce: newnonce, 381 | }); 382 | if (httpStatus == 204) { 383 | this.on(newnonce, (data) => { 384 | this.emit(nonce, data); 385 | }); 386 | } 387 | this.log("verifyHumanApi", httpStatus, custom_id, message.id); 388 | } 389 | } 390 | private EventError(id: string, error: Error) { 391 | const event = this.getEventById(id); 392 | if (!event) { 393 | return; 394 | } 395 | const eventMsg: MJEmit = { 396 | error, 397 | }; 398 | this.emit(event.nonce, eventMsg); 399 | } 400 | 401 | private done(message: any) { 402 | const { content, id, attachments, components, flags } = message; 403 | const { url, proxy_url, width, height } = attachments[0]; 404 | let uri = url; 405 | if (this.config.ImageProxy !== "") { 406 | uri = uri.replace("https://cdn.discordapp.com/", this.config.ImageProxy); 407 | } 408 | 409 | const MJmsg: MJMessage = { 410 | id, 411 | flags, 412 | content, 413 | hash: uriToHash(url), 414 | progress: "done", 415 | uri, 416 | proxy_url, 417 | options: formatOptions(components), 418 | width, 419 | height, 420 | }; 421 | this.filterMessages(MJmsg); 422 | return; 423 | } 424 | private processingImage(message: any) { 425 | const { content, id, attachments, flags } = message; 426 | if (!content) { 427 | return; 428 | } 429 | const event = this.getEventById(id); 430 | if (!event) { 431 | return; 432 | } 433 | event.prompt = content; 434 | //not image 435 | if (!attachments || attachments.length === 0) { 436 | return; 437 | } 438 | 439 | let uri = attachments[0].url; 440 | if (this.config.ImageProxy !== "") { 441 | uri = uri.replace("https://cdn.discordapp.com/", this.config.ImageProxy); 442 | } 443 | const MJmsg: MJMessage = { 444 | uri: uri, 445 | proxy_url: attachments[0].proxy_url, 446 | content: content, 447 | flags: flags, 448 | progress: content2progress(content), 449 | }; 450 | const eventMsg: MJEmit = { 451 | message: MJmsg, 452 | }; 453 | this.emitImage(event.nonce, eventMsg); 454 | } 455 | 456 | private async filterMessages(MJmsg: MJMessage) { 457 | // delay 300ms for discord message delete 458 | await this.timeout(300); 459 | const event = this.getEventByContent(MJmsg.content); 460 | if (!event) { 461 | this.log("FilterMessages not found", MJmsg, this.waitMjEvents); 462 | return; 463 | } 464 | const eventMsg: MJEmit = { 465 | message: MJmsg, 466 | }; 467 | this.emitImage(event.nonce, eventMsg); 468 | } 469 | private getEventByContent(content: string) { 470 | const prompt = content2prompt(content); 471 | //fist del message 472 | for (const [key, value] of this.waitMjEvents.entries()) { 473 | if ( 474 | value.del === true && 475 | prompt === content2prompt(value.prompt as string) 476 | ) { 477 | return value; 478 | } 479 | } 480 | 481 | for (const [key, value] of this.waitMjEvents.entries()) { 482 | if (prompt === content2prompt(value.prompt as string)) { 483 | return value; 484 | } 485 | } 486 | } 487 | 488 | private getEventById(id: string) { 489 | for (const [key, value] of this.waitMjEvents.entries()) { 490 | if (value.id === id) { 491 | return value; 492 | } 493 | } 494 | } 495 | private getEventByNonce(nonce: string) { 496 | for (const [key, value] of this.waitMjEvents.entries()) { 497 | if (value.nonce === nonce) { 498 | return value; 499 | } 500 | } 501 | } 502 | private updateMjEventIdByNonce(id: string, nonce: string) { 503 | if (nonce === "" || id === "") return; 504 | let event = this.waitMjEvents.get(nonce); 505 | if (!event) return; 506 | event.id = id; 507 | this.log("updateMjEventIdByNonce success", this.waitMjEvents.get(nonce)); 508 | } 509 | 510 | protected async log(...args: any[]) { 511 | this.config.Debug && console.info(...args, new Date().toISOString()); 512 | } 513 | 514 | emit(event: string, message: any) { 515 | this.event 516 | .filter((e) => e.event === event) 517 | .forEach((e) => e.callback(message)); 518 | } 519 | private emitImage(type: string, message: MJEmit) { 520 | this.emit(type, message); 521 | } 522 | //FIXME: emitMJ rename 523 | private emitMJ(id: string, data: any) { 524 | const event = this.getEventById(id); 525 | if (!event) return; 526 | this.emit(event.nonce, data); 527 | } 528 | 529 | on(event: string, callback: (message: any) => void) { 530 | this.event.push({ event, callback }); 531 | } 532 | onSystem( 533 | event: 534 | | "ready" 535 | | "messageCreate" 536 | | "messageUpdate" 537 | | "messageDelete" 538 | | "interactionCreate" 539 | | "interactionSuccess", 540 | callback: (message: any) => void 541 | ) { 542 | this.on(event, callback); 543 | } 544 | private emitSystem( 545 | type: 546 | | "ready" 547 | | "messageCreate" 548 | | "messageUpdate" 549 | | "messageDelete" 550 | | "interactionSuccess" 551 | | "interactionCreate", 552 | message: MJEmit 553 | ) { 554 | this.emit(type, message); 555 | } 556 | once(event: string, callback: (message: any) => void) { 557 | const once = (message: any) => { 558 | this.remove(event, once); 559 | callback(message); 560 | }; 561 | this.event.push({ event, callback: once }); 562 | } 563 | remove(event: string, callback: (message: any) => void) { 564 | this.event = this.event.filter( 565 | (e) => e.event !== event && e.callback !== callback 566 | ); 567 | } 568 | removeEvent(event: string) { 569 | this.event = this.event.filter((e) => e.event !== event); 570 | } 571 | //FIXME: USE ONCE 572 | onceInfo(callback: (message: any) => void) { 573 | const once = (message: any) => { 574 | this.remove("info", once); 575 | callback(message); 576 | }; 577 | this.event.push({ event: "info", callback: once }); 578 | } 579 | //FIXME: USE ONCE 580 | onceSettings(callback: (message: any) => void) { 581 | const once = (message: any) => { 582 | this.remove("settings", once); 583 | callback(message); 584 | }; 585 | this.event.push({ event: "settings", callback: once }); 586 | } 587 | onceMJ(nonce: string, callback: (data: any) => void) { 588 | const once = (message: any) => { 589 | this.remove(nonce, once); 590 | //FIXME: removeWaitMjEvent 591 | this.removeWaitMjEvent(nonce); 592 | callback(message); 593 | }; 594 | //FIXME: addWaitMjEvent 595 | this.waitMjEvents.set(nonce, { nonce }); 596 | this.event.push({ event: nonce, callback: once }); 597 | } 598 | private removeSkipMessageId(messageId: string) { 599 | const index = this.skipMessageId.findIndex((id) => id !== messageId); 600 | if (index !== -1) { 601 | this.skipMessageId.splice(index, 1); 602 | } 603 | } 604 | private removeWaitMjEvent(nonce: string) { 605 | this.waitMjEvents.delete(nonce); 606 | } 607 | onceImage(nonce: string, callback: (data: MJEmit) => void) { 608 | const once = (data: MJEmit) => { 609 | const { message, error } = data; 610 | if (error || (message && message.progress === "done")) { 611 | this.remove(nonce, once); 612 | } 613 | callback(data); 614 | }; 615 | this.event.push({ event: nonce, callback: once }); 616 | } 617 | 618 | async waitImageMessage({ 619 | nonce, 620 | prompt, 621 | onmodal, 622 | messageId, 623 | loading, 624 | }: { 625 | nonce: string; 626 | prompt?: string; 627 | messageId?: string; 628 | onmodal?: OnModal; 629 | loading?: LoadingHandler; 630 | }) { 631 | if (messageId) this.skipMessageId.push(messageId); 632 | return new Promise((resolve, reject) => { 633 | const handleImageMessage = ({ message, error }: MJEmit) => { 634 | if (error) { 635 | this.removeWaitMjEvent(nonce); 636 | reject(error); 637 | return; 638 | } 639 | if (message && message.progress === "done") { 640 | this.removeWaitMjEvent(nonce); 641 | messageId && this.removeSkipMessageId(messageId); 642 | resolve(message); 643 | return; 644 | } 645 | message && loading && loading(message.uri, message.progress || ""); 646 | }; 647 | this.waitMjEvents.set(nonce, { 648 | nonce, 649 | prompt, 650 | onmodal: async (oldnonce, id) => { 651 | if (onmodal === undefined) { 652 | // reject(new Error("onmodal is not defined")) 653 | return ""; 654 | } 655 | var nonce = await onmodal(oldnonce, id); 656 | if (nonce === "") { 657 | // reject(new Error("onmodal return empty nonce")) 658 | return ""; 659 | } 660 | this.removeWaitMjEvent(oldnonce); 661 | this.waitMjEvents.set(nonce, { nonce }); 662 | this.onceImage(nonce, handleImageMessage); 663 | return nonce; 664 | }, 665 | }); 666 | this.onceImage(nonce, handleImageMessage); 667 | }); 668 | } 669 | async waitDescribe(nonce: string) { 670 | return new Promise((resolve) => { 671 | this.onceMJ(nonce, (message) => { 672 | resolve(message); 673 | }); 674 | }); 675 | } 676 | async waitShorten(nonce: string) { 677 | return new Promise((resolve) => { 678 | this.onceMJ(nonce, (message) => { 679 | resolve(message); 680 | }); 681 | }); 682 | } 683 | async waitContent(event: string) { 684 | return new Promise((resolve) => { 685 | this.once(event, (message) => { 686 | resolve(message); 687 | }); 688 | }); 689 | } 690 | async waitInfo() { 691 | return new Promise((resolve, reject) => { 692 | this.onceInfo((message) => { 693 | resolve(formatInfo(message)); 694 | }); 695 | }); 696 | } 697 | async waitSettings() { 698 | return new Promise((resolve, reject) => { 699 | this.onceSettings((message) => { 700 | resolve({ 701 | id: message.id, 702 | flags: message.flags, 703 | content: message, 704 | options: formatOptions(message.components), 705 | }); 706 | }); 707 | }); 708 | } 709 | } 710 | -------------------------------------------------------------------------------- /src/face.swap.ts: -------------------------------------------------------------------------------- 1 | import { client } from "./gradio/index"; 2 | 3 | export class faceSwap { 4 | public hf_token?: string; 5 | constructor(hf_token?: string) { 6 | this.hf_token = hf_token; 7 | } 8 | async changeFace(Target: Blob, Source: Blob) { 9 | const app = await client("https://felixrosberg-face-swap.hf.space/", { 10 | hf_token: this.hf_token as any, 11 | }); 12 | // console.log("app", app); 13 | const result: any = await app.predict(1, [ 14 | Target, // blob in 'Target' Image component 15 | Source, // blob in 'Source' Image component 16 | 0, // number (numeric value between 0 and 100) in 'Anonymization ratio (%)' Slider component 17 | 0, // number (numeric value between 0 and 100) in 'Adversarial defense ratio (%)' Slider component 18 | "Compare", // string[] (array of strings) in 'Mode' Checkboxgroup component 19 | ]); 20 | // result.data; 21 | return result.data; 22 | // console.log(result.data[0]); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/gradio/globals.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Window { 3 | __gradio_mode__: "app" | "website"; 4 | launchGradio: Function; 5 | launchGradioFromSpaces: Function; 6 | gradio_config: Config; 7 | scoped_css_attach: (link: HTMLLinkElement) => void; 8 | __is_colab__: boolean; 9 | } 10 | } 11 | 12 | export interface Config { 13 | auth_required: boolean | undefined; 14 | auth_message: string; 15 | components: any[]; 16 | css: string | null; 17 | dependencies: any[]; 18 | dev_mode: boolean; 19 | enable_queue: boolean; 20 | layout: any; 21 | mode: "blocks" | "interface"; 22 | root: string; 23 | theme: string; 24 | title: string; 25 | version: string; 26 | space_id: string | null; 27 | is_colab: boolean; 28 | show_api: boolean; 29 | stylesheets: string[]; 30 | path: string; 31 | } 32 | -------------------------------------------------------------------------------- /src/gradio/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | client, 3 | post_data, 4 | upload_files, 5 | duplicate, 6 | api_factory, 7 | } from "./client"; 8 | export type { SpaceStatus } from "./types"; 9 | -------------------------------------------------------------------------------- /src/gradio/types.ts: -------------------------------------------------------------------------------- 1 | export interface Config { 2 | auth_required: boolean | undefined; 3 | auth_message: string; 4 | components: any[]; 5 | css: string | null; 6 | dependencies: any[]; 7 | dev_mode: boolean; 8 | enable_queue: boolean; 9 | layout: any; 10 | mode: "blocks" | "interface"; 11 | root: string; 12 | root_url?: string; 13 | theme: string; 14 | title: string; 15 | version: string; 16 | space_id: string | null; 17 | is_colab: boolean; 18 | show_api: boolean; 19 | stylesheets: string[]; 20 | path: string; 21 | } 22 | 23 | export interface Payload { 24 | data: Array; 25 | fn_index?: number; 26 | event_data?: unknown; 27 | time?: Date; 28 | } 29 | 30 | export interface PostResponse { 31 | error?: string; 32 | [x: string]: any; 33 | } 34 | export interface UploadResponse { 35 | error?: string; 36 | files?: Array; 37 | } 38 | 39 | export interface Status { 40 | queue: boolean; 41 | code?: string; 42 | success?: boolean; 43 | stage: "pending" | "error" | "complete" | "generating"; 44 | size?: number; 45 | position?: number; 46 | eta?: number; 47 | message?: string; 48 | progress_data?: Array<{ 49 | progress: number | null; 50 | index: number | null; 51 | length: number | null; 52 | unit: string | null; 53 | desc: string | null; 54 | }>; 55 | time?: Date; 56 | } 57 | 58 | export interface SpaceStatusNormal { 59 | status: "sleeping" | "running" | "building" | "error" | "stopped"; 60 | detail: 61 | | "SLEEPING" 62 | | "RUNNING" 63 | | "RUNNING_BUILDING" 64 | | "BUILDING" 65 | | "NOT_FOUND"; 66 | load_status: "pending" | "error" | "complete" | "generating"; 67 | message: string; 68 | } 69 | export interface SpaceStatusError { 70 | status: "space_error" | "paused"; 71 | detail: 72 | | "NO_APP_FILE" 73 | | "CONFIG_ERROR" 74 | | "BUILD_ERROR" 75 | | "RUNTIME_ERROR" 76 | | "PAUSED"; 77 | load_status: "error"; 78 | message: string; 79 | discussions_enabled: boolean; 80 | } 81 | export type SpaceStatus = SpaceStatusNormal | SpaceStatusError; 82 | 83 | export type status_callback_function = (a: Status) => void; 84 | export type SpaceStatusCallback = (a: SpaceStatus) => void; 85 | 86 | export type EventType = "data" | "status"; 87 | 88 | export interface EventMap { 89 | data: Payload; 90 | status: Status; 91 | } 92 | 93 | export type Event = { 94 | [P in K]: EventMap[P] & { type: P; endpoint: string; fn_index: number }; 95 | }[K]; 96 | export type EventListener = (event: Event) => void; 97 | export type ListenerMap = { 98 | [P in K]?: EventListener[]; 99 | }; 100 | export interface FileData { 101 | name: string; 102 | orig_name?: string; 103 | size?: number; 104 | data: string; 105 | blob?: File; 106 | is_file?: boolean; 107 | mime_type?: string; 108 | alt_text?: string; 109 | } 110 | -------------------------------------------------------------------------------- /src/gradio/utils.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "./types"; 2 | 3 | export function determine_protocol(endpoint: string): { 4 | ws_protocol: "ws" | "wss"; 5 | http_protocol: "http:" | "https:"; 6 | host: string; 7 | } { 8 | if (endpoint.startsWith("http")) { 9 | const { protocol, host } = new URL(endpoint); 10 | 11 | if (host.endsWith("hf.space")) { 12 | return { 13 | ws_protocol: "wss", 14 | host: host, 15 | http_protocol: protocol as "http:" | "https:", 16 | }; 17 | } else { 18 | return { 19 | ws_protocol: protocol === "https:" ? "wss" : "ws", 20 | http_protocol: protocol as "http:" | "https:", 21 | host, 22 | }; 23 | } 24 | } 25 | 26 | // default to secure if no protocol is provided 27 | return { 28 | ws_protocol: "wss", 29 | http_protocol: "https:", 30 | host: endpoint, 31 | }; 32 | } 33 | 34 | export const RE_SPACE_NAME = /^[^\/]*\/[^\/]*$/; 35 | export const RE_SPACE_DOMAIN = /.*hf\.space\/{0,1}$/; 36 | export async function process_endpoint( 37 | app_reference: string, 38 | token?: `hf_${string}` 39 | ): Promise<{ 40 | space_id: string | false; 41 | host: string; 42 | ws_protocol: "ws" | "wss"; 43 | http_protocol: "http:" | "https:"; 44 | }> { 45 | const headers: { Authorization?: string } = {}; 46 | if (token) { 47 | headers.Authorization = `Bearer ${token}`; 48 | } 49 | 50 | const _app_reference = app_reference.trim(); 51 | 52 | if (RE_SPACE_NAME.test(_app_reference)) { 53 | try { 54 | const res = await fetch( 55 | `https://huggingface.co/api/spaces/${_app_reference}/host`, 56 | { headers } 57 | ); 58 | 59 | if (res.status !== 200) 60 | throw new Error("Space metadata could not be loaded."); 61 | const _host = (await res.json()).host; 62 | 63 | return { 64 | space_id: app_reference, 65 | ...determine_protocol(_host), 66 | }; 67 | } catch (e: any) { 68 | throw new Error("Space metadata could not be loaded." + e.message); 69 | } 70 | } 71 | 72 | if (RE_SPACE_DOMAIN.test(_app_reference)) { 73 | const { ws_protocol, http_protocol, host } = 74 | determine_protocol(_app_reference); 75 | 76 | return { 77 | space_id: host.replace(".hf.space", ""), 78 | ws_protocol, 79 | http_protocol, 80 | host, 81 | }; 82 | } 83 | 84 | return { 85 | space_id: false, 86 | ...determine_protocol(_app_reference), 87 | }; 88 | } 89 | 90 | export function map_names_to_ids(fns: Config["dependencies"]) { 91 | let apis: Record = {}; 92 | 93 | fns.forEach(({ api_name }, i) => { 94 | if (api_name) apis[api_name] = i; 95 | }); 96 | 97 | return apis; 98 | } 99 | 100 | const RE_DISABLED_DISCUSSION = 101 | /^(?=[^]*\b[dD]iscussions{0,1}\b)(?=[^]*\b[dD]isabled\b)[^]*$/; 102 | export async function discussions_enabled(space_id: string) { 103 | try { 104 | const r = await fetch( 105 | `https://huggingface.co/api/spaces/${space_id}/discussions`, 106 | { 107 | method: "HEAD", 108 | } 109 | ); 110 | const error = r.headers.get("x-error-message"); 111 | 112 | if (error && RE_DISABLED_DISCUSSION.test(error)) return false; 113 | else return true; 114 | } catch (e) { 115 | return false; 116 | } 117 | } 118 | 119 | export async function get_space_hardware( 120 | space_id: string, 121 | token: `hf_${string}` 122 | ) { 123 | const headers: { Authorization?: string } = {}; 124 | if (token) { 125 | headers.Authorization = `Bearer ${token}`; 126 | } 127 | 128 | try { 129 | const res = await fetch( 130 | `https://huggingface.co/api/spaces/${space_id}/runtime`, 131 | { headers } 132 | ); 133 | 134 | if (res.status !== 200) 135 | throw new Error("Space hardware could not be obtained."); 136 | 137 | const { hardware } = await res.json(); 138 | 139 | return hardware; 140 | } catch (e: any) { 141 | throw new Error(e.message); 142 | } 143 | } 144 | 145 | export async function set_space_hardware( 146 | space_id: string, 147 | new_hardware: (typeof hardware_types)[number], 148 | token: `hf_${string}` 149 | ) { 150 | const headers: { Authorization?: string } = {}; 151 | if (token) { 152 | headers.Authorization = `Bearer ${token}`; 153 | } 154 | 155 | try { 156 | const res = await fetch( 157 | `https://huggingface.co/api/spaces/${space_id}/hardware`, 158 | { headers, body: JSON.stringify(new_hardware) } 159 | ); 160 | 161 | if (res.status !== 200) 162 | throw new Error( 163 | "Space hardware could not be set. Please ensure the space hardware provided is valid and that a Hugging Face token is passed in." 164 | ); 165 | 166 | const { hardware } = await res.json(); 167 | 168 | return hardware; 169 | } catch (e: any) { 170 | throw new Error(e.message); 171 | } 172 | } 173 | 174 | export async function set_space_timeout( 175 | space_id: string, 176 | timeout: number, 177 | token: `hf_${string}` 178 | ) { 179 | const headers: { Authorization?: string } = {}; 180 | if (token) { 181 | headers.Authorization = `Bearer ${token}`; 182 | } 183 | 184 | try { 185 | const res = await fetch( 186 | `https://huggingface.co/api/spaces/${space_id}/hardware`, 187 | { headers, body: JSON.stringify({ seconds: timeout }) } 188 | ); 189 | 190 | if (res.status !== 200) 191 | throw new Error( 192 | "Space hardware could not be set. Please ensure the space hardware provided is valid and that a Hugging Face token is passed in." 193 | ); 194 | 195 | const { hardware } = await res.json(); 196 | 197 | return hardware; 198 | } catch (e: any) { 199 | throw new Error(e.message); 200 | } 201 | } 202 | 203 | export const hardware_types = [ 204 | "cpu-basic", 205 | "cpu-upgrade", 206 | "t4-small", 207 | "t4-medium", 208 | "a10g-small", 209 | "a10g-large", 210 | "a100-large", 211 | ] as const; 212 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./midjourney"; 2 | export * from "./discord.message"; 3 | export * from "./discord.ws"; 4 | export * from "./interfaces/index"; 5 | export * from "./midjourney.api"; 6 | export * from "./command"; 7 | export * from "./verify.human"; 8 | export * from "./banned.words"; 9 | export * from "./face.swap"; 10 | -------------------------------------------------------------------------------- /src/interfaces/config.ts: -------------------------------------------------------------------------------- 1 | import WebSocket from "isomorphic-ws"; 2 | 3 | export type FetchFn = typeof fetch; 4 | export type WebSocketCl = typeof WebSocket; 5 | export const MJBot = "936929561302675456"; 6 | export const NijiBot = "1022952195194359889"; 7 | export interface MJConfig { 8 | ChannelId: string; 9 | SalaiToken: string; 10 | BotId: typeof MJBot | typeof NijiBot; 11 | Debug: boolean; 12 | Limit: number; 13 | MaxWait: number; 14 | SessionId: string; 15 | ServerId?: string; 16 | Ws?: boolean; 17 | Remix?: boolean; 18 | HuggingFaceToken?: string; 19 | DiscordBaseUrl: string; 20 | WsBaseUrl: string; 21 | fetch: FetchFn; 22 | ApiInterval: number; 23 | WebSocket: WebSocketCl; 24 | ImageProxy: string; 25 | } 26 | export interface MJConfigParam { 27 | SalaiToken: string; //DISCORD_TOKEN 28 | ChannelId?: string; //DISCORD_CHANNEL_ID 29 | ServerId?: string; //DISCORD_SERVER_ID 30 | BotId?: typeof MJBot | typeof NijiBot; //DISCORD_BOT_ID MJBot OR NijiBot 31 | Debug?: boolean; // print log 32 | ApiInterval?: number; //ApiInterval request api interval 33 | Limit?: number; //Limit of get message list 34 | MaxWait?: number; 35 | Remix?: boolean; //Remix:true use remix mode 36 | Ws?: boolean; //Ws:true use websocket get discord message (ephemeral message) 37 | HuggingFaceToken?: string; //HuggingFaceToken for verify human 38 | SessionId?: string; 39 | DiscordBaseUrl?: string; 40 | ImageProxy?: string; 41 | WsBaseUrl?: string; 42 | fetch?: FetchFn; //Node.js<18 need node.fetch Or proxy 43 | WebSocket?: WebSocketCl; //isomorphic-ws Or proxy 44 | } 45 | 46 | export const DefaultMJConfig: MJConfig = { 47 | BotId: MJBot, 48 | ChannelId: "1077800642086703114", 49 | SalaiToken: "", 50 | ApiInterval: 350, 51 | SessionId: "8bb7f5b79c7a49f7d0824ab4b8773a81", 52 | Debug: false, 53 | Limit: 50, 54 | Ws: true, 55 | MaxWait: 200, 56 | ImageProxy: "", 57 | DiscordBaseUrl: "https://discord.com", 58 | WsBaseUrl: "wss://gateway.discord.gg/?encoding=json&v=9", 59 | fetch: fetch, 60 | WebSocket: WebSocket, 61 | }; 62 | -------------------------------------------------------------------------------- /src/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./message"; 2 | export * from "./config"; 3 | export * from "./upload"; 4 | export * from "./modal"; 5 | -------------------------------------------------------------------------------- /src/interfaces/message.ts: -------------------------------------------------------------------------------- 1 | export interface MJMessage { 2 | uri: string; 3 | proxy_url?: string; 4 | content: string; 5 | flags: number; 6 | id?: string; 7 | hash?: string; 8 | progress?: string; 9 | options?: MJOptions[]; 10 | width?: number; 11 | height?: number; 12 | } 13 | 14 | export type LoadingHandler = (uri: string, progress: string) => void; 15 | export type OnModal = (nonce: string, id: string) => Promise; 16 | 17 | export interface WaitMjEvent { 18 | nonce: string; 19 | prompt?: string; 20 | id?: string; 21 | del?: boolean; // is delete message 22 | onmodal?: OnModal; 23 | } 24 | export interface MJEmit { 25 | error?: Error; 26 | message?: MJMessage; 27 | } 28 | 29 | export interface MJInfo { 30 | subscription: string; 31 | jobMode: string; 32 | visibilityMode: string; 33 | fastTimeRemaining: string; 34 | lifetimeUsage: string; 35 | relaxedUsage: string; 36 | queuedJobsFast: string; 37 | queuedJobsRelax: string; 38 | runningJobs: string; 39 | } 40 | 41 | export interface MJOptions { 42 | label: string; 43 | type: number; 44 | style: number; 45 | custom: string; 46 | } 47 | export interface MJSettings { 48 | content: string; 49 | id: string; 50 | flags: number; 51 | options: MJOptions[]; 52 | } 53 | export interface MJDescribe { 54 | id: string; 55 | flags: number; 56 | uri: string; 57 | proxy_url?: string; 58 | options: MJOptions[]; 59 | descriptions: string[]; 60 | } 61 | 62 | export interface MJShorten { 63 | description: string; 64 | id: string; 65 | flags: number; 66 | options: MJOptions[]; 67 | prompts: string[]; 68 | } 69 | -------------------------------------------------------------------------------- /src/interfaces/modal.ts: -------------------------------------------------------------------------------- 1 | 2 | export const RemixModalSubmitID = "MJ::RemixModal::new_prompt" 3 | export const CustomZoomModalSubmitID = "MJ::OutpaintCustomZoomModal::prompt" 4 | export const ShortenModalSubmitID = "MJ::ImagineModal::new_prompt" 5 | export const DescribeModalSubmitID = "MJ::Picreader::Modal::PromptField" 6 | 7 | export type ModalSubmitID = typeof RemixModalSubmitID | typeof CustomZoomModalSubmitID | typeof ShortenModalSubmitID | typeof DescribeModalSubmitID -------------------------------------------------------------------------------- /src/interfaces/upload.ts: -------------------------------------------------------------------------------- 1 | export type UploadParam = { 2 | filename: string; 3 | file_size: number; 4 | id: number | string; 5 | }; 6 | export type UploadSlot = { 7 | id: number; 8 | upload_filename: string; 9 | upload_url: string; 10 | }; 11 | export type DiscordImage = { 12 | id: number | string; 13 | filename: string; 14 | upload_filename: string; 15 | }; 16 | -------------------------------------------------------------------------------- /src/midjourney.api.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CustomZoomModalSubmitID, 3 | DescribeModalSubmitID, 4 | DiscordImage, 5 | MJConfig, 6 | ModalSubmitID, 7 | RemixModalSubmitID, 8 | ShortenModalSubmitID, 9 | UploadParam, 10 | UploadSlot, 11 | } from "./interfaces"; 12 | 13 | import { nextNonce, sleep } from "./utils"; 14 | import { Command } from "./command"; 15 | import async from "async"; 16 | 17 | export class MidjourneyApi extends Command { 18 | UpId = Date.now() % 10; // upload id 19 | constructor(public config: MJConfig) { 20 | super(config); 21 | } 22 | private safeIteractions = (request: any) => { 23 | return new Promise((resolve, reject) => { 24 | this.queue.push( 25 | { 26 | request, 27 | callback: (any: any) => { 28 | resolve(any); 29 | }, 30 | }, 31 | (error: any, result: any) => { 32 | if (error) { 33 | reject(error); 34 | } else { 35 | resolve(result); 36 | } 37 | } 38 | ); 39 | }); 40 | }; 41 | private processRequest = async ({ 42 | request, 43 | callback, 44 | }: { 45 | request: any; 46 | callback: (any: any) => void; 47 | }) => { 48 | const httpStatus = await this.interactions(request); 49 | callback(httpStatus); 50 | await sleep(this.config.ApiInterval); 51 | }; 52 | private queue = async.queue(this.processRequest, 1); 53 | private interactions = async (payload: any) => { 54 | try { 55 | const headers = { 56 | "Content-Type": "application/json", 57 | Authorization: this.config.SalaiToken, 58 | }; 59 | const response = await this.config.fetch( 60 | `${this.config.DiscordBaseUrl}/api/v9/interactions`, 61 | { 62 | method: "POST", 63 | body: JSON.stringify(payload), 64 | headers: headers, 65 | } 66 | ); 67 | if (response.status >= 400) { 68 | console.error("api.error.config", { 69 | payload: JSON.stringify(payload), 70 | config: this.config, 71 | }); 72 | } 73 | return response.status; 74 | } catch (error) { 75 | console.error(error); 76 | return 500; 77 | } 78 | }; 79 | 80 | async ImagineApi(prompt: string, nonce: string = nextNonce()) { 81 | const payload = await this.imaginePayload(prompt, nonce); 82 | return this.safeIteractions(payload); 83 | } 84 | 85 | async SwitchRemixApi(nonce: string = nextNonce()) { 86 | const payload = await this.PreferPayload(nonce); 87 | return this.safeIteractions(payload); 88 | } 89 | 90 | async ShortenApi(prompt: string, nonce: string = nextNonce()) { 91 | const payload = await this.shortenPayload(prompt, nonce); 92 | return this.safeIteractions(payload); 93 | } 94 | 95 | async VariationApi({ 96 | index, 97 | msgId, 98 | hash, 99 | nonce = nextNonce(), 100 | flags = 0, 101 | }: { 102 | index: 1 | 2 | 3 | 4; 103 | msgId: string; 104 | hash: string; 105 | nonce?: string; 106 | flags?: number; 107 | }) { 108 | return this.CustomApi({ 109 | msgId, 110 | customId: `MJ::JOB::variation::${index}::${hash}`, 111 | flags, 112 | nonce, 113 | }); 114 | } 115 | 116 | async UpscaleApi({ 117 | index, 118 | msgId, 119 | hash, 120 | nonce = nextNonce(), 121 | flags, 122 | }: { 123 | index: 1 | 2 | 3 | 4; 124 | msgId: string; 125 | hash: string; 126 | nonce?: string; 127 | flags: number; 128 | }) { 129 | return this.CustomApi({ 130 | msgId, 131 | customId: `MJ::JOB::upsample::${index}::${hash}`, 132 | flags, 133 | nonce, 134 | }); 135 | } 136 | 137 | async RerollApi({ 138 | msgId, 139 | hash, 140 | nonce = nextNonce(), 141 | flags, 142 | }: { 143 | msgId: string; 144 | hash: string; 145 | nonce?: string; 146 | flags: number; 147 | }) { 148 | return this.CustomApi({ 149 | msgId, 150 | customId: `MJ::JOB::reroll::0::${hash}::SOLO`, 151 | flags, 152 | nonce, 153 | }); 154 | } 155 | 156 | async CustomApi({ 157 | msgId, 158 | customId, 159 | flags, 160 | nonce = nextNonce(), 161 | }: { 162 | msgId: string; 163 | customId: string; 164 | flags: number; 165 | nonce?: string; 166 | }) { 167 | if (!msgId) throw new Error("msgId is empty"); 168 | if (flags === undefined) throw new Error("flags is undefined"); 169 | const payload = { 170 | type: 3, 171 | nonce, 172 | guild_id: this.config.ServerId, 173 | channel_id: this.config.ChannelId, 174 | message_flags: flags, 175 | message_id: msgId, 176 | application_id: this.config.BotId, 177 | session_id: this.config.SessionId, 178 | data: { 179 | component_type: 2, 180 | custom_id: customId, 181 | }, 182 | }; 183 | return this.safeIteractions(payload); 184 | } 185 | 186 | //FIXME: get SubmitCustomId from discord api 187 | async ModalSubmitApi({ 188 | nonce, 189 | msgId, 190 | customId, 191 | prompt, 192 | submitCustomId, 193 | }: { 194 | nonce: string; 195 | msgId: string; 196 | customId: string; 197 | prompt: string; 198 | submitCustomId: ModalSubmitID; 199 | }) { 200 | var payload = { 201 | type: 5, 202 | application_id: this.config.BotId, 203 | channel_id: this.config.ChannelId, 204 | guild_id: this.config.ServerId, 205 | data: { 206 | id: msgId, 207 | custom_id: customId, 208 | components: [ 209 | { 210 | type: 1, 211 | components: [ 212 | { 213 | type: 4, 214 | custom_id: submitCustomId, 215 | value: prompt, 216 | }, 217 | ], 218 | }, 219 | ], 220 | }, 221 | session_id: this.config.SessionId, 222 | nonce, 223 | }; 224 | console.log("submitCustomId", JSON.stringify(payload)); 225 | return this.safeIteractions(payload); 226 | } 227 | 228 | async RemixApi({ 229 | nonce, 230 | msgId, 231 | customId, 232 | prompt, 233 | }: { 234 | nonce: string; 235 | msgId: string; 236 | customId: string; 237 | prompt: string; 238 | }) { 239 | return this.ModalSubmitApi({ 240 | nonce, 241 | msgId, 242 | customId, 243 | prompt, 244 | submitCustomId: RemixModalSubmitID, 245 | }); 246 | } 247 | 248 | async ShortenImagineApi({ 249 | nonce, 250 | msgId, 251 | customId, 252 | prompt, 253 | }: { 254 | nonce: string; 255 | msgId: string; 256 | customId: string; 257 | prompt: string; 258 | }) { 259 | return this.ModalSubmitApi({ 260 | nonce, 261 | msgId, 262 | customId, 263 | prompt, 264 | submitCustomId: ShortenModalSubmitID, 265 | }); 266 | } 267 | 268 | async DescribeImagineApi({ 269 | nonce, 270 | msgId, 271 | customId, 272 | prompt, 273 | }: { 274 | nonce: string; 275 | msgId: string; 276 | customId: string; 277 | prompt: string; 278 | }) { 279 | return this.ModalSubmitApi({ 280 | nonce, 281 | msgId, 282 | customId, 283 | prompt, 284 | submitCustomId: DescribeModalSubmitID, 285 | }); 286 | } 287 | 288 | async CustomZoomImagineApi({ 289 | nonce, 290 | msgId, 291 | customId, 292 | prompt, 293 | }: { 294 | nonce: string; 295 | msgId: string; 296 | customId: string; 297 | prompt: string; 298 | }) { 299 | customId = customId.replace( 300 | "MJ::CustomZoom", 301 | "MJ::OutpaintCustomZoomModal" 302 | ); 303 | return this.ModalSubmitApi({ 304 | nonce, 305 | msgId, 306 | customId, 307 | prompt, 308 | submitCustomId: CustomZoomModalSubmitID, 309 | }); 310 | } 311 | 312 | async InfoApi(nonce?: string) { 313 | const payload = await this.infoPayload(nonce); 314 | return this.safeIteractions(payload); 315 | } 316 | 317 | async SettingsApi(nonce?: string) { 318 | const payload = await this.settingsPayload(nonce); 319 | return this.safeIteractions(payload); 320 | } 321 | 322 | async FastApi(nonce?: string) { 323 | const payload = await this.fastPayload(nonce); 324 | return this.safeIteractions(payload); 325 | } 326 | 327 | async RelaxApi(nonce?: string) { 328 | const payload = await this.relaxPayload(nonce); 329 | return this.safeIteractions(payload); 330 | } 331 | 332 | /** 333 | * 334 | * @param fileUrl http file path 335 | * @returns 336 | */ 337 | async UploadImageByUri(fileUrl: string) { 338 | const response = await this.config.fetch(fileUrl); 339 | const fileData = await response.arrayBuffer(); 340 | const mimeType = response.headers.get("content-type"); 341 | const filename = fileUrl.split("/").pop() || "image.png"; 342 | const file_size = fileData.byteLength; 343 | if (!mimeType) { 344 | throw new Error("Unknown mime type"); 345 | } 346 | const { attachments } = await this.attachments({ 347 | filename, 348 | file_size, 349 | id: this.UpId++, 350 | }); 351 | const UploadSlot = attachments[0]; 352 | await this.uploadImage(UploadSlot, fileData, mimeType); 353 | const resp: DiscordImage = { 354 | id: UploadSlot.id, 355 | filename: UploadSlot.upload_filename.split("/").pop() || "image.png", 356 | upload_filename: UploadSlot.upload_filename, 357 | }; 358 | return resp; 359 | } 360 | 361 | async UploadImageByBole(blob: Blob, filename = nextNonce() + ".png") { 362 | const fileData = await blob.arrayBuffer(); 363 | const mimeType = blob.type; 364 | const file_size = fileData.byteLength; 365 | if (!mimeType) { 366 | throw new Error("Unknown mime type"); 367 | } 368 | const { attachments } = await this.attachments({ 369 | filename, 370 | file_size, 371 | id: this.UpId++, 372 | }); 373 | const UploadSlot = attachments[0]; 374 | await this.uploadImage(UploadSlot, fileData, mimeType); 375 | const resp: DiscordImage = { 376 | id: UploadSlot.id, 377 | filename: UploadSlot.upload_filename.split("/").pop() || "image.png", 378 | upload_filename: UploadSlot.upload_filename, 379 | }; 380 | return resp; 381 | } 382 | 383 | /** 384 | * prepare an attachement to upload an image. 385 | */ 386 | private async attachments( 387 | ...files: UploadParam[] 388 | ): Promise<{ attachments: UploadSlot[] }> { 389 | const { SalaiToken, DiscordBaseUrl, ChannelId, fetch } = this.config; 390 | const headers = { 391 | Authorization: SalaiToken, 392 | "content-type": "application/json", 393 | }; 394 | const url = new URL( 395 | `${DiscordBaseUrl}/api/v9/channels/${ChannelId}/attachments` 396 | ); 397 | const body = { files }; 398 | const response = await this.config.fetch(url, { 399 | headers, 400 | method: "POST", 401 | body: JSON.stringify(body), 402 | }); 403 | if (response.status === 200) { 404 | return (await response.json()) as { attachments: UploadSlot[] }; 405 | } 406 | const error = `Attachments return ${response.status} ${ 407 | response.statusText 408 | } ${await response.text()}`; 409 | throw new Error(error); 410 | } 411 | 412 | private async uploadImage( 413 | slot: UploadSlot, 414 | data: ArrayBuffer, 415 | contentType: string 416 | ): Promise { 417 | const body = new Uint8Array(data); 418 | const headers = { "content-type": contentType }; 419 | const response = await this.config.fetch(slot.upload_url, { 420 | method: "PUT", 421 | headers, 422 | body, 423 | }); 424 | if (!response.ok) { 425 | throw new Error( 426 | `uploadImage return ${response.status} ${ 427 | response.statusText 428 | } ${await response.text()}` 429 | ); 430 | } 431 | } 432 | 433 | async DescribeApi(image: DiscordImage, nonce?: string) { 434 | const payload = await this.describePayload(image, nonce); 435 | return this.safeIteractions(payload); 436 | } 437 | async upImageApi(image: DiscordImage, nonce?: string) { 438 | const { SalaiToken, DiscordBaseUrl, ChannelId, fetch } = this.config; 439 | const payload = { 440 | content: "", 441 | nonce, 442 | channel_id: ChannelId, 443 | type: 0, 444 | sticker_ids: [], 445 | attachments: [image], 446 | }; 447 | 448 | const url = new URL( 449 | `${DiscordBaseUrl}/api/v9/channels/${ChannelId}/messages` 450 | ); 451 | const headers = { 452 | Authorization: SalaiToken, 453 | "content-type": "application/json", 454 | }; 455 | const response = await fetch(url, { 456 | headers, 457 | method: "POST", 458 | body: JSON.stringify(payload), 459 | }); 460 | 461 | return response.status; 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /src/midjourney.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DefaultMJConfig, 3 | LoadingHandler, 4 | MJConfig, 5 | MJConfigParam, 6 | } from "./interfaces"; 7 | import { MidjourneyApi } from "./midjourney.api"; 8 | import { MidjourneyMessage } from "./discord.message"; 9 | import { 10 | toRemixCustom, 11 | custom2Type, 12 | nextNonce, 13 | random, 14 | base64ToBlob, 15 | } from "./utils"; 16 | import { WsMessage } from "./discord.ws"; 17 | import { faceSwap } from "./face.swap"; 18 | export class Midjourney extends MidjourneyMessage { 19 | public config: MJConfig; 20 | private wsClient?: WsMessage; 21 | public MJApi: MidjourneyApi; 22 | constructor(defaults: MJConfigParam) { 23 | const { SalaiToken } = defaults; 24 | if (!SalaiToken) { 25 | throw new Error("SalaiToken are required"); 26 | } 27 | super(defaults); 28 | this.config = { 29 | ...DefaultMJConfig, 30 | ...defaults, 31 | }; 32 | this.MJApi = new MidjourneyApi(this.config); 33 | } 34 | async Connect() { 35 | if (!this.config.Ws) { 36 | return this; 37 | } 38 | await this.MJApi.allCommand(); 39 | if (this.wsClient) return this; 40 | this.wsClient = new WsMessage(this.config, this.MJApi); 41 | await this.wsClient.onceReady(); 42 | return this; 43 | } 44 | async init() { 45 | await this.Connect(); 46 | const settings = await this.Settings(); 47 | if (settings) { 48 | // this.log(`settings:`, settings.content); 49 | const remix = settings.options.find((o) => o.label === "Remix mode"); 50 | if (remix?.style == 3) { 51 | this.config.Remix = true; 52 | this.log(`Remix mode enabled`); 53 | } 54 | } 55 | return this; 56 | } 57 | async Imagine(prompt: string, loading?: LoadingHandler) { 58 | prompt = prompt.trim(); 59 | if (!this.config.Ws) { 60 | const seed = random(1000000000, 9999999999); 61 | prompt = `[${seed}] ${prompt}`; 62 | } else { 63 | await this.getWsClient(); 64 | } 65 | 66 | const nonce = nextNonce(); 67 | this.log(`Imagine`, prompt, "nonce", nonce); 68 | const httpStatus = await this.MJApi.ImagineApi(prompt, nonce); 69 | if (httpStatus !== 204) { 70 | throw new Error(`ImagineApi failed with status ${httpStatus}`); 71 | } 72 | if (this.wsClient) { 73 | return await this.wsClient.waitImageMessage({ nonce, loading, prompt }); 74 | } else { 75 | this.log(`await generate image`); 76 | const msg = await this.WaitMessage(prompt, loading); 77 | this.log(`image generated`, prompt, msg?.uri); 78 | return msg; 79 | } 80 | } 81 | // check ws enabled && connect 82 | private async getWsClient() { 83 | if (!this.config.Ws) { 84 | throw new Error(`ws not enabled`); 85 | } 86 | if (!this.wsClient) { 87 | await this.Connect(); 88 | } 89 | if (!this.wsClient) { 90 | throw new Error(`ws not connected`); 91 | } 92 | return this.wsClient; 93 | } 94 | 95 | async Settings() { 96 | const wsClient = await this.getWsClient(); 97 | const nonce = nextNonce(); 98 | const httpStatus = await this.MJApi.SettingsApi(nonce); 99 | if (httpStatus !== 204) { 100 | throw new Error(`ImagineApi failed with status ${httpStatus}`); 101 | } 102 | return wsClient.waitSettings(); 103 | } 104 | async Reset() { 105 | const settings = await this.Settings(); 106 | if (!settings) { 107 | throw new Error(`Settings not found`); 108 | } 109 | const reset = settings.options.find((o) => o.label === "Reset Settings"); 110 | if (!reset) { 111 | throw new Error(`Reset Settings not found`); 112 | } 113 | const httpstatus = await this.MJApi.CustomApi({ 114 | msgId: settings.id, 115 | customId: reset.custom, 116 | flags: settings.flags, 117 | }); 118 | if (httpstatus !== 204) { 119 | throw new Error(`Reset failed with status ${httpstatus}`); 120 | } 121 | } 122 | 123 | async Info() { 124 | const wsClient = await this.getWsClient(); 125 | const nonce = nextNonce(); 126 | const httpStatus = await this.MJApi.InfoApi(nonce); 127 | if (httpStatus !== 204) { 128 | throw new Error(`InfoApi failed with status ${httpStatus}`); 129 | } 130 | return wsClient.waitInfo(); 131 | } 132 | 133 | async Fast() { 134 | const nonce = nextNonce(); 135 | const httpStatus = await this.MJApi.FastApi(nonce); 136 | if (httpStatus !== 204) { 137 | throw new Error(`FastApi failed with status ${httpStatus}`); 138 | } 139 | return null; 140 | } 141 | async Relax() { 142 | const nonce = nextNonce(); 143 | const httpStatus = await this.MJApi.RelaxApi(nonce); 144 | if (httpStatus !== 204) { 145 | throw new Error(`RelaxApi failed with status ${httpStatus}`); 146 | } 147 | return null; 148 | } 149 | async SwitchRemix() { 150 | const wsClient = await this.getWsClient(); 151 | const nonce = nextNonce(); 152 | const httpStatus = await this.MJApi.SwitchRemixApi(nonce); 153 | if (httpStatus !== 204) { 154 | throw new Error(`RelaxApi failed with status ${httpStatus}`); 155 | } 156 | return wsClient.waitContent("prefer-remix"); 157 | } 158 | async Describe(imgUri: string) { 159 | const wsClient = await this.getWsClient(); 160 | const nonce = nextNonce(); 161 | const DcImage = await this.MJApi.UploadImageByUri(imgUri); 162 | this.log(`Describe`, DcImage); 163 | const httpStatus = await this.MJApi.DescribeApi(DcImage, nonce); 164 | if (httpStatus !== 204) { 165 | throw new Error(`DescribeApi failed with status ${httpStatus}`); 166 | } 167 | return wsClient.waitDescribe(nonce); 168 | } 169 | async DescribeByBlob(blob: Blob) { 170 | const wsClient = await this.getWsClient(); 171 | const nonce = nextNonce(); 172 | const DcImage = await this.MJApi.UploadImageByBole(blob); 173 | this.log(`Describe`, DcImage); 174 | const httpStatus = await this.MJApi.DescribeApi(DcImage, nonce); 175 | if (httpStatus !== 204) { 176 | throw new Error(`DescribeApi failed with status ${httpStatus}`); 177 | } 178 | return wsClient.waitDescribe(nonce); 179 | } 180 | 181 | async Shorten(prompt: string) { 182 | const wsClient = await this.getWsClient(); 183 | const nonce = nextNonce(); 184 | const httpStatus = await this.MJApi.ShortenApi(prompt, nonce); 185 | if (httpStatus !== 204) { 186 | throw new Error(`ShortenApi failed with status ${httpStatus}`); 187 | } 188 | return wsClient.waitShorten(nonce); 189 | } 190 | 191 | async Variation({ 192 | index, 193 | msgId, 194 | hash, 195 | content, 196 | flags, 197 | loading, 198 | }: { 199 | index: 1 | 2 | 3 | 4; 200 | msgId: string; 201 | hash: string; 202 | content?: string; 203 | flags: number; 204 | loading?: LoadingHandler; 205 | }) { 206 | return await this.Custom({ 207 | customId: `MJ::JOB::variation::${index}::${hash}`, 208 | msgId, 209 | content, 210 | flags, 211 | loading, 212 | }); 213 | } 214 | 215 | async Upscale({ 216 | index, 217 | msgId, 218 | hash, 219 | content, 220 | flags, 221 | loading, 222 | }: { 223 | index: 1 | 2 | 3 | 4; 224 | msgId: string; 225 | hash: string; 226 | content?: string; 227 | flags: number; 228 | loading?: LoadingHandler; 229 | }) { 230 | return await this.Custom({ 231 | customId: `MJ::JOB::upsample::${index}::${hash}`, 232 | msgId, 233 | content, 234 | flags, 235 | loading, 236 | }); 237 | } 238 | 239 | async Custom({ 240 | msgId, 241 | customId, 242 | content, 243 | flags, 244 | loading, 245 | }: { 246 | msgId: string; 247 | customId: string; 248 | content?: string; 249 | flags: number; 250 | loading?: LoadingHandler; 251 | }) { 252 | if (this.config.Ws) { 253 | await this.getWsClient(); 254 | } 255 | const nonce = nextNonce(); 256 | const httpStatus = await this.MJApi.CustomApi({ 257 | msgId, 258 | customId, 259 | flags, 260 | nonce, 261 | }); 262 | if (httpStatus !== 204) { 263 | throw new Error(`CustomApi failed with status ${httpStatus}`); 264 | } 265 | if (this.wsClient) { 266 | return await this.wsClient.waitImageMessage({ 267 | nonce, 268 | loading, 269 | messageId: msgId, 270 | prompt: content, 271 | onmodal: async (nonde, id) => { 272 | if (content === undefined || content === "") { 273 | return ""; 274 | } 275 | const newNonce = nextNonce(); 276 | switch (custom2Type(customId)) { 277 | case "customZoom": 278 | const httpStatus = await this.MJApi.CustomZoomImagineApi({ 279 | msgId: id, 280 | customId, 281 | prompt: content, 282 | nonce: newNonce, 283 | }); 284 | if (httpStatus !== 204) { 285 | throw new Error( 286 | `CustomZoomImagineApi failed with status ${httpStatus}` 287 | ); 288 | } 289 | return newNonce; 290 | case "variation": 291 | if (this.config.Remix !== true) { 292 | return ""; 293 | } 294 | customId = toRemixCustom(customId); 295 | const remixHttpStatus = await this.MJApi.RemixApi({ 296 | msgId: id, 297 | customId, 298 | prompt: content, 299 | nonce: newNonce, 300 | }); 301 | if (remixHttpStatus !== 204) { 302 | throw new Error( 303 | `RemixApi failed with status ${remixHttpStatus}` 304 | ); 305 | } 306 | return newNonce; 307 | default: 308 | return ""; 309 | throw new Error(`unknown customId ${customId}`); 310 | } 311 | }, 312 | }); 313 | } 314 | if (content === undefined || content === "") { 315 | throw new Error(`content is required`); 316 | } 317 | return await this.WaitMessage(content, loading); 318 | } 319 | 320 | async ZoomOut({ 321 | level, 322 | msgId, 323 | hash, 324 | content, 325 | flags, 326 | loading, 327 | }: { 328 | level: "high" | "low" | "2x" | "1.5x"; 329 | msgId: string; 330 | hash: string; 331 | content?: string; 332 | flags: number; 333 | loading?: LoadingHandler; 334 | }) { 335 | let customId: string; 336 | switch (level) { 337 | case "high": 338 | customId = `MJ::JOB::high_variation::1::${hash}::SOLO`; 339 | break; 340 | case "low": 341 | customId = `MJ::JOB::low_variation::1::${hash}::SOLO`; 342 | break; 343 | case "2x": 344 | customId = `MJ::Outpaint::50::1::${hash}::SOLO`; 345 | break; 346 | case "1.5x": 347 | customId = `MJ::Outpaint::75::1::${hash}::SOLO`; 348 | break; 349 | } 350 | return this.Custom({ 351 | msgId, 352 | customId, 353 | content, 354 | flags, 355 | loading, 356 | }); 357 | } 358 | 359 | async Reroll({ 360 | msgId, 361 | hash, 362 | content, 363 | flags, 364 | loading, 365 | }: { 366 | msgId: string; 367 | hash: string; 368 | content?: string; 369 | flags: number; 370 | loading?: LoadingHandler; 371 | }) { 372 | return await this.Custom({ 373 | customId: `MJ::JOB::reroll::0::${hash}::SOLO`, 374 | msgId, 375 | content, 376 | flags, 377 | loading, 378 | }); 379 | } 380 | 381 | async FaceSwap(target: string, source: string) { 382 | const wsClient = await this.getWsClient(); 383 | const app = new faceSwap(this.config.HuggingFaceToken); 384 | const Target = await (await this.config.fetch(target)).blob(); 385 | const Source = await (await this.config.fetch(source)).blob(); 386 | const res = await app.changeFace(Target, Source); 387 | this.log(res[0]); 388 | const blob = await base64ToBlob(res[0] as string); 389 | const DcImage = await this.MJApi.UploadImageByBole(blob); 390 | const nonce = nextNonce(); 391 | const httpStatus = await this.MJApi.DescribeApi(DcImage, nonce); 392 | if (httpStatus !== 204) { 393 | throw new Error(`DescribeApi failed with status ${httpStatus}`); 394 | } 395 | return wsClient.waitDescribe(nonce); 396 | } 397 | 398 | Close() { 399 | if (this.wsClient) { 400 | this.wsClient.close(); 401 | this.wsClient = undefined; 402 | } 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { Snowyflake, Epoch } from "snowyflake"; 2 | import { MJInfo, MJOptions } from "../interfaces"; 3 | 4 | export const sleep = async (ms: number): Promise => 5 | await new Promise((resolve) => setTimeout(resolve, ms)); 6 | 7 | export const random = (min: number, max: number): number => 8 | Math.floor(Math.random() * (max - min) + min); 9 | 10 | const snowflake = new Snowyflake({ 11 | workerId: 0n, 12 | processId: 0n, 13 | epoch: Epoch.Discord, // BigInt timestamp 14 | }); 15 | 16 | export const nextNonce = (): string => snowflake.nextId().toString(); 17 | 18 | export const formatPrompts = (prompts: string) => { 19 | const regex = /(\d️⃣ .+)/g; 20 | const matches = prompts.match(regex); 21 | if (matches) { 22 | const shortenedPrompts = matches.map((match) => match.trim()); 23 | return shortenedPrompts; 24 | } else { 25 | return []; 26 | } 27 | }; 28 | 29 | export const formatOptions = (components: any) => { 30 | var data: MJOptions[] = []; 31 | for (var i = 0; i < components.length; i++) { 32 | const component = components[i]; 33 | if (component.components && component.components.length > 0) { 34 | const item = formatOptions(component.components); 35 | data = data.concat(item); 36 | } 37 | if (!component.custom_id) continue; 38 | data.push({ 39 | type: component.type, 40 | style: component.style, 41 | label: component.label || component.emoji?.name, 42 | custom: component.custom_id, 43 | }); 44 | } 45 | return data; 46 | }; 47 | 48 | export const formatInfo = (msg: string) => { 49 | let jsonResult: MJInfo = { 50 | subscription: "", 51 | jobMode: "", 52 | visibilityMode: "", 53 | fastTimeRemaining: "", 54 | lifetimeUsage: "", 55 | relaxedUsage: "", 56 | queuedJobsFast: "", 57 | queuedJobsRelax: "", 58 | runningJobs: "", 59 | }; // Initialize jsonResult with empty object 60 | msg.split("\n").forEach(function (line) { 61 | const colonIndex = line.indexOf(":"); 62 | if (colonIndex > -1) { 63 | const key = line.substring(0, colonIndex).trim().replaceAll("**", ""); 64 | const value = line.substring(colonIndex + 1).trim(); 65 | switch (key) { 66 | case "Subscription": 67 | jsonResult.subscription = value; 68 | break; 69 | case "Job Mode": 70 | jsonResult.jobMode = value; 71 | break; 72 | case "Visibility Mode": 73 | jsonResult.visibilityMode = value; 74 | break; 75 | case "Fast Time Remaining": 76 | jsonResult.fastTimeRemaining = value; 77 | break; 78 | case "Lifetime Usage": 79 | jsonResult.lifetimeUsage = value; 80 | break; 81 | case "Relaxed Usage": 82 | jsonResult.relaxedUsage = value; 83 | break; 84 | case "Queued Jobs (fast)": 85 | jsonResult.queuedJobsFast = value; 86 | break; 87 | case "Queued Jobs (relax)": 88 | jsonResult.queuedJobsRelax = value; 89 | break; 90 | case "Running Jobs": 91 | jsonResult.runningJobs = value; 92 | break; 93 | default: 94 | // Do nothing 95 | } 96 | } 97 | }); 98 | return jsonResult; 99 | }; 100 | 101 | export const uriToHash = (uri: string) => { 102 | return uri.split("_").pop()?.split(".")[0] ?? ""; 103 | }; 104 | 105 | export const content2progress = (content: string) => { 106 | if (!content) return ""; 107 | const spcon = content.split("<@"); 108 | if (spcon.length < 2) { 109 | return ""; 110 | } 111 | content = spcon[1]; 112 | const regex = /\(([^)]+)\)/; // matches the value inside the first parenthesis 113 | const match = content.match(regex); 114 | let progress = ""; 115 | if (match) { 116 | progress = match[1]; 117 | } 118 | return progress; 119 | }; 120 | 121 | export const content2prompt = (content: string) => { 122 | if (!content) return ""; 123 | const pattern = /\*\*(.*?)\*\*/; // Match **middle content 124 | const matches = content.match(pattern); 125 | if (matches && matches.length > 1) { 126 | return matches[1]; // Get the matched content 127 | } else { 128 | console.log("No match found.", content); 129 | return content; 130 | } 131 | }; 132 | 133 | export function custom2Type(custom: string) { 134 | if (custom.includes("upsample")) { 135 | return "upscale"; 136 | } else if (custom.includes("variation")) { 137 | return "variation"; 138 | } else if (custom.includes("reroll")) { 139 | return "reroll"; 140 | } else if (custom.includes("CustomZoom")) { 141 | return "customZoom"; 142 | } else if (custom.includes("Outpaint")) { 143 | return "variation"; 144 | } else if (custom.includes("remaster")) { 145 | return "reroll"; 146 | } 147 | return null; 148 | } 149 | 150 | export const toRemixCustom = (customID: string) => { 151 | const parts = customID.split("::"); 152 | const convertedString = `MJ::RemixModal::${parts[4]}::${parts[3]}::1`; 153 | return convertedString; 154 | }; 155 | 156 | export async function base64ToBlob(base64Image: string): Promise { 157 | // 移除 base64 图像头部信息 158 | const base64Data = base64Image.replace( 159 | /^data:image\/(png|jpeg|jpg);base64,/, 160 | "" 161 | ); 162 | 163 | // 将 base64 数据解码为二进制数据 164 | const binaryData = atob(base64Data); 165 | 166 | // 创建一个 Uint8Array 来存储二进制数据 167 | const arrayBuffer = new ArrayBuffer(binaryData.length); 168 | const uint8Array = new Uint8Array(arrayBuffer); 169 | for (let i = 0; i < binaryData.length; i++) { 170 | uint8Array[i] = binaryData.charCodeAt(i); 171 | } 172 | 173 | // 使用 Uint8Array 创建 Blob 对象 174 | return new Blob([uint8Array], { type: "image/png" }); // 替换为相应的 MIME 类型 175 | } 176 | -------------------------------------------------------------------------------- /src/verify.human.ts: -------------------------------------------------------------------------------- 1 | import { HfInference } from "@huggingface/inference"; 2 | import { MJConfig } from "./interfaces"; 3 | export class VerifyHuman { 4 | private inference: HfInference; 5 | 6 | constructor(public config: MJConfig) { 7 | const { HuggingFaceToken } = config; 8 | if (HuggingFaceToken === "" || HuggingFaceToken) { 9 | throw new Error("HuggingFaceToken is required"); 10 | } 11 | this.inference = new HfInference(HuggingFaceToken); 12 | } 13 | 14 | async verify(imageUri: string, categories: string[]) { 15 | console.log("verify----start", imageUri, categories); 16 | const imageCates = await this.inference.imageClassification({ 17 | data: await (await this.config.fetch(imageUri)).blob(), 18 | model: "google/vit-base-patch16-224", 19 | }); 20 | console.log("verify----response", { imageCates }); 21 | for (const imageCate of imageCates) { 22 | const { label } = imageCate; 23 | for (const category of categories) { 24 | if (label.includes(category)) { 25 | return category; 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/face.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { faceSwap } from "../src"; 3 | /** 4 | * 5 | * ``` 6 | * npx tsx test/face.ts 7 | * ``` 8 | */ 9 | 10 | async function test2() { 11 | const app = new faceSwap(process.env.HuggingFaceToken); 12 | const Target = await ( 13 | await fetch( 14 | "https://cdn.discordapp.com/attachments/1108587422389899304/1129321837042602016/guapitu006_a_girls_face_with_david_bowies_thunderbolt_71ee5899-bd45-4fc4-8c9d-92f19ddb0a03.png" 15 | ) 16 | ).blob(); 17 | const Source = await ( 18 | await fetch( 19 | "https://cdn.discordapp.com/attachments/1108587422389899304/1129321826804306031/guapitu006_Cute_warrior_girl_in_the_style_of_Baten_Kaitos__111f39bc-329e-4fab-9af7-ee219fedf260.png" 20 | ) 21 | ).blob(); 22 | 23 | await app.changeFace(Target, Source); 24 | } 25 | test2(); 26 | -------------------------------------------------------------------------------- /test/test.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Command, DefaultMJConfig, Midjourney, WsMessage } from "../src"; 3 | /** 4 | * 5 | * a simple example of how to use the imagine command 6 | * ``` 7 | * npx tsx test/test.ts 8 | * ``` 9 | */ 10 | 11 | async function test2() { 12 | const config = { 13 | ...DefaultMJConfig, 14 | ...{ 15 | ServerId: process.env.SERVER_ID, 16 | ChannelId: process.env.CHANNEL_ID, 17 | SalaiToken: process.env.SALAI_TOKEN, 18 | }, 19 | }; 20 | 21 | const command = new Command(config); 22 | const msg = await command.getCommand("imagine"); 23 | } 24 | test2(); 25 | -------------------------------------------------------------------------------- /test/test2.ts: -------------------------------------------------------------------------------- 1 | import async from "async"; 2 | /** 3 | * 4 | * a simple example of how to use the imagine command 5 | * ``` 6 | * npx tsx test/test2.ts 7 | * ``` 8 | */ 9 | 10 | const processRequest = async ({ 11 | request, 12 | callback, 13 | }: { 14 | request: any; 15 | callback: (any) => void; 16 | }) => { 17 | // 在这里执行实际的HTTP请求 18 | // 可以使用任何HTTP库,比如axios或node-fetch 19 | // console.log("Request processed:", request, new Date()); 20 | 21 | // 这里只是一个示例,使用setTimeout模拟一个异步请求 22 | await new Promise((resolve) => setTimeout(resolve, 1000)); 23 | callback(request + " processed"); 24 | await new Promise((resolve) => setTimeout(resolve, 2000)); 25 | return "sleep processed"; 26 | }; 27 | const queue = async.queue(processRequest, 1); 28 | const addRequest = (request: any) => { 29 | console.log("Request queued:", request, new Date()); 30 | return new Promise((resolve, reject) => { 31 | queue.push( 32 | { 33 | request, 34 | callback: (any) => { 35 | resolve(any); 36 | }, 37 | }, 38 | (error: any, result: any) => { 39 | if (error) { 40 | reject(error); 41 | } else { 42 | resolve(result); 43 | } 44 | } 45 | ); 46 | }); 47 | }; 48 | 49 | const safeRequest = async (request: any) => { 50 | const dd = await addRequest(request); 51 | return dd + " safeRequest" + new Date(); 52 | }; 53 | async function test2() { 54 | safeRequest("1").then((result) => console.log(result)); 55 | safeRequest("2").then((result) => console.log(result)); 56 | safeRequest("3").then((result) => console.log(result)); 57 | safeRequest("4").then((result) => console.log(result)); 58 | safeRequest("5").then((result) => console.log(result)); 59 | safeRequest("6").then((result) => console.log(result)); 60 | const dd = await safeRequest("32"); 61 | console.log(dd); 62 | } 63 | test2(); 64 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | /* Projects */ 5 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 6 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 7 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 8 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 9 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 10 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 11 | /* Language and Environment */ 12 | "target": "ES2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 13 | "lib": [ 14 | "es2021", 15 | "DOM", 16 | "esnext.asynciterable" 17 | ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, 18 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 19 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 20 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 21 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 22 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 23 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 24 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 25 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 26 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 27 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 28 | /* Modules */ 29 | "module": "commonjs" /* Specify what module code is generated. */, 30 | // "rootDir": "./", /* Specify the root folder within your source files. */ 31 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 32 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 33 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 34 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 35 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 36 | "types": [ 37 | "node" 38 | ] /* Specify type package names to be included without being referenced in a source file. */, 39 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 40 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 41 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 42 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 43 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 44 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 45 | // "resolveJsonModule": true, /* Enable importing .json files. */ 46 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 47 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 48 | /* JavaScript Support */ 49 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 50 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 51 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 52 | /* Emit */ 53 | "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */, 54 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 55 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 56 | "sourceMap": true /* Create source map files for emitted JavaScript files. */, 57 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 58 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 59 | "outDir": "./libs" /* Specify an output folder for all emitted files. */, 60 | // "removeComments": true, /* Disable emitting comments. */ 61 | // "noEmit": true, /* Disable emitting files from a compilation. */ 62 | "importHelpers": true /* Allow importing helper functions from tslib once per project, instead of including them per-file. */, 63 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 64 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 65 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 66 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 67 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 68 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 69 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 70 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 71 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 72 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 73 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 74 | "declarationDir": "./libs" /* Specify the output directory for generated declaration files. */, 75 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 76 | /* Interop Constraints */ 77 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 78 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 79 | "allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */, 80 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 83 | /* Type Checking */ 84 | "strict": true /* Enable all strict type-checking options. */, 85 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 86 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 87 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 88 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 89 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 90 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 91 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 92 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 93 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 94 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 95 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 96 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 97 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 98 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 99 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 100 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 101 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 102 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 103 | /* Completeness */ 104 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 105 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 106 | }, 107 | "include": ["./src"] 108 | } 109 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@cspotcode/source-map-support@^0.8.0": 6 | version "0.8.1" 7 | resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz" 8 | integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== 9 | dependencies: 10 | "@jridgewell/trace-mapping" "0.3.9" 11 | 12 | "@esbuild-kit/cjs-loader@^2.4.2": 13 | version "2.4.2" 14 | resolved "https://registry.npmjs.org/@esbuild-kit/cjs-loader/-/cjs-loader-2.4.2.tgz" 15 | integrity sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg== 16 | dependencies: 17 | "@esbuild-kit/core-utils" "^3.0.0" 18 | get-tsconfig "^4.4.0" 19 | 20 | "@esbuild-kit/core-utils@^3.0.0": 21 | version "3.1.0" 22 | resolved "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.1.0.tgz" 23 | integrity sha512-Uuk8RpCg/7fdHSceR1M6XbSZFSuMrxcePFuGgyvsBn+u339dk5OeL4jv2EojwTN2st/unJGsVm4qHWjWNmJ/tw== 24 | dependencies: 25 | esbuild "~0.17.6" 26 | source-map-support "^0.5.21" 27 | 28 | "@esbuild-kit/esm-loader@^2.5.5": 29 | version "2.5.5" 30 | resolved "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.5.5.tgz" 31 | integrity sha512-Qwfvj/qoPbClxCRNuac1Du01r9gvNOT+pMYtJDapfB1eoGN1YlJ1BixLyL9WVENRx5RXgNLdfYdx/CuswlGhMw== 32 | dependencies: 33 | "@esbuild-kit/core-utils" "^3.0.0" 34 | get-tsconfig "^4.4.0" 35 | 36 | "@esbuild/android-arm64@0.17.18": 37 | version "0.17.18" 38 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.18.tgz#4aa8d8afcffb4458736ca9b32baa97d7cb5861ea" 39 | integrity sha512-/iq0aK0eeHgSC3z55ucMAHO05OIqmQehiGay8eP5l/5l+iEr4EIbh4/MI8xD9qRFjqzgkc0JkX0LculNC9mXBw== 40 | 41 | "@esbuild/android-arm@0.17.18": 42 | version "0.17.18" 43 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.18.tgz#74a7e95af4ee212ebc9db9baa87c06a594f2a427" 44 | integrity sha512-EmwL+vUBZJ7mhFCs5lA4ZimpUH3WMAoqvOIYhVQwdIgSpHC8ImHdsRyhHAVxpDYUSm0lWvd63z0XH1IlImS2Qw== 45 | 46 | "@esbuild/android-x64@0.17.18": 47 | version "0.17.18" 48 | resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.18.tgz#1dcd13f201997c9fe0b204189d3a0da4eb4eb9b6" 49 | integrity sha512-x+0efYNBF3NPW2Xc5bFOSFW7tTXdAcpfEg2nXmxegm4mJuVeS+i109m/7HMiOQ6M12aVGGFlqJX3RhNdYM2lWg== 50 | 51 | "@esbuild/darwin-arm64@0.17.18": 52 | version "0.17.18" 53 | resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.18.tgz" 54 | integrity sha512-6tY+djEAdF48M1ONWnQb1C+6LiXrKjmqjzPNPWXhu/GzOHTHX2nh8Mo2ZAmBFg0kIodHhciEgUBtcYCAIjGbjQ== 55 | 56 | "@esbuild/darwin-x64@0.17.18": 57 | version "0.17.18" 58 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.18.tgz#a6da308d0ac8a498c54d62e0b2bfb7119b22d315" 59 | integrity sha512-Qq84ykvLvya3dO49wVC9FFCNUfSrQJLbxhoQk/TE1r6MjHo3sFF2tlJCwMjhkBVq3/ahUisj7+EpRSz0/+8+9A== 60 | 61 | "@esbuild/freebsd-arm64@0.17.18": 62 | version "0.17.18" 63 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.18.tgz#b83122bb468889399d0d63475d5aea8d6829c2c2" 64 | integrity sha512-fw/ZfxfAzuHfaQeMDhbzxp9mc+mHn1Y94VDHFHjGvt2Uxl10mT4CDavHm+/L9KG441t1QdABqkVYwakMUeyLRA== 65 | 66 | "@esbuild/freebsd-x64@0.17.18": 67 | version "0.17.18" 68 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.18.tgz#af59e0e03fcf7f221b34d4c5ab14094862c9c864" 69 | integrity sha512-FQFbRtTaEi8ZBi/A6kxOC0V0E9B/97vPdYjY9NdawyLd4Qk5VD5g2pbWN2VR1c0xhzcJm74HWpObPszWC+qTew== 70 | 71 | "@esbuild/linux-arm64@0.17.18": 72 | version "0.17.18" 73 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.18.tgz#8551d72ba540c5bce4bab274a81c14ed01eafdcf" 74 | integrity sha512-R7pZvQZFOY2sxUG8P6A21eq6q+eBv7JPQYIybHVf1XkQYC+lT7nDBdC7wWKTrbvMXKRaGudp/dzZCwL/863mZQ== 75 | 76 | "@esbuild/linux-arm@0.17.18": 77 | version "0.17.18" 78 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.18.tgz#e09e76e526df4f665d4d2720d28ff87d15cdf639" 79 | integrity sha512-jW+UCM40LzHcouIaqv3e/oRs0JM76JfhHjCavPxMUti7VAPh8CaGSlS7cmyrdpzSk7A+8f0hiedHqr/LMnfijg== 80 | 81 | "@esbuild/linux-ia32@0.17.18": 82 | version "0.17.18" 83 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.18.tgz#47878860ce4fe73a36fd8627f5647bcbbef38ba4" 84 | integrity sha512-ygIMc3I7wxgXIxk6j3V00VlABIjq260i967Cp9BNAk5pOOpIXmd1RFQJQX9Io7KRsthDrQYrtcx7QCof4o3ZoQ== 85 | 86 | "@esbuild/linux-loong64@0.17.18": 87 | version "0.17.18" 88 | resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.18.tgz#3f8fbf5267556fc387d20b2e708ce115de5c967a" 89 | integrity sha512-bvPG+MyFs5ZlwYclCG1D744oHk1Pv7j8psF5TfYx7otCVmcJsEXgFEhQkbhNW8otDHL1a2KDINW20cfCgnzgMQ== 90 | 91 | "@esbuild/linux-mips64el@0.17.18": 92 | version "0.17.18" 93 | resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.18.tgz#9d896d8f3c75f6c226cbeb840127462e37738226" 94 | integrity sha512-oVqckATOAGuiUOa6wr8TXaVPSa+6IwVJrGidmNZS1cZVx0HqkTMkqFGD2HIx9H1RvOwFeWYdaYbdY6B89KUMxA== 95 | 96 | "@esbuild/linux-ppc64@0.17.18": 97 | version "0.17.18" 98 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.18.tgz#3d9deb60b2d32c9985bdc3e3be090d30b7472783" 99 | integrity sha512-3dLlQO+b/LnQNxgH4l9rqa2/IwRJVN9u/bK63FhOPB4xqiRqlQAU0qDU3JJuf0BmaH0yytTBdoSBHrb2jqc5qQ== 100 | 101 | "@esbuild/linux-riscv64@0.17.18": 102 | version "0.17.18" 103 | resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.18.tgz#8a943cf13fd24ff7ed58aefb940ef178f93386bc" 104 | integrity sha512-/x7leOyDPjZV3TcsdfrSI107zItVnsX1q2nho7hbbQoKnmoeUWjs+08rKKt4AUXju7+3aRZSsKrJtaRmsdL1xA== 105 | 106 | "@esbuild/linux-s390x@0.17.18": 107 | version "0.17.18" 108 | resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.18.tgz#66cb01f4a06423e5496facabdce4f7cae7cb80e5" 109 | integrity sha512-cX0I8Q9xQkL/6F5zWdYmVf5JSQt+ZfZD2bJudZrWD+4mnUvoZ3TDDXtDX2mUaq6upMFv9FlfIh4Gfun0tbGzuw== 110 | 111 | "@esbuild/linux-x64@0.17.18": 112 | version "0.17.18" 113 | resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.18.tgz#23c26050c6c5d1359c7b774823adc32b3883b6c9" 114 | integrity sha512-66RmRsPlYy4jFl0vG80GcNRdirx4nVWAzJmXkevgphP1qf4dsLQCpSKGM3DUQCojwU1hnepI63gNZdrr02wHUA== 115 | 116 | "@esbuild/netbsd-x64@0.17.18": 117 | version "0.17.18" 118 | resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.18.tgz#789a203d3115a52633ff6504f8cbf757f15e703b" 119 | integrity sha512-95IRY7mI2yrkLlTLb1gpDxdC5WLC5mZDi+kA9dmM5XAGxCME0F8i4bYH4jZreaJ6lIZ0B8hTrweqG1fUyW7jbg== 120 | 121 | "@esbuild/openbsd-x64@0.17.18": 122 | version "0.17.18" 123 | resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.18.tgz#d7b998a30878f8da40617a10af423f56f12a5e90" 124 | integrity sha512-WevVOgcng+8hSZ4Q3BKL3n1xTv5H6Nb53cBrtzzEjDbbnOmucEVcZeGCsCOi9bAOcDYEeBZbD2SJNBxlfP3qiA== 125 | 126 | "@esbuild/sunos-x64@0.17.18": 127 | version "0.17.18" 128 | resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.18.tgz#ecad0736aa7dae07901ba273db9ef3d3e93df31f" 129 | integrity sha512-Rzf4QfQagnwhQXVBS3BYUlxmEbcV7MY+BH5vfDZekU5eYpcffHSyjU8T0xucKVuOcdCsMo+Ur5wmgQJH2GfNrg== 130 | 131 | "@esbuild/win32-arm64@0.17.18": 132 | version "0.17.18" 133 | resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.18.tgz#58dfc177da30acf956252d7c8ae9e54e424887c4" 134 | integrity sha512-Kb3Ko/KKaWhjeAm2YoT/cNZaHaD1Yk/pa3FTsmqo9uFh1D1Rfco7BBLIPdDOozrObj2sahslFuAQGvWbgWldAg== 135 | 136 | "@esbuild/win32-ia32@0.17.18": 137 | version "0.17.18" 138 | resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.18.tgz#340f6163172b5272b5ae60ec12c312485f69232b" 139 | integrity sha512-0/xUMIdkVHwkvxfbd5+lfG7mHOf2FRrxNbPiKWg9C4fFrB8H0guClmaM3BFiRUYrznVoyxTIyC/Ou2B7QQSwmw== 140 | 141 | "@esbuild/win32-x64@0.17.18": 142 | version "0.17.18" 143 | resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.18.tgz#3a8e57153905308db357fd02f57c180ee3a0a1fa" 144 | integrity sha512-qU25Ma1I3NqTSHJUOKi9sAH1/Mzuvlke0ioMJRthLXKm7JiSKVwFghlGbDLOO2sARECGhja4xYfRAZNPAkooYg== 145 | 146 | "@huggingface/inference@^2.5.0": 147 | version "2.5.0" 148 | resolved "https://registry.npmjs.org/@huggingface/inference/-/inference-2.5.0.tgz" 149 | integrity sha512-X3NSdrWAKNTLAsEKabH48Wc+Osys+S7ilRcH1bf9trSDmJlzPVXDseXMRBHCFPCYd5AAAIakhENO4zCqstVg8g== 150 | 151 | "@jridgewell/resolve-uri@^3.0.3": 152 | version "3.1.1" 153 | resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz" 154 | integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== 155 | 156 | "@jridgewell/sourcemap-codec@^1.4.10": 157 | version "1.4.15" 158 | resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz" 159 | integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== 160 | 161 | "@jridgewell/trace-mapping@0.3.9": 162 | version "0.3.9" 163 | resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" 164 | integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== 165 | dependencies: 166 | "@jridgewell/resolve-uri" "^3.0.3" 167 | "@jridgewell/sourcemap-codec" "^1.4.10" 168 | 169 | "@tsconfig/node10@^1.0.7": 170 | version "1.0.9" 171 | resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz" 172 | integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== 173 | 174 | "@tsconfig/node12@^1.0.7": 175 | version "1.0.11" 176 | resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz" 177 | integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== 178 | 179 | "@tsconfig/node14@^1.0.0": 180 | version "1.0.3" 181 | resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz" 182 | integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== 183 | 184 | "@tsconfig/node16@^1.0.2": 185 | version "1.0.4" 186 | resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz" 187 | integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== 188 | 189 | "@types/async@^3.2.20": 190 | version "3.2.20" 191 | resolved "https://registry.yarnpkg.com/@types/async/-/async-3.2.20.tgz#53517caaa68c94f99da1c4e986cf7f2954981515" 192 | integrity sha512-6jSBQQugzyX1aWto0CbvOnmxrU9tMoXfA9gc4IrLEtvr3dTwSg5GLGoWiZnGLI6UG/kqpB3JOQKQrqnhUWGKQA== 193 | 194 | "@types/node@*", "@types/node@^18.16.0": 195 | version "18.16.0" 196 | resolved "https://registry.npmjs.org/@types/node/-/node-18.16.0.tgz" 197 | integrity sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ== 198 | 199 | "@types/ws@^8.5.4": 200 | version "8.5.4" 201 | resolved "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz" 202 | integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg== 203 | dependencies: 204 | "@types/node" "*" 205 | 206 | acorn-walk@^8.1.1: 207 | version "8.2.0" 208 | resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz" 209 | integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== 210 | 211 | acorn@^8.4.1: 212 | version "8.8.2" 213 | resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz" 214 | integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== 215 | 216 | arg@^4.1.0: 217 | version "4.1.3" 218 | resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" 219 | integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== 220 | 221 | async@^3.2.4: 222 | version "3.2.4" 223 | resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" 224 | integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== 225 | 226 | buffer-from@^1.0.0: 227 | version "1.1.2" 228 | resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" 229 | integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== 230 | 231 | create-require@^1.1.0: 232 | version "1.1.1" 233 | resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" 234 | integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== 235 | 236 | diff@^4.0.1: 237 | version "4.0.2" 238 | resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" 239 | integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== 240 | 241 | dotenv@^16.0.3: 242 | version "16.0.3" 243 | resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz" 244 | integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== 245 | 246 | esbuild@~0.17.6: 247 | version "0.17.18" 248 | resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.17.18.tgz" 249 | integrity sha512-z1lix43jBs6UKjcZVKOw2xx69ffE2aG0PygLL5qJ9OS/gy0Ewd1gW/PUQIOIQGXBHWNywSc0floSKoMFF8aK2w== 250 | optionalDependencies: 251 | "@esbuild/android-arm" "0.17.18" 252 | "@esbuild/android-arm64" "0.17.18" 253 | "@esbuild/android-x64" "0.17.18" 254 | "@esbuild/darwin-arm64" "0.17.18" 255 | "@esbuild/darwin-x64" "0.17.18" 256 | "@esbuild/freebsd-arm64" "0.17.18" 257 | "@esbuild/freebsd-x64" "0.17.18" 258 | "@esbuild/linux-arm" "0.17.18" 259 | "@esbuild/linux-arm64" "0.17.18" 260 | "@esbuild/linux-ia32" "0.17.18" 261 | "@esbuild/linux-loong64" "0.17.18" 262 | "@esbuild/linux-mips64el" "0.17.18" 263 | "@esbuild/linux-ppc64" "0.17.18" 264 | "@esbuild/linux-riscv64" "0.17.18" 265 | "@esbuild/linux-s390x" "0.17.18" 266 | "@esbuild/linux-x64" "0.17.18" 267 | "@esbuild/netbsd-x64" "0.17.18" 268 | "@esbuild/openbsd-x64" "0.17.18" 269 | "@esbuild/sunos-x64" "0.17.18" 270 | "@esbuild/win32-arm64" "0.17.18" 271 | "@esbuild/win32-ia32" "0.17.18" 272 | "@esbuild/win32-x64" "0.17.18" 273 | 274 | fsevents@~2.3.2: 275 | version "2.3.2" 276 | resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" 277 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 278 | 279 | get-tsconfig@^4.4.0: 280 | version "4.5.0" 281 | resolved "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.5.0.tgz" 282 | integrity sha512-MjhiaIWCJ1sAU4pIQ5i5OfOuHHxVo1oYeNsWTON7jxYkod8pHocXeh+SSbmu5OZZZK73B6cbJ2XADzXehLyovQ== 283 | 284 | isomorphic-ws@^5.0.0: 285 | version "5.0.0" 286 | resolved "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz" 287 | integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== 288 | 289 | make-error@^1.1.1: 290 | version "1.3.6" 291 | resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" 292 | integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== 293 | 294 | prettier@^2.8.8: 295 | version "2.8.8" 296 | resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz" 297 | integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== 298 | 299 | semiver@^1.1.0: 300 | version "1.1.0" 301 | resolved "https://registry.yarnpkg.com/semiver/-/semiver-1.1.0.tgz#9c97fb02c21c7ce4fcf1b73e2c7a24324bdddd5f" 302 | integrity sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg== 303 | 304 | snowyflake@^2.0.0: 305 | version "2.0.0" 306 | resolved "https://registry.npmjs.org/snowyflake/-/snowyflake-2.0.0.tgz" 307 | integrity sha512-BxeqV0KJxJASu6EBJGUkX194Zhh37AEa0ow/JRK39icWbLTG9Wl/7LAL6a/ZMSjNm4O9pZk6QoLcWP7f/YKmtA== 308 | 309 | source-map-support@^0.5.21: 310 | version "0.5.21" 311 | resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" 312 | integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== 313 | dependencies: 314 | buffer-from "^1.0.0" 315 | source-map "^0.6.0" 316 | 317 | source-map@^0.6.0: 318 | version "0.6.1" 319 | resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" 320 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 321 | 322 | ts-node@^10.9.1: 323 | version "10.9.1" 324 | resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz" 325 | integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== 326 | dependencies: 327 | "@cspotcode/source-map-support" "^0.8.0" 328 | "@tsconfig/node10" "^1.0.7" 329 | "@tsconfig/node12" "^1.0.7" 330 | "@tsconfig/node14" "^1.0.0" 331 | "@tsconfig/node16" "^1.0.2" 332 | acorn "^8.4.1" 333 | acorn-walk "^8.1.1" 334 | arg "^4.1.0" 335 | create-require "^1.1.0" 336 | diff "^4.0.1" 337 | make-error "^1.1.1" 338 | v8-compile-cache-lib "^3.0.1" 339 | yn "3.1.1" 340 | 341 | tslib@^2.5.0: 342 | version "2.5.0" 343 | resolved "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz" 344 | integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== 345 | 346 | tsx@^3.12.6: 347 | version "3.12.6" 348 | resolved "https://registry.npmjs.org/tsx/-/tsx-3.12.6.tgz" 349 | integrity sha512-q93WgS3lBdHlPgS0h1i+87Pt6n9K/qULIMNYZo07nSeu2z5QE2CellcAZfofVXBo2tQg9av2ZcRMQ2S2i5oadQ== 350 | dependencies: 351 | "@esbuild-kit/cjs-loader" "^2.4.2" 352 | "@esbuild-kit/core-utils" "^3.0.0" 353 | "@esbuild-kit/esm-loader" "^2.5.5" 354 | optionalDependencies: 355 | fsevents "~2.3.2" 356 | 357 | typescript@^5.0.4: 358 | version "5.0.4" 359 | resolved "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz" 360 | integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw== 361 | 362 | v8-compile-cache-lib@^3.0.1: 363 | version "3.0.1" 364 | resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz" 365 | integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== 366 | 367 | ws@^8.13.0: 368 | version "8.13.0" 369 | resolved "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz" 370 | integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== 371 | 372 | yn@3.1.1: 373 | version "3.1.1" 374 | resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" 375 | integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== 376 | --------------------------------------------------------------------------------