├── .eslintrc.json ├── .github ├── FUNDING.yml ├── problemMatchers │ ├── eslint.json │ └── tsc.json └── workflows │ └── CI.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── docs ├── README.md ├── SUMMARY.md ├── api-reference │ ├── createtranscript.md │ └── generatefrommessages.md └── guides │ └── soon-tm.md ├── package.json ├── pnpm-lock.yaml ├── src ├── downloader │ └── images.ts ├── generator │ ├── index.tsx │ ├── renderers │ │ ├── attachment.tsx │ │ ├── components.tsx │ │ ├── content.tsx │ │ ├── embed.tsx │ │ ├── message.tsx │ │ ├── reply.tsx │ │ └── systemMessage.tsx │ └── transcript.tsx ├── index.ts ├── static │ └── client.ts ├── types.ts └── utils │ ├── buildProfiles.ts │ ├── embeds.ts │ ├── extend.ts │ ├── types.d.ts │ └── utils.ts ├── tests ├── README.md └── generate.ts ├── tsconfig.eslint.json └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 7 | "parser": "@typescript-eslint/parser", 8 | "ignorePatterns": ["dist"], 9 | "parserOptions": { 10 | "project": "./tsconfig.eslint.json" 11 | }, 12 | "plugins": ["@typescript-eslint"], 13 | "rules": { 14 | "@typescript-eslint/switch-exhaustiveness-check": "error", 15 | "@typescript-eslint/consistent-type-imports": "error", 16 | "@typescript-eslint/no-non-null-assertion": "off" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ItzDerock 2 | ko_fi: derock 3 | -------------------------------------------------------------------------------- /.github/problemMatchers/eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "eslint-stylish", 5 | "pattern": [ 6 | { 7 | "regexp": "^([^\\s].*)$", 8 | "file": 1 9 | }, 10 | { 11 | "regexp": "^\\s+(\\d+):(\\d+)\\s+(error|warning|info)\\s+(.*)\\s\\s+(.*)$", 12 | "line": 1, 13 | "column": 2, 14 | "severity": 3, 15 | "message": 4, 16 | "code": 5, 17 | "loop": true 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.github/problemMatchers/tsc.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "tsc", 5 | "pattern": [ 6 | { 7 | "regexp": "^(?:\\s+\\d+\\>)?([^\\s].*)\\((\\d+|\\d+,\\d+|\\d+,\\d+,\\d+,\\d+)\\)\\s*:\\s+(error|warning|info)\\s+(\\w{1,2}\\d+)\\s*:\\s*(.*)$", 8 | "file": 1, 9 | "location": 2, 10 | "severity": 3, 11 | "code": 4, 12 | "message": 5 13 | } 14 | ] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | Linting: 11 | name: Linting 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout Project 15 | uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3 16 | - name: Add problem matcher 17 | run: echo "::add-matcher::.github/problemMatchers/eslint.json" 18 | - name: Use Node.js v20 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: 20 22 | 23 | - uses: pnpm/action-setup@v3 24 | with: 25 | version: 8 26 | 27 | - name: Install Dependencies 28 | run: pnpm install --frozen-lockfile 29 | 30 | - name: Run ESLint 31 | run: pnpm run lint --fix=false 32 | 33 | TypeCheck: 34 | name: TypeCheck 35 | runs-on: ubuntu-latest 36 | steps: 37 | - name: Checkout Project 38 | uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3 39 | - name: Add problem matcher 40 | run: echo "::add-matcher::.github/problemMatchers/tsc.json" 41 | - name: Use Node.js v20 42 | uses: actions/setup-node@v4 43 | with: 44 | node-version: 20 45 | - uses: pnpm/action-setup@v3 46 | with: 47 | version: 8 48 | 49 | - name: Install Dependencies 50 | run: pnpm install --frozen-lockfile 51 | 52 | - name: Validate Types 53 | run: pnpm run typecheck 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | 3 | # Created by https://www.toptal.com/developers/gitignore/api/node,macos 4 | # Edit at https://www.toptal.com/developers/gitignore?templates=node,macos 5 | 6 | ### macOS ### 7 | # General 8 | .DS_Store 9 | .AppleDouble 10 | .LSOverride 11 | 12 | # Icon must end with two \r 13 | Icon 14 | 15 | 16 | # Thumbnails 17 | ._* 18 | 19 | # Files that might appear in the root of a volume 20 | .DocumentRevisions-V100 21 | .fseventsd 22 | .Spotlight-V100 23 | .TemporaryItems 24 | .Trashes 25 | .VolumeIcon.icns 26 | .com.apple.timemachine.donotpresent 27 | 28 | # Directories potentially created on remote AFP share 29 | .AppleDB 30 | .AppleDesktop 31 | Network Trash Folder 32 | Temporary Items 33 | .apdisk 34 | 35 | ### macOS Patch ### 36 | # iCloud generated files 37 | *.icloud 38 | 39 | ### Node ### 40 | # Logs 41 | logs 42 | *.log 43 | npm-debug.log* 44 | yarn-debug.log* 45 | yarn-error.log* 46 | lerna-debug.log* 47 | .pnpm-debug.log* 48 | 49 | # Diagnostic reports (https://nodejs.org/api/report.html) 50 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 51 | 52 | # Runtime data 53 | pids 54 | *.pid 55 | *.seed 56 | *.pid.lock 57 | 58 | # Directory for instrumented libs generated by jscoverage/JSCover 59 | lib-cov 60 | 61 | # Coverage directory used by tools like istanbul 62 | coverage 63 | *.lcov 64 | 65 | # nyc test coverage 66 | .nyc_output 67 | 68 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 69 | .grunt 70 | 71 | # Bower dependency directory (https://bower.io/) 72 | bower_components 73 | 74 | # node-waf configuration 75 | .lock-wscript 76 | 77 | # Compiled binary addons (https://nodejs.org/api/addons.html) 78 | build/Release 79 | 80 | # Dependency directories 81 | node_modules/ 82 | jspm_packages/ 83 | 84 | # Snowpack dependency directory (https://snowpack.dev/) 85 | web_modules/ 86 | 87 | # TypeScript cache 88 | *.tsbuildinfo 89 | 90 | # Optional npm cache directory 91 | .npm 92 | 93 | # Optional eslint cache 94 | .eslintcache 95 | 96 | # Optional stylelint cache 97 | .stylelintcache 98 | 99 | # Microbundle cache 100 | .rpt2_cache/ 101 | .rts2_cache_cjs/ 102 | .rts2_cache_es/ 103 | .rts2_cache_umd/ 104 | 105 | # Optional REPL history 106 | .node_repl_history 107 | 108 | # Output of 'npm pack' 109 | *.tgz 110 | 111 | # Yarn Integrity file 112 | .yarn-integrity 113 | 114 | # dotenv environment variable files 115 | .env 116 | .env.development.local 117 | .env.test.local 118 | .env.production.local 119 | .env.local 120 | 121 | # parcel-bundler cache (https://parceljs.org/) 122 | .cache 123 | .parcel-cache 124 | 125 | # Next.js build output 126 | .next 127 | out 128 | 129 | # Nuxt.js build / generate output 130 | .nuxt 131 | dist 132 | 133 | # Gatsby files 134 | .cache/ 135 | # Comment in the public line in if your project uses Gatsby and not Next.js 136 | # https://nextjs.org/blog/next-9-1#public-directory-support 137 | # public 138 | 139 | # vuepress build output 140 | .vuepress/dist 141 | 142 | # vuepress v2.x temp and cache directory 143 | .temp 144 | 145 | # Docusaurus cache and generated files 146 | .docusaurus 147 | 148 | # Serverless directories 149 | .serverless/ 150 | 151 | # FuseBox cache 152 | .fusebox/ 153 | 154 | # DynamoDB Local files 155 | .dynamodb/ 156 | 157 | # TernJS port file 158 | .tern-port 159 | 160 | # Stores VSCode versions used for testing VSCode extensions 161 | .vscode-test 162 | 163 | # yarn v2 164 | .yarn/cache 165 | .yarn/unplugged 166 | .yarn/build-state.yml 167 | .yarn/install-state.gz 168 | .pnp.* 169 | 170 | ### Node Patch ### 171 | # Serverless Webpack directories 172 | .webpack/ 173 | 174 | # Optional stylelint cache 175 | 176 | # SvelteKit build / generate output 177 | .svelte-kit 178 | 179 | # End of https://www.toptal.com/developers/gitignore/api/node,macos 180 | 181 | discord-html-transcripts-*.tgz -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | package-lock.json 3 | pnpm-lock.yaml -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "useTabs": false, 4 | "quoteProps": "as-needed", 5 | "trailingComma": "es5", 6 | "endOfLine": "lf", 7 | "singleQuote": true 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": "explicit", 5 | "source.organizeImports": "never" 6 | }, 7 | "typescript.tsdk": "node_modules/typescript/lib" 8 | } 9 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `discord-html-transcripts` 2 | 3 | [![Discord](https://img.shields.io/discord/555474311637499955?label=discord)](https://discord.gg/rf5qN7C) 4 | [![npm](https://img.shields.io/npm/dw/discord-html-transcripts)](http://npmjs.org/package/discord-html-transcripts) 5 | ![GitHub package.json version](https://img.shields.io/github/package-json/v/ItzDerock/discord-html-transcripts) 6 | ![GitHub Repo stars](https://img.shields.io/github/stars/ItzDerock/discord-html-transcripts?style=social) 7 | 8 | Discord HTML Transcripts is a node.js module to generate nice looking HTML transcripts. Processes discord markdown like **bold**, _italics_, ~~strikethroughs~~, and more. Nicely formats attachments and embeds. Built in XSS protection, preventing users from inserting arbitrary html tags. 9 | 10 | This module can format the following: 11 | 12 | - Discord flavored markdown 13 | - Uses [discord-markdown-parser](https://github.com/ItzDerock/discord-markdown-parser) 14 | - Allows for complex markdown syntax to be parsed properly 15 | - Embeds 16 | - System messages 17 | - Join messages 18 | - Message Pins 19 | - Boost messages 20 | - Slash commands 21 | - Will show the name of the command in the same style as Discord 22 | - Buttons 23 | - Reactions 24 | - Attachments 25 | - Images, videos, audio, and generic files 26 | - Replies 27 | - Mentions 28 | - Threads 29 | 30 | **This module is designed to work with [discord.js](https://discord.js.org/#/) v14/v15 _only_. If you need v13 support, roll back to v2.X.X** 31 | 32 | Styles from [@derockdev/discord-components](https://github.com/ItzDerock/discord-components). 33 | Behind the scenes, this package uses React SSR to generate a static site. 34 | 35 | ## 👋 Support 36 | 37 | Please do not DM me requesting support with this package, I will not respond. 38 | Instead, please open a thread on [this](https://discord.gg/MZQN8QMJg8) server. 39 | 40 | ## 🖨️ Example Output 41 | 42 | ![output](https://derock.media/r/6G6FIl.gif) 43 | 44 | ## 📝 Usage 45 | 46 | ### Example usage using the built in message fetcher. 47 | 48 | ```js 49 | const discordTranscripts = require('discord-html-transcripts'); 50 | // or (if using typescript) import * as discordTranscripts from 'discord-html-transcripts'; 51 | 52 | const channel = message.channel; // or however you get your TextChannel 53 | 54 | // Must be awaited 55 | const attachment = await discordTranscripts.createTranscript(channel); 56 | 57 | channel.send({ 58 | files: [attachment], 59 | }); 60 | ``` 61 | 62 | ### Or if you prefer, you can pass in your own messages. 63 | 64 | ```js 65 | const discordTranscripts = require('discord-html-transcripts'); 66 | // or (if using typescript) import * as discordTranscripts from 'discord-html-transcripts'; 67 | 68 | const messages = someWayToGetMessages(); // Must be Collection or Message[] 69 | const channel = someWayToGetChannel(); // Used for ticket name, guild icon, and guild name 70 | 71 | // Must be awaited 72 | const attachment = await discordTranscripts.generateFromMessages(messages, channel); 73 | 74 | channel.send({ 75 | files: [attachment], 76 | }); 77 | ``` 78 | 79 | ## ⚙️ Configuration 80 | 81 | Both methods of generating a transcript allow for an option object as the last parameter. 82 | **All configuration options are optional!** 83 | 84 | ### Built in Message Fetcher 85 | 86 | ```js 87 | const attachment = await discordTranscripts.createTranscript(channel, { 88 | limit: -1, // Max amount of messages to fetch. `-1` recursively fetches. 89 | returnType: 'attachment', // Valid options: 'buffer' | 'string' | 'attachment' Default: 'attachment' OR use the enum ExportReturnType 90 | filename: 'transcript.html', // Only valid with returnType is 'attachment'. Name of attachment. 91 | saveImages: false, // Download all images and include the image data in the HTML (allows viewing the image even after it has been deleted) (! WILL INCREASE FILE SIZE !) 92 | footerText: "Exported {number} message{s}", // Change text at footer, don't forget to put {number} to show how much messages got exported, and {s} for plural 93 | callbacks: { 94 | // register custom callbacks for the following: 95 | resolveChannel: (channelId: string) => Awaitable, 96 | resolveUser: (userId: string) => Awaitable, 97 | resolveRole: (roleId: string) => Awaitable 98 | }, 99 | poweredBy: true, // Whether to include the "Powered by discord-html-transcripts" footer 100 | hydrate: true, // Whether to hydrate the html server-side 101 | filter: (message) => true // Filter messages, e.g. (message) => !message.author.bot 102 | }); 103 | ``` 104 | 105 | ### Providing your own messages 106 | 107 | ```js 108 | const attachment = await discordTranscripts.generateFromMessages(messages, channel, { 109 | // Same as createTranscript, except no limit or filter 110 | }); 111 | ``` 112 | 113 | ## 🤝 Enjoy the package? 114 | 115 | Give it a star ⭐ and/or support me on [ko-fi](https://ko-fi.com/derock) 116 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Welcome to the official discord-html-transcripts documentation and guide. 3 | --- 4 | 5 | # 🏠 Home 6 | 7 | Discord HTML Transcripts is a node.js module to generate nice-looking HTML transcripts. Processes discord markdown like **bold**, _italics_, ~~strikethroughs~~, and more. Nicely formats attachments and embeds to match the look and feel of Discord. Includes built-in XSS protection, preventing users from inserting arbitrary HTML tags. 8 | 9 | ## Socials 10 | 11 | Join the [Discord Server](https://discord.gg/VgMxx2NAu4) and create a new forum post if you need help. You can also follow the announcement channel to get updates whenever a new version is released. 12 | 13 | If you find a bug, please create a post on [GitHub](https://github.com/ItzDerock/discord-html-transcripts/issues). 14 | 15 | ## Getting Started 16 | 17 | To get started with `discord-html-transcripts` install the module and ensure you are running `discord.js^14` 18 | 19 | {% tabs %} 20 | {% tab title="NPM" %} 21 | 22 | ```shell 23 | npm install --save discord-html-transcripts 24 | ``` 25 | 26 | {% endtab %} 27 | 28 | {% tab title="PNPM" %} 29 | 30 | ```shell 31 | pnpm i -S discord-html-transcripts` 32 | ``` 33 | 34 | {% endtab %} 35 | 36 | {% tab title="Yarn" %} 37 | 38 | ```shell 39 | yarn add discord-html-transcripts 40 | ``` 41 | 42 | {% endtab %} 43 | {% endtabs %} 44 | 45 | ## Contributing 46 | 47 | The code to this module can be found on GitHub: 48 | 49 | {% embed url="https://github.com/ItzDerock/discord-html-transcripts" %} 50 | ItzDerock/discord-html-transcripts 51 | {% endembed %} 52 | 53 | The markdown files to this documentation can also be found there. 54 | 55 | ## Useful Links 56 | 57 | {% content-ref url="broken-reference" %} 58 | [Broken link](broken-reference) 59 | {% endcontent-ref %} 60 | -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | - [🏠 Home](README.md) 4 | 5 | ## Guides 6 | 7 | - [soon™️](guides/soon-tm.md) 8 | 9 | ## API Reference 10 | 11 | - [createTranscript](api-reference/createtranscript.md) 12 | - [generateFromMessages](api-reference/generatefrommessages.md) 13 | -------------------------------------------------------------------------------- /docs/api-reference/createtranscript.md: -------------------------------------------------------------------------------- 1 | # createTranscript 2 | 3 | Will fetch (by default, all) the messages from the provided channel and can return either a `Buffer`, `string`, or `AttachmentBuilder` 4 | 5 | ## Example 6 | 7 | {% tabs %} 8 | {% tab title="JavaScript" %} 9 | {% code lineNumbers="true" %} 10 | 11 | ```javascript 12 | const discordTranscripts = require("discord-html-transcripts"); 13 | 14 | [...] 15 | 16 | // Notice the async here ⤵️ 17 | client.on('messageCreate', async (message) => { 18 | if (message.content === "!transcript") { 19 | // Use the following to fetch the transcript. 20 | const transcript = await discordTranscripts.createTranscript( 21 | message.channel, 22 | { 23 | // options go here 24 | // for example 25 | saveImages: true, 26 | footerText: "Saved {number} message{s}" 27 | } 28 | ); 29 | 30 | // and by default, createTranscript will return an AttachmentBuilder 31 | // which you can directly send to discord.js 32 | message.reply({ 33 | content: "Here's your transcript!", 34 | files: [transcript] 35 | }); 36 | } 37 | }); 38 | ``` 39 | 40 | {% endcode %} 41 | {% endtab %} 42 | 43 | {% tab title="TypeScript" %} 44 | {% code lineNumbers="true" %} 45 | 46 | ```typescript 47 | import * as discordTranscripts from "discord-html-transcripts"; 48 | 49 | [...] 50 | 51 | // Notice the async here ⤵️ 52 | client.on('messageCreate', async (message) => { 53 | if (message.content === "!transcript") { 54 | // Use the following to fetch the transcript. 55 | const transcript = await discordTranscripts.createTranscript( 56 | message.channel, 57 | { 58 | // options go here 59 | // for example 60 | saveImages: true, 61 | footerText: "Saved {number} message{s}" 62 | } 63 | ); 64 | 65 | // and by default, createTranscript will return an AttachmentBuilder 66 | // which you can directly send to discord.js 67 | message.reply({ 68 | content: "Here's your transcript!", 69 | files: [transcript] 70 | }); 71 | } 72 | }); 73 | ``` 74 | 75 | {% endcode %} 76 | {% endtab %} 77 | {% endtabs %} 78 | 79 | ## Parameters 80 | 81 | ```javascript 82 | createTranscript(channel, (options = {})); 83 | ``` 84 | 85 | ### `channel: TextBasedChannel` 86 | 87 | Defined in [discord.js](https://discord.js.org/#/docs/discord.js/main/typedef/GuildTextBasedChannel) as `TextChannel | NewsChannel | ThreadChannel | VoiceChannel`\ 88 | ``This is the channel Discord HTML Transcripts will fetch messages from. 89 | 90 | ### `options: CreateTranscriptOptions` 91 | 92 | The same options as [generatefrommessages.md](generatefrommessages.md 'mention') but adds the `limit` option which lets you limit set the number of messages to fetch. 93 | 94 | ### `options.limit: number` 95 | 96 | The number of messages to fetch. 97 | 98 | ### `options.filter: (message: Message) => boolean` 99 | 100 | A function that will be called for each message to determine if it should be included in the transcript. If false, the message will not be included. 101 | -------------------------------------------------------------------------------- /docs/api-reference/generatefrommessages.md: -------------------------------------------------------------------------------- 1 | # generateFromMessages 2 | 3 | If you want to provide your own messages for finer control of what `discord-html-transcripts` will save, use this function. 4 | 5 | ## Example 6 | 7 | {% tabs %} 8 | {% tab title="JavaScript" %} 9 | 10 | ```javascript 11 | const discordTranscripts = require("discord-html-transcripts"); 12 | const { Collection } = require("discord.js"); 13 | 14 | [...] 15 | 16 | const messages = new Collection(); 17 | const channel = /* somehow get this */; 18 | 19 | // somehow fill the messages collection 20 | 21 | const transcript = await discordTranscripts.generateFromMessages( 22 | messages, // the content in the transcript 23 | channel, // used for transcript title, etc 24 | { /* options */ } 25 | ); 26 | 27 | // By default returns an AttachmentBuilder that can be sent in a channel. 28 | channel.send({ 29 | files: [attachment] 30 | }); 31 | ``` 32 | 33 | {% endtab %} 34 | 35 | {% tab title="TypeScript" %} 36 | 37 | ```typescript 38 | import * as discordTranscripts from "discord-html-transcripts"; 39 | import { Collection, Message } from "discord.js"; 40 | 41 | [...] 42 | 43 | const messages = new Collection(); 44 | const channel = /* somehow get this */; 45 | 46 | // somehow fill the messages collection 47 | 48 | const transcript = await discordTranscripts.generateFromMessages( 49 | messages, // the content in the transcript 50 | channel, // used for transcript title, etc 51 | { /* options */ } 52 | ); 53 | 54 | // By default returns an AttachmentBuilder that can be sent in a channel. 55 | channel.send({ 56 | files: [attachment] 57 | }); 58 | ``` 59 | 60 | {% endtab %} 61 | {% endtabs %} 62 | 63 | ## Parameters 64 | 65 | ```javascript 66 | generateFromMessages(messages, channel, (options = {})); 67 | ``` 68 | 69 | ### `messages: Message[] | Collection` 70 | 71 | These are the messages that will be used in the body of the transcript. Can either be an array of discord.js [Message](https://discord.js.org/#/docs/discord.js/main/class/Message) objects or a [Collection](https://discord.js.org/#/docs/collection/main/class/Collection) of Messages. 72 | 73 | ### `channel: TextBasedChannel` 74 | 75 | Defined in [discord.js](https://discord.js.org/#/docs/discord.js/main/typedef/GuildTextBasedChannel) as `TextChannel | NewsChannel | ThreadChannel | VoiceChannel`\ 76 | ``This is channel is used to grab information about the transcript, like guild name and icon, channel name, etc. 77 | 78 | ### `options: GenerateFromMessagesOptions` 79 | 80 | An object with the discord-html-transcripts configuration options. 81 | 82 |
83 | 84 | TLDR: quick summary of everything below. 85 | 86 | ```javascript 87 | const attachment = await discordTranscripts.createTranscript(channel, { 88 | returnType: 'attachment', // Valid options: 'buffer' | 'string' | 'attachment' Default: 'attachment' OR use the enum ExportReturnType 89 | filename: 'transcript.html', // Only valid with returnType is 'attachment'. Name of attachment. 90 | saveImages: false, // Download all images and include the image data in the HTML (allows viewing the image even after it has been deleted) (! WILL INCREASE FILE SIZE !) 91 | footerText: "Exported {number} message{s}", // Change text at footer, don't forget to put {number} to show how much messages got exported, and {s} for plural 92 | callbacks: { 93 | // register custom callbacks for the following: 94 | resolveChannel: (channelId: string) => Awaitable, 95 | resolveUser: (userId: string) => Awaitable, 96 | resolveRole: (roleId: string) => Awaitable 97 | }, 98 | poweredBy: true // Whether to include the "Powered by discord-html-transcripts" footer 99 | }); 100 | ``` 101 | 102 |
103 | 104 | #### `options.returnType: 'buffer' | 'string' | 'attachment'` 105 | 106 | It's recommended to use the `ExportReturnType` enum instead of passing in a string.\ 107 | This option determines what this function will return. 108 | 109 | - **buffer**: the HTML data as a buffer. 110 | - string: the HTML data as a string. 111 | - attachment: the HTML data as an `AttachmentBuilder` 112 | 113 | The default value is `attachment` 114 | 115 | #### `options.filename: string` 116 | 117 | The name of the output file when the return type is `attachment` 118 | 119 | The default value is `transcript-{channel id}.html` 120 | 121 | #### `options.saveImages: boolean` 122 | 123 | Enabling this option will make Discord HTML Transcripts download all image attachments. This is useful in use cases where the channel will be deleted which will wipe all images off of Discord's CDN, which will break images that aren't downloaded. 124 | 125 | **If you are uploading the transcript to discord,** enabling this option may cause issues. Your bot may hit the upload filesize limit since images take up a lot of space! 126 | 127 | The default value is `false` 128 | 129 | #### `options.footerText: string` 130 | 131 | The text that will be used in the footer of the transcript. You can use the following placeholders: 132 | 133 | - `{number}`: the total number of messages exported. Useful when you are using `createTranscript(...)` 134 | - `{s}`: Adds an s if the number is >0, otherwise it is replaced with nothing 135 | 136 | The default value is `Exported {number} message{s}` 137 | 138 | #### `options.poweredBy: boolean` 139 | 140 | Disabling this will remove the `Powered by discord-html-transcripts` in the footer. 141 | 142 | The default value is `true` 143 | 144 | #### `options.callbacks.resolveChannel: (channelId: string) => Awaitable` 145 | 146 | A custom function that will be used by the module whenever it needs to resolve a channel (for example, if someone mentions a channel) 147 | 148 | The default option uses `channel.client.channels.fetch(...)` function. 149 | 150 | #### `options.callbacks.resolveUser: (userId: string) => Awaitable` 151 | 152 | A custom function that will be used by the module whenever it needs to resolve a user (for example, if a user is mentioned) 153 | 154 | The default option uses `channel.client.users.fetch(...)` 155 | 156 | #### `options.callbacks.resolveRole: (roleId: string) => Awaitable` 157 | 158 | #### A custom function that will be used by the module whenever it needs to resolve a role (for example, if a role is mentioned) 159 | 160 | The default option uses `channel.guild?.roles.fetch(...)` 161 | -------------------------------------------------------------------------------- /docs/guides/soon-tm.md: -------------------------------------------------------------------------------- 1 | # soon™️ 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discord-html-transcripts", 3 | "version": "3.2.0", 4 | "description": "A nicely formatted html transcript generator for discord.js.", 5 | "main": "dist/index.js", 6 | "homepage": "https://github.com/ItzDerock/discord-html-transcripts", 7 | "types": "./dist/index.d.ts", 8 | "scripts": { 9 | "test": "echo \"Error: no test specified\" && exit 1", 10 | "build": "tsc -p tsconfig.json", 11 | "prepack": "npm run build", 12 | "test:typescript": "ts-node ./tests/generate.ts", 13 | "lint": "prettier --write --cache . && eslint --cache --fix .", 14 | "typecheck": "tsc -p tsconfig.eslint.json" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/ItzDerock/discord-html-transcripts.git" 19 | }, 20 | "keywords": [ 21 | "discord.js", 22 | "discord.js-transcripts", 23 | "discord.js-html-transcripts", 24 | "html-transcripts", 25 | "discord-html-transcripts", 26 | "discord-transcripts" 27 | ], 28 | "author": "Derock ", 29 | "license": "GNU GPLv3", 30 | "files": [ 31 | "dist/**/*.js", 32 | "dist/**/*.d.ts", 33 | "dist/**/*.js.map" 34 | ], 35 | "devDependencies": { 36 | "@types/debug": "^4.1.12", 37 | "@types/node": "^20.11.17", 38 | "@types/react": "^18.2.47", 39 | "@types/react-dom": "^18.2.18", 40 | "@typescript-eslint/eslint-plugin": "^5.62.0", 41 | "@typescript-eslint/parser": "^5.62.0", 42 | "debug": "^4.3.4", 43 | "discord.js": "^14.14.1", 44 | "dotenv": "^16.3.1", 45 | "eslint": "^8.56.0", 46 | "husky": "^8.0.3", 47 | "prettier": "^2.8.8", 48 | "pretty-quick": "^3.1.3", 49 | "sharp": "^0.33.2", 50 | "ts-node": "^10.9.2", 51 | "typescript": "^5.3.3" 52 | }, 53 | "dependencies": { 54 | "@derockdev/discord-components-core": "^3.6.1", 55 | "@derockdev/discord-components-react": "^3.6.1", 56 | "discord-markdown-parser": "~1.1.0", 57 | "react": "0.0.0-experimental-6639ed3b3-20240111", 58 | "react-dom": "0.0.0-experimental-6639ed3b3-20240111", 59 | "simple-markdown": "^0.7.3", 60 | "twemoji": "^14.0.2", 61 | "undici": "^7.9.0" 62 | }, 63 | "peerDependencies": { 64 | "discord.js": "^14.0.0 || ^15.0.0" 65 | }, 66 | "overrides": { 67 | "react": "$react" 68 | }, 69 | "packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977" 70 | } 71 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | '@derockdev/discord-components-core': 12 | specifier: ^3.6.1 13 | version: 3.6.1 14 | '@derockdev/discord-components-react': 15 | specifier: ^3.6.1 16 | version: 3.6.1(react-dom@0.0.0-experimental-6639ed3b3-20240111(react@0.0.0-experimental-6639ed3b3-20240111))(react@0.0.0-experimental-6639ed3b3-20240111) 17 | discord-markdown-parser: 18 | specifier: ~1.1.0 19 | version: 1.1.0 20 | react: 21 | specifier: 0.0.0-experimental-6639ed3b3-20240111 22 | version: 0.0.0-experimental-6639ed3b3-20240111 23 | react-dom: 24 | specifier: 0.0.0-experimental-6639ed3b3-20240111 25 | version: 0.0.0-experimental-6639ed3b3-20240111(react@0.0.0-experimental-6639ed3b3-20240111) 26 | simple-markdown: 27 | specifier: ^0.7.3 28 | version: 0.7.3 29 | twemoji: 30 | specifier: ^14.0.2 31 | version: 14.0.2 32 | undici: 33 | specifier: ^7.9.0 34 | version: 7.9.0 35 | devDependencies: 36 | '@types/debug': 37 | specifier: ^4.1.12 38 | version: 4.1.12 39 | '@types/node': 40 | specifier: ^20.11.17 41 | version: 20.17.47 42 | '@types/react': 43 | specifier: ^18.2.47 44 | version: 18.3.21 45 | '@types/react-dom': 46 | specifier: ^18.2.18 47 | version: 18.3.7(@types/react@18.3.21) 48 | '@typescript-eslint/eslint-plugin': 49 | specifier: ^5.62.0 50 | version: 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3) 51 | '@typescript-eslint/parser': 52 | specifier: ^5.62.0 53 | version: 5.62.0(eslint@8.57.1)(typescript@5.8.3) 54 | debug: 55 | specifier: ^4.3.4 56 | version: 4.4.1 57 | discord.js: 58 | specifier: ^14.14.1 59 | version: 14.19.3 60 | dotenv: 61 | specifier: ^16.3.1 62 | version: 16.5.0 63 | eslint: 64 | specifier: ^8.56.0 65 | version: 8.57.1 66 | husky: 67 | specifier: ^8.0.3 68 | version: 8.0.3 69 | prettier: 70 | specifier: ^2.8.8 71 | version: 2.8.8 72 | pretty-quick: 73 | specifier: ^3.1.3 74 | version: 3.3.1(prettier@2.8.8) 75 | sharp: 76 | specifier: ^0.33.2 77 | version: 0.33.5 78 | ts-node: 79 | specifier: ^10.9.2 80 | version: 10.9.2(@types/node@20.17.47)(typescript@5.8.3) 81 | typescript: 82 | specifier: ^5.3.3 83 | version: 5.8.3 84 | 85 | packages: 86 | 87 | '@cspotcode/source-map-support@0.8.1': 88 | resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} 89 | engines: {node: '>=12'} 90 | 91 | '@derockdev/discord-components-core@3.6.1': 92 | resolution: {integrity: sha512-qLcoab2Olui1IzJavnPzMgZzopWU21D3VDthkFgzZyiID4C5+OiSWx6ZNxz6wnMKfv/253AsXg8opdCwoRJKgg==} 93 | engines: {node: '>=v14.0.0'} 94 | 95 | '@derockdev/discord-components-react@3.6.1': 96 | resolution: {integrity: sha512-+EIHAo5wgXbVwJVgsRohi5/ZcWwrzzCPlV45c1lDL5iOvuuHDZKuPXJdUCdxUJBUpd2zxhcvjBXEZIlJqTe+sA==} 97 | engines: {node: '>=v14.0.0'} 98 | peerDependencies: 99 | react: 16.8.x || 17.x || 18.x 100 | react-dom: 16.8.x || 17.x || 18.x 101 | 102 | '@discordjs/builders@1.11.2': 103 | resolution: {integrity: sha512-F1WTABdd8/R9D1icJzajC4IuLyyS8f3rTOz66JsSI3pKvpCAtsMBweu8cyNYsIyvcrKAVn9EPK+Psoymq+XC0A==} 104 | engines: {node: '>=16.11.0'} 105 | 106 | '@discordjs/collection@1.5.3': 107 | resolution: {integrity: sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==} 108 | engines: {node: '>=16.11.0'} 109 | 110 | '@discordjs/collection@2.1.1': 111 | resolution: {integrity: sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==} 112 | engines: {node: '>=18'} 113 | 114 | '@discordjs/formatters@0.6.1': 115 | resolution: {integrity: sha512-5cnX+tASiPCqCWtFcFslxBVUaCetB0thvM/JyavhbXInP1HJIEU+Qv/zMrnuwSsX3yWH2lVXNJZeDK3EiP4HHg==} 116 | engines: {node: '>=16.11.0'} 117 | 118 | '@discordjs/rest@2.5.0': 119 | resolution: {integrity: sha512-PWhchxTzpn9EV3vvPRpwS0EE2rNYB9pvzDU/eLLW3mByJl0ZHZjHI2/wA8EbH2gRMQV7nu+0FoDF84oiPl8VAQ==} 120 | engines: {node: '>=18'} 121 | 122 | '@discordjs/util@1.1.1': 123 | resolution: {integrity: sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==} 124 | engines: {node: '>=18'} 125 | 126 | '@discordjs/ws@1.2.2': 127 | resolution: {integrity: sha512-dyfq7yn0wO0IYeYOs3z79I6/HumhmKISzFL0Z+007zQJMtAFGtt3AEoq1nuLXtcunUE5YYYQqgKvybXukAK8/w==} 128 | engines: {node: '>=16.11.0'} 129 | 130 | '@emnapi/runtime@1.4.3': 131 | resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} 132 | 133 | '@eslint-community/eslint-utils@4.7.0': 134 | resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} 135 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 136 | peerDependencies: 137 | eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 138 | 139 | '@eslint-community/regexpp@4.12.1': 140 | resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} 141 | engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 142 | 143 | '@eslint/eslintrc@2.1.4': 144 | resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} 145 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 146 | 147 | '@eslint/js@8.57.1': 148 | resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} 149 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 150 | 151 | '@humanwhocodes/config-array@0.13.0': 152 | resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} 153 | engines: {node: '>=10.10.0'} 154 | deprecated: Use @eslint/config-array instead 155 | 156 | '@humanwhocodes/module-importer@1.0.1': 157 | resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 158 | engines: {node: '>=12.22'} 159 | 160 | '@humanwhocodes/object-schema@2.0.3': 161 | resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} 162 | deprecated: Use @eslint/object-schema instead 163 | 164 | '@img/sharp-darwin-arm64@0.33.5': 165 | resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} 166 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 167 | cpu: [arm64] 168 | os: [darwin] 169 | 170 | '@img/sharp-darwin-x64@0.33.5': 171 | resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} 172 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 173 | cpu: [x64] 174 | os: [darwin] 175 | 176 | '@img/sharp-libvips-darwin-arm64@1.0.4': 177 | resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} 178 | cpu: [arm64] 179 | os: [darwin] 180 | 181 | '@img/sharp-libvips-darwin-x64@1.0.4': 182 | resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} 183 | cpu: [x64] 184 | os: [darwin] 185 | 186 | '@img/sharp-libvips-linux-arm64@1.0.4': 187 | resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} 188 | cpu: [arm64] 189 | os: [linux] 190 | 191 | '@img/sharp-libvips-linux-arm@1.0.5': 192 | resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} 193 | cpu: [arm] 194 | os: [linux] 195 | 196 | '@img/sharp-libvips-linux-s390x@1.0.4': 197 | resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} 198 | cpu: [s390x] 199 | os: [linux] 200 | 201 | '@img/sharp-libvips-linux-x64@1.0.4': 202 | resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} 203 | cpu: [x64] 204 | os: [linux] 205 | 206 | '@img/sharp-libvips-linuxmusl-arm64@1.0.4': 207 | resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} 208 | cpu: [arm64] 209 | os: [linux] 210 | 211 | '@img/sharp-libvips-linuxmusl-x64@1.0.4': 212 | resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} 213 | cpu: [x64] 214 | os: [linux] 215 | 216 | '@img/sharp-linux-arm64@0.33.5': 217 | resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} 218 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 219 | cpu: [arm64] 220 | os: [linux] 221 | 222 | '@img/sharp-linux-arm@0.33.5': 223 | resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} 224 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 225 | cpu: [arm] 226 | os: [linux] 227 | 228 | '@img/sharp-linux-s390x@0.33.5': 229 | resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} 230 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 231 | cpu: [s390x] 232 | os: [linux] 233 | 234 | '@img/sharp-linux-x64@0.33.5': 235 | resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} 236 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 237 | cpu: [x64] 238 | os: [linux] 239 | 240 | '@img/sharp-linuxmusl-arm64@0.33.5': 241 | resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} 242 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 243 | cpu: [arm64] 244 | os: [linux] 245 | 246 | '@img/sharp-linuxmusl-x64@0.33.5': 247 | resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} 248 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 249 | cpu: [x64] 250 | os: [linux] 251 | 252 | '@img/sharp-wasm32@0.33.5': 253 | resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} 254 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 255 | cpu: [wasm32] 256 | 257 | '@img/sharp-win32-ia32@0.33.5': 258 | resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} 259 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 260 | cpu: [ia32] 261 | os: [win32] 262 | 263 | '@img/sharp-win32-x64@0.33.5': 264 | resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} 265 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 266 | cpu: [x64] 267 | os: [win32] 268 | 269 | '@jridgewell/resolve-uri@3.1.2': 270 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 271 | engines: {node: '>=6.0.0'} 272 | 273 | '@jridgewell/sourcemap-codec@1.5.0': 274 | resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 275 | 276 | '@jridgewell/trace-mapping@0.3.9': 277 | resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} 278 | 279 | '@nodelib/fs.scandir@2.1.5': 280 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 281 | engines: {node: '>= 8'} 282 | 283 | '@nodelib/fs.stat@2.0.5': 284 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 285 | engines: {node: '>= 8'} 286 | 287 | '@nodelib/fs.walk@1.2.8': 288 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 289 | engines: {node: '>= 8'} 290 | 291 | '@sapphire/async-queue@1.5.5': 292 | resolution: {integrity: sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==} 293 | engines: {node: '>=v14.0.0', npm: '>=7.0.0'} 294 | 295 | '@sapphire/shapeshift@4.0.0': 296 | resolution: {integrity: sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==} 297 | engines: {node: '>=v16'} 298 | 299 | '@sapphire/snowflake@3.5.3': 300 | resolution: {integrity: sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==} 301 | engines: {node: '>=v14.0.0', npm: '>=7.0.0'} 302 | 303 | '@stencil/core@3.4.2': 304 | resolution: {integrity: sha512-FAUhUVaakCy29nU2GwO/HQBRV1ihPRvncz3PUc8oR+UJLAxGabTmP8PLY7wvHfbw+Cvi4VXfJFTBvdfDu6iKPQ==} 305 | engines: {node: '>=14.10.0', npm: '>=6.0.0'} 306 | hasBin: true 307 | 308 | '@tsconfig/node10@1.0.11': 309 | resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} 310 | 311 | '@tsconfig/node12@1.0.11': 312 | resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} 313 | 314 | '@tsconfig/node14@1.0.3': 315 | resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} 316 | 317 | '@tsconfig/node16@1.0.4': 318 | resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} 319 | 320 | '@types/debug@4.1.12': 321 | resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} 322 | 323 | '@types/json-schema@7.0.15': 324 | resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} 325 | 326 | '@types/ms@2.1.0': 327 | resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} 328 | 329 | '@types/node@20.17.47': 330 | resolution: {integrity: sha512-3dLX0Upo1v7RvUimvxLeXqwrfyKxUINk0EAM83swP2mlSUcwV73sZy8XhNz8bcZ3VbsfQyC/y6jRdL5tgCNpDQ==} 331 | 332 | '@types/prop-types@15.7.14': 333 | resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} 334 | 335 | '@types/react-dom@18.3.7': 336 | resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} 337 | peerDependencies: 338 | '@types/react': ^18.0.0 339 | 340 | '@types/react@18.3.21': 341 | resolution: {integrity: sha512-gXLBtmlcRJeT09/sI4PxVwyrku6SaNUj/6cMubjE6T6XdY1fDmBL7r0nX0jbSZPU/Xr0KuwLLZh6aOYY5d91Xw==} 342 | 343 | '@types/semver@7.7.0': 344 | resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==} 345 | 346 | '@types/ws@8.18.1': 347 | resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} 348 | 349 | '@typescript-eslint/eslint-plugin@5.62.0': 350 | resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} 351 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 352 | peerDependencies: 353 | '@typescript-eslint/parser': ^5.0.0 354 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 355 | typescript: '*' 356 | peerDependenciesMeta: 357 | typescript: 358 | optional: true 359 | 360 | '@typescript-eslint/parser@5.62.0': 361 | resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} 362 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 363 | peerDependencies: 364 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 365 | typescript: '*' 366 | peerDependenciesMeta: 367 | typescript: 368 | optional: true 369 | 370 | '@typescript-eslint/scope-manager@5.62.0': 371 | resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} 372 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 373 | 374 | '@typescript-eslint/type-utils@5.62.0': 375 | resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} 376 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 377 | peerDependencies: 378 | eslint: '*' 379 | typescript: '*' 380 | peerDependenciesMeta: 381 | typescript: 382 | optional: true 383 | 384 | '@typescript-eslint/types@5.62.0': 385 | resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} 386 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 387 | 388 | '@typescript-eslint/typescript-estree@5.62.0': 389 | resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} 390 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 391 | peerDependencies: 392 | typescript: '*' 393 | peerDependenciesMeta: 394 | typescript: 395 | optional: true 396 | 397 | '@typescript-eslint/utils@5.62.0': 398 | resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} 399 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 400 | peerDependencies: 401 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 402 | 403 | '@typescript-eslint/visitor-keys@5.62.0': 404 | resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} 405 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 406 | 407 | '@ungap/structured-clone@1.3.0': 408 | resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} 409 | 410 | '@vladfrangu/async_event_emitter@2.4.6': 411 | resolution: {integrity: sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==} 412 | engines: {node: '>=v14.0.0', npm: '>=7.0.0'} 413 | 414 | acorn-jsx@5.3.2: 415 | resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 416 | peerDependencies: 417 | acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 418 | 419 | acorn-walk@8.3.4: 420 | resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} 421 | engines: {node: '>=0.4.0'} 422 | 423 | acorn@8.14.1: 424 | resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} 425 | engines: {node: '>=0.4.0'} 426 | hasBin: true 427 | 428 | ajv@6.12.6: 429 | resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 430 | 431 | ansi-regex@5.0.1: 432 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 433 | engines: {node: '>=8'} 434 | 435 | ansi-styles@4.3.0: 436 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 437 | engines: {node: '>=8'} 438 | 439 | arg@4.1.3: 440 | resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} 441 | 442 | argparse@2.0.1: 443 | resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 444 | 445 | array-union@2.1.0: 446 | resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} 447 | engines: {node: '>=8'} 448 | 449 | balanced-match@1.0.2: 450 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 451 | 452 | brace-expansion@1.1.11: 453 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 454 | 455 | braces@3.0.3: 456 | resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 457 | engines: {node: '>=8'} 458 | 459 | callsites@3.1.0: 460 | resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 461 | engines: {node: '>=6'} 462 | 463 | chalk@4.1.2: 464 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 465 | engines: {node: '>=10'} 466 | 467 | clsx@1.2.1: 468 | resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} 469 | engines: {node: '>=6'} 470 | 471 | color-convert@2.0.1: 472 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 473 | engines: {node: '>=7.0.0'} 474 | 475 | color-name@1.1.4: 476 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 477 | 478 | color-string@1.9.1: 479 | resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} 480 | 481 | color@4.2.3: 482 | resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} 483 | engines: {node: '>=12.5.0'} 484 | 485 | concat-map@0.0.1: 486 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 487 | 488 | create-require@1.1.1: 489 | resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} 490 | 491 | cross-spawn@7.0.6: 492 | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 493 | engines: {node: '>= 8'} 494 | 495 | csstype@3.1.3: 496 | resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} 497 | 498 | debug@4.4.1: 499 | resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} 500 | engines: {node: '>=6.0'} 501 | peerDependencies: 502 | supports-color: '*' 503 | peerDependenciesMeta: 504 | supports-color: 505 | optional: true 506 | 507 | deep-is@0.1.4: 508 | resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 509 | 510 | detect-libc@2.0.4: 511 | resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} 512 | engines: {node: '>=8'} 513 | 514 | diff@4.0.2: 515 | resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} 516 | engines: {node: '>=0.3.1'} 517 | 518 | dir-glob@3.0.1: 519 | resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} 520 | engines: {node: '>=8'} 521 | 522 | discord-api-types@0.38.8: 523 | resolution: {integrity: sha512-xuRXPD44FcbKHrQK15FS1HFlMRNJtsaZou/SVws18vQ7zHqmlxyDktMkZpyvD6gE2ctGOVYC/jUyoMMAyBWfcw==} 524 | 525 | discord-markdown-parser@1.1.0: 526 | resolution: {integrity: sha512-o2+iFgt5qer6UYY5hVTPGq2mGzleKRGYKcvymg67FdKg4AMJ061KbebKunCERWKjx79dmNHMDnGV2F0DRGCNkw==} 527 | 528 | discord.js@14.19.3: 529 | resolution: {integrity: sha512-lncTRk0k+8Q5D3nThnODBR8fR8x2fM798o8Vsr40Krx0DjPwpZCuxxTcFMrXMQVOqM1QB9wqWgaXPg3TbmlHqA==} 530 | engines: {node: '>=18'} 531 | 532 | doctrine@3.0.0: 533 | resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} 534 | engines: {node: '>=6.0.0'} 535 | 536 | dotenv@16.5.0: 537 | resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} 538 | engines: {node: '>=12'} 539 | 540 | end-of-stream@1.4.4: 541 | resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} 542 | 543 | escape-string-regexp@4.0.0: 544 | resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 545 | engines: {node: '>=10'} 546 | 547 | eslint-scope@5.1.1: 548 | resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} 549 | engines: {node: '>=8.0.0'} 550 | 551 | eslint-scope@7.2.2: 552 | resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} 553 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 554 | 555 | eslint-visitor-keys@3.4.3: 556 | resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 557 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 558 | 559 | eslint@8.57.1: 560 | resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} 561 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 562 | deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. 563 | hasBin: true 564 | 565 | espree@9.6.1: 566 | resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} 567 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 568 | 569 | esquery@1.6.0: 570 | resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} 571 | engines: {node: '>=0.10'} 572 | 573 | esrecurse@4.3.0: 574 | resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} 575 | engines: {node: '>=4.0'} 576 | 577 | estraverse@4.3.0: 578 | resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} 579 | engines: {node: '>=4.0'} 580 | 581 | estraverse@5.3.0: 582 | resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} 583 | engines: {node: '>=4.0'} 584 | 585 | esutils@2.0.3: 586 | resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} 587 | engines: {node: '>=0.10.0'} 588 | 589 | execa@4.1.0: 590 | resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} 591 | engines: {node: '>=10'} 592 | 593 | fast-deep-equal@3.1.3: 594 | resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 595 | 596 | fast-glob@3.3.3: 597 | resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} 598 | engines: {node: '>=8.6.0'} 599 | 600 | fast-json-stable-stringify@2.1.0: 601 | resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 602 | 603 | fast-levenshtein@2.0.6: 604 | resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 605 | 606 | fastq@1.19.1: 607 | resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} 608 | 609 | file-entry-cache@6.0.1: 610 | resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} 611 | engines: {node: ^10.12.0 || >=12.0.0} 612 | 613 | fill-range@7.1.1: 614 | resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 615 | engines: {node: '>=8'} 616 | 617 | find-up@4.1.0: 618 | resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} 619 | engines: {node: '>=8'} 620 | 621 | find-up@5.0.0: 622 | resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 623 | engines: {node: '>=10'} 624 | 625 | flat-cache@3.2.0: 626 | resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} 627 | engines: {node: ^10.12.0 || >=12.0.0} 628 | 629 | flatted@3.3.3: 630 | resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} 631 | 632 | fs-extra@8.1.0: 633 | resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} 634 | engines: {node: '>=6 <7 || >=8'} 635 | 636 | fs.realpath@1.0.0: 637 | resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 638 | 639 | get-stream@5.2.0: 640 | resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} 641 | engines: {node: '>=8'} 642 | 643 | glob-parent@5.1.2: 644 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 645 | engines: {node: '>= 6'} 646 | 647 | glob-parent@6.0.2: 648 | resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 649 | engines: {node: '>=10.13.0'} 650 | 651 | glob@7.2.3: 652 | resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 653 | deprecated: Glob versions prior to v9 are no longer supported 654 | 655 | globals@13.24.0: 656 | resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} 657 | engines: {node: '>=8'} 658 | 659 | globby@11.1.0: 660 | resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} 661 | engines: {node: '>=10'} 662 | 663 | graceful-fs@4.2.11: 664 | resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} 665 | 666 | graphemer@1.4.0: 667 | resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} 668 | 669 | has-flag@4.0.0: 670 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 671 | engines: {node: '>=8'} 672 | 673 | hex-to-rgba@2.0.1: 674 | resolution: {integrity: sha512-5XqPJBpsEUMsseJUi2w2Hl7cHFFi3+OO10M2pzAvKB1zL6fc+koGMhmBqoDOCB4GemiRM/zvDMRIhVw6EkB8dQ==} 675 | 676 | highlight.js@11.11.1: 677 | resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} 678 | engines: {node: '>=12.0.0'} 679 | 680 | human-signals@1.1.1: 681 | resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} 682 | engines: {node: '>=8.12.0'} 683 | 684 | husky@8.0.3: 685 | resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} 686 | engines: {node: '>=14'} 687 | hasBin: true 688 | 689 | ignore@5.3.2: 690 | resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} 691 | engines: {node: '>= 4'} 692 | 693 | import-fresh@3.3.1: 694 | resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} 695 | engines: {node: '>=6'} 696 | 697 | imurmurhash@0.1.4: 698 | resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 699 | engines: {node: '>=0.8.19'} 700 | 701 | inflight@1.0.6: 702 | resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 703 | deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. 704 | 705 | inherits@2.0.4: 706 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 707 | 708 | is-arrayish@0.3.2: 709 | resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} 710 | 711 | is-extglob@2.1.1: 712 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 713 | engines: {node: '>=0.10.0'} 714 | 715 | is-glob@4.0.3: 716 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 717 | engines: {node: '>=0.10.0'} 718 | 719 | is-number@7.0.0: 720 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 721 | engines: {node: '>=0.12.0'} 722 | 723 | is-path-inside@3.0.3: 724 | resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} 725 | engines: {node: '>=8'} 726 | 727 | is-stream@2.0.1: 728 | resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} 729 | engines: {node: '>=8'} 730 | 731 | isexe@2.0.0: 732 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 733 | 734 | js-tokens@4.0.0: 735 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 736 | 737 | js-yaml@4.1.0: 738 | resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} 739 | hasBin: true 740 | 741 | json-buffer@3.0.1: 742 | resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} 743 | 744 | json-schema-traverse@0.4.1: 745 | resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 746 | 747 | json-stable-stringify-without-jsonify@1.0.1: 748 | resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} 749 | 750 | jsonfile@4.0.0: 751 | resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} 752 | 753 | jsonfile@5.0.0: 754 | resolution: {integrity: sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==} 755 | 756 | keyv@4.5.4: 757 | resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} 758 | 759 | levn@0.4.1: 760 | resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} 761 | engines: {node: '>= 0.8.0'} 762 | 763 | locate-path@5.0.0: 764 | resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} 765 | engines: {node: '>=8'} 766 | 767 | locate-path@6.0.0: 768 | resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 769 | engines: {node: '>=10'} 770 | 771 | lodash.merge@4.6.2: 772 | resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 773 | 774 | lodash.snakecase@4.1.1: 775 | resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} 776 | 777 | lodash@4.17.21: 778 | resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} 779 | 780 | loose-envify@1.4.0: 781 | resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} 782 | hasBin: true 783 | 784 | magic-bytes.js@1.12.1: 785 | resolution: {integrity: sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==} 786 | 787 | make-error@1.3.6: 788 | resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} 789 | 790 | merge-stream@2.0.0: 791 | resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} 792 | 793 | merge2@1.4.1: 794 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 795 | engines: {node: '>= 8'} 796 | 797 | micromatch@4.0.8: 798 | resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} 799 | engines: {node: '>=8.6'} 800 | 801 | mimic-fn@2.1.0: 802 | resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} 803 | engines: {node: '>=6'} 804 | 805 | minimatch@3.1.2: 806 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 807 | 808 | mri@1.2.0: 809 | resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} 810 | engines: {node: '>=4'} 811 | 812 | ms@2.1.3: 813 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 814 | 815 | natural-compare-lite@1.4.0: 816 | resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} 817 | 818 | natural-compare@1.4.0: 819 | resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 820 | 821 | npm-run-path@4.0.1: 822 | resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} 823 | engines: {node: '>=8'} 824 | 825 | once@1.4.0: 826 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 827 | 828 | onetime@5.1.2: 829 | resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} 830 | engines: {node: '>=6'} 831 | 832 | optionator@0.9.4: 833 | resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} 834 | engines: {node: '>= 0.8.0'} 835 | 836 | p-limit@2.3.0: 837 | resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} 838 | engines: {node: '>=6'} 839 | 840 | p-limit@3.1.0: 841 | resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 842 | engines: {node: '>=10'} 843 | 844 | p-locate@4.1.0: 845 | resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} 846 | engines: {node: '>=8'} 847 | 848 | p-locate@5.0.0: 849 | resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 850 | engines: {node: '>=10'} 851 | 852 | p-try@2.2.0: 853 | resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} 854 | engines: {node: '>=6'} 855 | 856 | parent-module@1.0.1: 857 | resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 858 | engines: {node: '>=6'} 859 | 860 | path-exists@4.0.0: 861 | resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 862 | engines: {node: '>=8'} 863 | 864 | path-is-absolute@1.0.1: 865 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 866 | engines: {node: '>=0.10.0'} 867 | 868 | path-key@3.1.1: 869 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 870 | engines: {node: '>=8'} 871 | 872 | path-type@4.0.0: 873 | resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} 874 | engines: {node: '>=8'} 875 | 876 | picocolors@1.1.1: 877 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 878 | 879 | picomatch@2.3.1: 880 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 881 | engines: {node: '>=8.6'} 882 | 883 | picomatch@3.0.1: 884 | resolution: {integrity: sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==} 885 | engines: {node: '>=10'} 886 | 887 | prelude-ls@1.2.1: 888 | resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 889 | engines: {node: '>= 0.8.0'} 890 | 891 | prettier@2.8.8: 892 | resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} 893 | engines: {node: '>=10.13.0'} 894 | hasBin: true 895 | 896 | pretty-quick@3.3.1: 897 | resolution: {integrity: sha512-3b36UXfYQ+IXXqex6mCca89jC8u0mYLqFAN5eTQKoXO6oCQYcIVYZEB/5AlBHI7JPYygReM2Vv6Vom/Gln7fBg==} 898 | engines: {node: '>=10.13'} 899 | hasBin: true 900 | peerDependencies: 901 | prettier: ^2.0.0 902 | 903 | pump@3.0.2: 904 | resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} 905 | 906 | punycode@2.3.1: 907 | resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 908 | engines: {node: '>=6'} 909 | 910 | queue-microtask@1.2.3: 911 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 912 | 913 | react-dom@0.0.0-experimental-6639ed3b3-20240111: 914 | resolution: {integrity: sha512-kXDE4i5kexrWyLtburrEcFkR+doI93EamQLy3I53uLJVvn5SLyfRSRhnjeJUcss4bKXez8pe5oOawUEAgozCIg==} 915 | peerDependencies: 916 | react: 0.0.0-experimental-6639ed3b3-20240111 917 | 918 | react@0.0.0-experimental-6639ed3b3-20240111: 919 | resolution: {integrity: sha512-CmzPub/g8+2MxDxzAqMY3wEup07abAguEgtfu0Ewu/cupZiMgaqTphFBQkhtLl1RyGfo6+r3XBQ6BeBNJtUEOA==} 920 | engines: {node: '>=0.10.0'} 921 | 922 | resolve-from@4.0.0: 923 | resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 924 | engines: {node: '>=4'} 925 | 926 | reusify@1.1.0: 927 | resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} 928 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 929 | 930 | rimraf@3.0.2: 931 | resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} 932 | deprecated: Rimraf versions prior to v4 are no longer supported 933 | hasBin: true 934 | 935 | run-parallel@1.2.0: 936 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 937 | 938 | scheduler@0.0.0-experimental-6639ed3b3-20240111: 939 | resolution: {integrity: sha512-yLzFojBxcQjgIe+uWBbaG54rx9LVODQPT2mSEdRYyoHwI/lUpqOdJViWHDX4QjSrYQlZhUtukDs+PoENJaiCcQ==} 940 | 941 | semver@7.7.2: 942 | resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} 943 | engines: {node: '>=10'} 944 | hasBin: true 945 | 946 | sharp@0.33.5: 947 | resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} 948 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 949 | 950 | shebang-command@2.0.0: 951 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 952 | engines: {node: '>=8'} 953 | 954 | shebang-regex@3.0.0: 955 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 956 | engines: {node: '>=8'} 957 | 958 | signal-exit@3.0.7: 959 | resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} 960 | 961 | simple-markdown@0.7.3: 962 | resolution: {integrity: sha512-uGXIc13NGpqfPeFJIt/7SHHxd6HekEJYtsdoCM06mEBPL9fQH/pSD7LRM6PZ7CKchpSvxKL4tvwMamqAaNDAyg==} 963 | 964 | simple-swizzle@0.2.2: 965 | resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} 966 | 967 | slash@3.0.0: 968 | resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 969 | engines: {node: '>=8'} 970 | 971 | strip-ansi@6.0.1: 972 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 973 | engines: {node: '>=8'} 974 | 975 | strip-final-newline@2.0.0: 976 | resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} 977 | engines: {node: '>=6'} 978 | 979 | strip-json-comments@3.1.1: 980 | resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 981 | engines: {node: '>=8'} 982 | 983 | supports-color@7.2.0: 984 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 985 | engines: {node: '>=8'} 986 | 987 | text-table@0.2.0: 988 | resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} 989 | 990 | to-regex-range@5.0.1: 991 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 992 | engines: {node: '>=8.0'} 993 | 994 | ts-mixer@6.0.4: 995 | resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} 996 | 997 | ts-node@10.9.2: 998 | resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} 999 | hasBin: true 1000 | peerDependencies: 1001 | '@swc/core': '>=1.2.50' 1002 | '@swc/wasm': '>=1.2.50' 1003 | '@types/node': '*' 1004 | typescript: '>=2.7' 1005 | peerDependenciesMeta: 1006 | '@swc/core': 1007 | optional: true 1008 | '@swc/wasm': 1009 | optional: true 1010 | 1011 | tslib@1.14.1: 1012 | resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} 1013 | 1014 | tslib@2.8.1: 1015 | resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 1016 | 1017 | tsutils@3.21.0: 1018 | resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} 1019 | engines: {node: '>= 6'} 1020 | peerDependencies: 1021 | typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' 1022 | 1023 | twemoji-parser@14.0.0: 1024 | resolution: {integrity: sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA==} 1025 | 1026 | twemoji@14.0.2: 1027 | resolution: {integrity: sha512-BzOoXIe1QVdmsUmZ54xbEH+8AgtOKUiG53zO5vVP2iUu6h5u9lN15NcuS6te4OY96qx0H7JK9vjjl9WQbkTRuA==} 1028 | 1029 | type-check@0.4.0: 1030 | resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 1031 | engines: {node: '>= 0.8.0'} 1032 | 1033 | type-fest@0.20.2: 1034 | resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} 1035 | engines: {node: '>=10'} 1036 | 1037 | typescript@5.8.3: 1038 | resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} 1039 | engines: {node: '>=14.17'} 1040 | hasBin: true 1041 | 1042 | undici-types@6.19.8: 1043 | resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} 1044 | 1045 | undici@6.21.1: 1046 | resolution: {integrity: sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==} 1047 | engines: {node: '>=18.17'} 1048 | 1049 | undici@7.9.0: 1050 | resolution: {integrity: sha512-e696y354tf5cFZPXsF26Yg+5M63+5H3oE6Vtkh2oqbvsE2Oe7s2nIbcQh5lmG7Lp/eS29vJtTpw9+p6PX0qNSg==} 1051 | engines: {node: '>=20.18.1'} 1052 | 1053 | universalify@0.1.2: 1054 | resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} 1055 | engines: {node: '>= 4.0.0'} 1056 | 1057 | uri-js@4.4.1: 1058 | resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 1059 | 1060 | v8-compile-cache-lib@3.0.1: 1061 | resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} 1062 | 1063 | which@2.0.2: 1064 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1065 | engines: {node: '>= 8'} 1066 | hasBin: true 1067 | 1068 | word-wrap@1.2.5: 1069 | resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} 1070 | engines: {node: '>=0.10.0'} 1071 | 1072 | wrappy@1.0.2: 1073 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 1074 | 1075 | ws@8.18.2: 1076 | resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} 1077 | engines: {node: '>=10.0.0'} 1078 | peerDependencies: 1079 | bufferutil: ^4.0.1 1080 | utf-8-validate: '>=5.0.2' 1081 | peerDependenciesMeta: 1082 | bufferutil: 1083 | optional: true 1084 | utf-8-validate: 1085 | optional: true 1086 | 1087 | yn@3.1.1: 1088 | resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} 1089 | engines: {node: '>=6'} 1090 | 1091 | yocto-queue@0.1.0: 1092 | resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 1093 | engines: {node: '>=10'} 1094 | 1095 | snapshots: 1096 | 1097 | '@cspotcode/source-map-support@0.8.1': 1098 | dependencies: 1099 | '@jridgewell/trace-mapping': 0.3.9 1100 | 1101 | '@derockdev/discord-components-core@3.6.1': 1102 | dependencies: 1103 | '@stencil/core': 3.4.2 1104 | clsx: 1.2.1 1105 | hex-to-rgba: 2.0.1 1106 | highlight.js: 11.11.1 1107 | 1108 | '@derockdev/discord-components-react@3.6.1(react-dom@0.0.0-experimental-6639ed3b3-20240111(react@0.0.0-experimental-6639ed3b3-20240111))(react@0.0.0-experimental-6639ed3b3-20240111)': 1109 | dependencies: 1110 | '@derockdev/discord-components-core': 3.6.1 1111 | react: 0.0.0-experimental-6639ed3b3-20240111 1112 | react-dom: 0.0.0-experimental-6639ed3b3-20240111(react@0.0.0-experimental-6639ed3b3-20240111) 1113 | tslib: 2.8.1 1114 | 1115 | '@discordjs/builders@1.11.2': 1116 | dependencies: 1117 | '@discordjs/formatters': 0.6.1 1118 | '@discordjs/util': 1.1.1 1119 | '@sapphire/shapeshift': 4.0.0 1120 | discord-api-types: 0.38.8 1121 | fast-deep-equal: 3.1.3 1122 | ts-mixer: 6.0.4 1123 | tslib: 2.8.1 1124 | 1125 | '@discordjs/collection@1.5.3': {} 1126 | 1127 | '@discordjs/collection@2.1.1': {} 1128 | 1129 | '@discordjs/formatters@0.6.1': 1130 | dependencies: 1131 | discord-api-types: 0.38.8 1132 | 1133 | '@discordjs/rest@2.5.0': 1134 | dependencies: 1135 | '@discordjs/collection': 2.1.1 1136 | '@discordjs/util': 1.1.1 1137 | '@sapphire/async-queue': 1.5.5 1138 | '@sapphire/snowflake': 3.5.3 1139 | '@vladfrangu/async_event_emitter': 2.4.6 1140 | discord-api-types: 0.38.8 1141 | magic-bytes.js: 1.12.1 1142 | tslib: 2.8.1 1143 | undici: 6.21.1 1144 | 1145 | '@discordjs/util@1.1.1': {} 1146 | 1147 | '@discordjs/ws@1.2.2': 1148 | dependencies: 1149 | '@discordjs/collection': 2.1.1 1150 | '@discordjs/rest': 2.5.0 1151 | '@discordjs/util': 1.1.1 1152 | '@sapphire/async-queue': 1.5.5 1153 | '@types/ws': 8.18.1 1154 | '@vladfrangu/async_event_emitter': 2.4.6 1155 | discord-api-types: 0.38.8 1156 | tslib: 2.8.1 1157 | ws: 8.18.2 1158 | transitivePeerDependencies: 1159 | - bufferutil 1160 | - utf-8-validate 1161 | 1162 | '@emnapi/runtime@1.4.3': 1163 | dependencies: 1164 | tslib: 2.8.1 1165 | optional: true 1166 | 1167 | '@eslint-community/eslint-utils@4.7.0(eslint@8.57.1)': 1168 | dependencies: 1169 | eslint: 8.57.1 1170 | eslint-visitor-keys: 3.4.3 1171 | 1172 | '@eslint-community/regexpp@4.12.1': {} 1173 | 1174 | '@eslint/eslintrc@2.1.4': 1175 | dependencies: 1176 | ajv: 6.12.6 1177 | debug: 4.4.1 1178 | espree: 9.6.1 1179 | globals: 13.24.0 1180 | ignore: 5.3.2 1181 | import-fresh: 3.3.1 1182 | js-yaml: 4.1.0 1183 | minimatch: 3.1.2 1184 | strip-json-comments: 3.1.1 1185 | transitivePeerDependencies: 1186 | - supports-color 1187 | 1188 | '@eslint/js@8.57.1': {} 1189 | 1190 | '@humanwhocodes/config-array@0.13.0': 1191 | dependencies: 1192 | '@humanwhocodes/object-schema': 2.0.3 1193 | debug: 4.4.1 1194 | minimatch: 3.1.2 1195 | transitivePeerDependencies: 1196 | - supports-color 1197 | 1198 | '@humanwhocodes/module-importer@1.0.1': {} 1199 | 1200 | '@humanwhocodes/object-schema@2.0.3': {} 1201 | 1202 | '@img/sharp-darwin-arm64@0.33.5': 1203 | optionalDependencies: 1204 | '@img/sharp-libvips-darwin-arm64': 1.0.4 1205 | optional: true 1206 | 1207 | '@img/sharp-darwin-x64@0.33.5': 1208 | optionalDependencies: 1209 | '@img/sharp-libvips-darwin-x64': 1.0.4 1210 | optional: true 1211 | 1212 | '@img/sharp-libvips-darwin-arm64@1.0.4': 1213 | optional: true 1214 | 1215 | '@img/sharp-libvips-darwin-x64@1.0.4': 1216 | optional: true 1217 | 1218 | '@img/sharp-libvips-linux-arm64@1.0.4': 1219 | optional: true 1220 | 1221 | '@img/sharp-libvips-linux-arm@1.0.5': 1222 | optional: true 1223 | 1224 | '@img/sharp-libvips-linux-s390x@1.0.4': 1225 | optional: true 1226 | 1227 | '@img/sharp-libvips-linux-x64@1.0.4': 1228 | optional: true 1229 | 1230 | '@img/sharp-libvips-linuxmusl-arm64@1.0.4': 1231 | optional: true 1232 | 1233 | '@img/sharp-libvips-linuxmusl-x64@1.0.4': 1234 | optional: true 1235 | 1236 | '@img/sharp-linux-arm64@0.33.5': 1237 | optionalDependencies: 1238 | '@img/sharp-libvips-linux-arm64': 1.0.4 1239 | optional: true 1240 | 1241 | '@img/sharp-linux-arm@0.33.5': 1242 | optionalDependencies: 1243 | '@img/sharp-libvips-linux-arm': 1.0.5 1244 | optional: true 1245 | 1246 | '@img/sharp-linux-s390x@0.33.5': 1247 | optionalDependencies: 1248 | '@img/sharp-libvips-linux-s390x': 1.0.4 1249 | optional: true 1250 | 1251 | '@img/sharp-linux-x64@0.33.5': 1252 | optionalDependencies: 1253 | '@img/sharp-libvips-linux-x64': 1.0.4 1254 | optional: true 1255 | 1256 | '@img/sharp-linuxmusl-arm64@0.33.5': 1257 | optionalDependencies: 1258 | '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 1259 | optional: true 1260 | 1261 | '@img/sharp-linuxmusl-x64@0.33.5': 1262 | optionalDependencies: 1263 | '@img/sharp-libvips-linuxmusl-x64': 1.0.4 1264 | optional: true 1265 | 1266 | '@img/sharp-wasm32@0.33.5': 1267 | dependencies: 1268 | '@emnapi/runtime': 1.4.3 1269 | optional: true 1270 | 1271 | '@img/sharp-win32-ia32@0.33.5': 1272 | optional: true 1273 | 1274 | '@img/sharp-win32-x64@0.33.5': 1275 | optional: true 1276 | 1277 | '@jridgewell/resolve-uri@3.1.2': {} 1278 | 1279 | '@jridgewell/sourcemap-codec@1.5.0': {} 1280 | 1281 | '@jridgewell/trace-mapping@0.3.9': 1282 | dependencies: 1283 | '@jridgewell/resolve-uri': 3.1.2 1284 | '@jridgewell/sourcemap-codec': 1.5.0 1285 | 1286 | '@nodelib/fs.scandir@2.1.5': 1287 | dependencies: 1288 | '@nodelib/fs.stat': 2.0.5 1289 | run-parallel: 1.2.0 1290 | 1291 | '@nodelib/fs.stat@2.0.5': {} 1292 | 1293 | '@nodelib/fs.walk@1.2.8': 1294 | dependencies: 1295 | '@nodelib/fs.scandir': 2.1.5 1296 | fastq: 1.19.1 1297 | 1298 | '@sapphire/async-queue@1.5.5': {} 1299 | 1300 | '@sapphire/shapeshift@4.0.0': 1301 | dependencies: 1302 | fast-deep-equal: 3.1.3 1303 | lodash: 4.17.21 1304 | 1305 | '@sapphire/snowflake@3.5.3': {} 1306 | 1307 | '@stencil/core@3.4.2': {} 1308 | 1309 | '@tsconfig/node10@1.0.11': {} 1310 | 1311 | '@tsconfig/node12@1.0.11': {} 1312 | 1313 | '@tsconfig/node14@1.0.3': {} 1314 | 1315 | '@tsconfig/node16@1.0.4': {} 1316 | 1317 | '@types/debug@4.1.12': 1318 | dependencies: 1319 | '@types/ms': 2.1.0 1320 | 1321 | '@types/json-schema@7.0.15': {} 1322 | 1323 | '@types/ms@2.1.0': {} 1324 | 1325 | '@types/node@20.17.47': 1326 | dependencies: 1327 | undici-types: 6.19.8 1328 | 1329 | '@types/prop-types@15.7.14': {} 1330 | 1331 | '@types/react-dom@18.3.7(@types/react@18.3.21)': 1332 | dependencies: 1333 | '@types/react': 18.3.21 1334 | 1335 | '@types/react@18.3.21': 1336 | dependencies: 1337 | '@types/prop-types': 15.7.14 1338 | csstype: 3.1.3 1339 | 1340 | '@types/semver@7.7.0': {} 1341 | 1342 | '@types/ws@8.18.1': 1343 | dependencies: 1344 | '@types/node': 20.17.47 1345 | 1346 | '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3)': 1347 | dependencies: 1348 | '@eslint-community/regexpp': 4.12.1 1349 | '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.8.3) 1350 | '@typescript-eslint/scope-manager': 5.62.0 1351 | '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.1)(typescript@5.8.3) 1352 | '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.8.3) 1353 | debug: 4.4.1 1354 | eslint: 8.57.1 1355 | graphemer: 1.4.0 1356 | ignore: 5.3.2 1357 | natural-compare-lite: 1.4.0 1358 | semver: 7.7.2 1359 | tsutils: 3.21.0(typescript@5.8.3) 1360 | optionalDependencies: 1361 | typescript: 5.8.3 1362 | transitivePeerDependencies: 1363 | - supports-color 1364 | 1365 | '@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3)': 1366 | dependencies: 1367 | '@typescript-eslint/scope-manager': 5.62.0 1368 | '@typescript-eslint/types': 5.62.0 1369 | '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) 1370 | debug: 4.4.1 1371 | eslint: 8.57.1 1372 | optionalDependencies: 1373 | typescript: 5.8.3 1374 | transitivePeerDependencies: 1375 | - supports-color 1376 | 1377 | '@typescript-eslint/scope-manager@5.62.0': 1378 | dependencies: 1379 | '@typescript-eslint/types': 5.62.0 1380 | '@typescript-eslint/visitor-keys': 5.62.0 1381 | 1382 | '@typescript-eslint/type-utils@5.62.0(eslint@8.57.1)(typescript@5.8.3)': 1383 | dependencies: 1384 | '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) 1385 | '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.8.3) 1386 | debug: 4.4.1 1387 | eslint: 8.57.1 1388 | tsutils: 3.21.0(typescript@5.8.3) 1389 | optionalDependencies: 1390 | typescript: 5.8.3 1391 | transitivePeerDependencies: 1392 | - supports-color 1393 | 1394 | '@typescript-eslint/types@5.62.0': {} 1395 | 1396 | '@typescript-eslint/typescript-estree@5.62.0(typescript@5.8.3)': 1397 | dependencies: 1398 | '@typescript-eslint/types': 5.62.0 1399 | '@typescript-eslint/visitor-keys': 5.62.0 1400 | debug: 4.4.1 1401 | globby: 11.1.0 1402 | is-glob: 4.0.3 1403 | semver: 7.7.2 1404 | tsutils: 3.21.0(typescript@5.8.3) 1405 | optionalDependencies: 1406 | typescript: 5.8.3 1407 | transitivePeerDependencies: 1408 | - supports-color 1409 | 1410 | '@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@5.8.3)': 1411 | dependencies: 1412 | '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) 1413 | '@types/json-schema': 7.0.15 1414 | '@types/semver': 7.7.0 1415 | '@typescript-eslint/scope-manager': 5.62.0 1416 | '@typescript-eslint/types': 5.62.0 1417 | '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) 1418 | eslint: 8.57.1 1419 | eslint-scope: 5.1.1 1420 | semver: 7.7.2 1421 | transitivePeerDependencies: 1422 | - supports-color 1423 | - typescript 1424 | 1425 | '@typescript-eslint/visitor-keys@5.62.0': 1426 | dependencies: 1427 | '@typescript-eslint/types': 5.62.0 1428 | eslint-visitor-keys: 3.4.3 1429 | 1430 | '@ungap/structured-clone@1.3.0': {} 1431 | 1432 | '@vladfrangu/async_event_emitter@2.4.6': {} 1433 | 1434 | acorn-jsx@5.3.2(acorn@8.14.1): 1435 | dependencies: 1436 | acorn: 8.14.1 1437 | 1438 | acorn-walk@8.3.4: 1439 | dependencies: 1440 | acorn: 8.14.1 1441 | 1442 | acorn@8.14.1: {} 1443 | 1444 | ajv@6.12.6: 1445 | dependencies: 1446 | fast-deep-equal: 3.1.3 1447 | fast-json-stable-stringify: 2.1.0 1448 | json-schema-traverse: 0.4.1 1449 | uri-js: 4.4.1 1450 | 1451 | ansi-regex@5.0.1: {} 1452 | 1453 | ansi-styles@4.3.0: 1454 | dependencies: 1455 | color-convert: 2.0.1 1456 | 1457 | arg@4.1.3: {} 1458 | 1459 | argparse@2.0.1: {} 1460 | 1461 | array-union@2.1.0: {} 1462 | 1463 | balanced-match@1.0.2: {} 1464 | 1465 | brace-expansion@1.1.11: 1466 | dependencies: 1467 | balanced-match: 1.0.2 1468 | concat-map: 0.0.1 1469 | 1470 | braces@3.0.3: 1471 | dependencies: 1472 | fill-range: 7.1.1 1473 | 1474 | callsites@3.1.0: {} 1475 | 1476 | chalk@4.1.2: 1477 | dependencies: 1478 | ansi-styles: 4.3.0 1479 | supports-color: 7.2.0 1480 | 1481 | clsx@1.2.1: {} 1482 | 1483 | color-convert@2.0.1: 1484 | dependencies: 1485 | color-name: 1.1.4 1486 | 1487 | color-name@1.1.4: {} 1488 | 1489 | color-string@1.9.1: 1490 | dependencies: 1491 | color-name: 1.1.4 1492 | simple-swizzle: 0.2.2 1493 | 1494 | color@4.2.3: 1495 | dependencies: 1496 | color-convert: 2.0.1 1497 | color-string: 1.9.1 1498 | 1499 | concat-map@0.0.1: {} 1500 | 1501 | create-require@1.1.1: {} 1502 | 1503 | cross-spawn@7.0.6: 1504 | dependencies: 1505 | path-key: 3.1.1 1506 | shebang-command: 2.0.0 1507 | which: 2.0.2 1508 | 1509 | csstype@3.1.3: {} 1510 | 1511 | debug@4.4.1: 1512 | dependencies: 1513 | ms: 2.1.3 1514 | 1515 | deep-is@0.1.4: {} 1516 | 1517 | detect-libc@2.0.4: {} 1518 | 1519 | diff@4.0.2: {} 1520 | 1521 | dir-glob@3.0.1: 1522 | dependencies: 1523 | path-type: 4.0.0 1524 | 1525 | discord-api-types@0.38.8: {} 1526 | 1527 | discord-markdown-parser@1.1.0: 1528 | dependencies: 1529 | simple-markdown: 0.7.3 1530 | 1531 | discord.js@14.19.3: 1532 | dependencies: 1533 | '@discordjs/builders': 1.11.2 1534 | '@discordjs/collection': 1.5.3 1535 | '@discordjs/formatters': 0.6.1 1536 | '@discordjs/rest': 2.5.0 1537 | '@discordjs/util': 1.1.1 1538 | '@discordjs/ws': 1.2.2 1539 | '@sapphire/snowflake': 3.5.3 1540 | discord-api-types: 0.38.8 1541 | fast-deep-equal: 3.1.3 1542 | lodash.snakecase: 4.1.1 1543 | magic-bytes.js: 1.12.1 1544 | tslib: 2.8.1 1545 | undici: 6.21.1 1546 | transitivePeerDependencies: 1547 | - bufferutil 1548 | - utf-8-validate 1549 | 1550 | doctrine@3.0.0: 1551 | dependencies: 1552 | esutils: 2.0.3 1553 | 1554 | dotenv@16.5.0: {} 1555 | 1556 | end-of-stream@1.4.4: 1557 | dependencies: 1558 | once: 1.4.0 1559 | 1560 | escape-string-regexp@4.0.0: {} 1561 | 1562 | eslint-scope@5.1.1: 1563 | dependencies: 1564 | esrecurse: 4.3.0 1565 | estraverse: 4.3.0 1566 | 1567 | eslint-scope@7.2.2: 1568 | dependencies: 1569 | esrecurse: 4.3.0 1570 | estraverse: 5.3.0 1571 | 1572 | eslint-visitor-keys@3.4.3: {} 1573 | 1574 | eslint@8.57.1: 1575 | dependencies: 1576 | '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) 1577 | '@eslint-community/regexpp': 4.12.1 1578 | '@eslint/eslintrc': 2.1.4 1579 | '@eslint/js': 8.57.1 1580 | '@humanwhocodes/config-array': 0.13.0 1581 | '@humanwhocodes/module-importer': 1.0.1 1582 | '@nodelib/fs.walk': 1.2.8 1583 | '@ungap/structured-clone': 1.3.0 1584 | ajv: 6.12.6 1585 | chalk: 4.1.2 1586 | cross-spawn: 7.0.6 1587 | debug: 4.4.1 1588 | doctrine: 3.0.0 1589 | escape-string-regexp: 4.0.0 1590 | eslint-scope: 7.2.2 1591 | eslint-visitor-keys: 3.4.3 1592 | espree: 9.6.1 1593 | esquery: 1.6.0 1594 | esutils: 2.0.3 1595 | fast-deep-equal: 3.1.3 1596 | file-entry-cache: 6.0.1 1597 | find-up: 5.0.0 1598 | glob-parent: 6.0.2 1599 | globals: 13.24.0 1600 | graphemer: 1.4.0 1601 | ignore: 5.3.2 1602 | imurmurhash: 0.1.4 1603 | is-glob: 4.0.3 1604 | is-path-inside: 3.0.3 1605 | js-yaml: 4.1.0 1606 | json-stable-stringify-without-jsonify: 1.0.1 1607 | levn: 0.4.1 1608 | lodash.merge: 4.6.2 1609 | minimatch: 3.1.2 1610 | natural-compare: 1.4.0 1611 | optionator: 0.9.4 1612 | strip-ansi: 6.0.1 1613 | text-table: 0.2.0 1614 | transitivePeerDependencies: 1615 | - supports-color 1616 | 1617 | espree@9.6.1: 1618 | dependencies: 1619 | acorn: 8.14.1 1620 | acorn-jsx: 5.3.2(acorn@8.14.1) 1621 | eslint-visitor-keys: 3.4.3 1622 | 1623 | esquery@1.6.0: 1624 | dependencies: 1625 | estraverse: 5.3.0 1626 | 1627 | esrecurse@4.3.0: 1628 | dependencies: 1629 | estraverse: 5.3.0 1630 | 1631 | estraverse@4.3.0: {} 1632 | 1633 | estraverse@5.3.0: {} 1634 | 1635 | esutils@2.0.3: {} 1636 | 1637 | execa@4.1.0: 1638 | dependencies: 1639 | cross-spawn: 7.0.6 1640 | get-stream: 5.2.0 1641 | human-signals: 1.1.1 1642 | is-stream: 2.0.1 1643 | merge-stream: 2.0.0 1644 | npm-run-path: 4.0.1 1645 | onetime: 5.1.2 1646 | signal-exit: 3.0.7 1647 | strip-final-newline: 2.0.0 1648 | 1649 | fast-deep-equal@3.1.3: {} 1650 | 1651 | fast-glob@3.3.3: 1652 | dependencies: 1653 | '@nodelib/fs.stat': 2.0.5 1654 | '@nodelib/fs.walk': 1.2.8 1655 | glob-parent: 5.1.2 1656 | merge2: 1.4.1 1657 | micromatch: 4.0.8 1658 | 1659 | fast-json-stable-stringify@2.1.0: {} 1660 | 1661 | fast-levenshtein@2.0.6: {} 1662 | 1663 | fastq@1.19.1: 1664 | dependencies: 1665 | reusify: 1.1.0 1666 | 1667 | file-entry-cache@6.0.1: 1668 | dependencies: 1669 | flat-cache: 3.2.0 1670 | 1671 | fill-range@7.1.1: 1672 | dependencies: 1673 | to-regex-range: 5.0.1 1674 | 1675 | find-up@4.1.0: 1676 | dependencies: 1677 | locate-path: 5.0.0 1678 | path-exists: 4.0.0 1679 | 1680 | find-up@5.0.0: 1681 | dependencies: 1682 | locate-path: 6.0.0 1683 | path-exists: 4.0.0 1684 | 1685 | flat-cache@3.2.0: 1686 | dependencies: 1687 | flatted: 3.3.3 1688 | keyv: 4.5.4 1689 | rimraf: 3.0.2 1690 | 1691 | flatted@3.3.3: {} 1692 | 1693 | fs-extra@8.1.0: 1694 | dependencies: 1695 | graceful-fs: 4.2.11 1696 | jsonfile: 4.0.0 1697 | universalify: 0.1.2 1698 | 1699 | fs.realpath@1.0.0: {} 1700 | 1701 | get-stream@5.2.0: 1702 | dependencies: 1703 | pump: 3.0.2 1704 | 1705 | glob-parent@5.1.2: 1706 | dependencies: 1707 | is-glob: 4.0.3 1708 | 1709 | glob-parent@6.0.2: 1710 | dependencies: 1711 | is-glob: 4.0.3 1712 | 1713 | glob@7.2.3: 1714 | dependencies: 1715 | fs.realpath: 1.0.0 1716 | inflight: 1.0.6 1717 | inherits: 2.0.4 1718 | minimatch: 3.1.2 1719 | once: 1.4.0 1720 | path-is-absolute: 1.0.1 1721 | 1722 | globals@13.24.0: 1723 | dependencies: 1724 | type-fest: 0.20.2 1725 | 1726 | globby@11.1.0: 1727 | dependencies: 1728 | array-union: 2.1.0 1729 | dir-glob: 3.0.1 1730 | fast-glob: 3.3.3 1731 | ignore: 5.3.2 1732 | merge2: 1.4.1 1733 | slash: 3.0.0 1734 | 1735 | graceful-fs@4.2.11: {} 1736 | 1737 | graphemer@1.4.0: {} 1738 | 1739 | has-flag@4.0.0: {} 1740 | 1741 | hex-to-rgba@2.0.1: {} 1742 | 1743 | highlight.js@11.11.1: {} 1744 | 1745 | human-signals@1.1.1: {} 1746 | 1747 | husky@8.0.3: {} 1748 | 1749 | ignore@5.3.2: {} 1750 | 1751 | import-fresh@3.3.1: 1752 | dependencies: 1753 | parent-module: 1.0.1 1754 | resolve-from: 4.0.0 1755 | 1756 | imurmurhash@0.1.4: {} 1757 | 1758 | inflight@1.0.6: 1759 | dependencies: 1760 | once: 1.4.0 1761 | wrappy: 1.0.2 1762 | 1763 | inherits@2.0.4: {} 1764 | 1765 | is-arrayish@0.3.2: {} 1766 | 1767 | is-extglob@2.1.1: {} 1768 | 1769 | is-glob@4.0.3: 1770 | dependencies: 1771 | is-extglob: 2.1.1 1772 | 1773 | is-number@7.0.0: {} 1774 | 1775 | is-path-inside@3.0.3: {} 1776 | 1777 | is-stream@2.0.1: {} 1778 | 1779 | isexe@2.0.0: {} 1780 | 1781 | js-tokens@4.0.0: {} 1782 | 1783 | js-yaml@4.1.0: 1784 | dependencies: 1785 | argparse: 2.0.1 1786 | 1787 | json-buffer@3.0.1: {} 1788 | 1789 | json-schema-traverse@0.4.1: {} 1790 | 1791 | json-stable-stringify-without-jsonify@1.0.1: {} 1792 | 1793 | jsonfile@4.0.0: 1794 | optionalDependencies: 1795 | graceful-fs: 4.2.11 1796 | 1797 | jsonfile@5.0.0: 1798 | dependencies: 1799 | universalify: 0.1.2 1800 | optionalDependencies: 1801 | graceful-fs: 4.2.11 1802 | 1803 | keyv@4.5.4: 1804 | dependencies: 1805 | json-buffer: 3.0.1 1806 | 1807 | levn@0.4.1: 1808 | dependencies: 1809 | prelude-ls: 1.2.1 1810 | type-check: 0.4.0 1811 | 1812 | locate-path@5.0.0: 1813 | dependencies: 1814 | p-locate: 4.1.0 1815 | 1816 | locate-path@6.0.0: 1817 | dependencies: 1818 | p-locate: 5.0.0 1819 | 1820 | lodash.merge@4.6.2: {} 1821 | 1822 | lodash.snakecase@4.1.1: {} 1823 | 1824 | lodash@4.17.21: {} 1825 | 1826 | loose-envify@1.4.0: 1827 | dependencies: 1828 | js-tokens: 4.0.0 1829 | 1830 | magic-bytes.js@1.12.1: {} 1831 | 1832 | make-error@1.3.6: {} 1833 | 1834 | merge-stream@2.0.0: {} 1835 | 1836 | merge2@1.4.1: {} 1837 | 1838 | micromatch@4.0.8: 1839 | dependencies: 1840 | braces: 3.0.3 1841 | picomatch: 2.3.1 1842 | 1843 | mimic-fn@2.1.0: {} 1844 | 1845 | minimatch@3.1.2: 1846 | dependencies: 1847 | brace-expansion: 1.1.11 1848 | 1849 | mri@1.2.0: {} 1850 | 1851 | ms@2.1.3: {} 1852 | 1853 | natural-compare-lite@1.4.0: {} 1854 | 1855 | natural-compare@1.4.0: {} 1856 | 1857 | npm-run-path@4.0.1: 1858 | dependencies: 1859 | path-key: 3.1.1 1860 | 1861 | once@1.4.0: 1862 | dependencies: 1863 | wrappy: 1.0.2 1864 | 1865 | onetime@5.1.2: 1866 | dependencies: 1867 | mimic-fn: 2.1.0 1868 | 1869 | optionator@0.9.4: 1870 | dependencies: 1871 | deep-is: 0.1.4 1872 | fast-levenshtein: 2.0.6 1873 | levn: 0.4.1 1874 | prelude-ls: 1.2.1 1875 | type-check: 0.4.0 1876 | word-wrap: 1.2.5 1877 | 1878 | p-limit@2.3.0: 1879 | dependencies: 1880 | p-try: 2.2.0 1881 | 1882 | p-limit@3.1.0: 1883 | dependencies: 1884 | yocto-queue: 0.1.0 1885 | 1886 | p-locate@4.1.0: 1887 | dependencies: 1888 | p-limit: 2.3.0 1889 | 1890 | p-locate@5.0.0: 1891 | dependencies: 1892 | p-limit: 3.1.0 1893 | 1894 | p-try@2.2.0: {} 1895 | 1896 | parent-module@1.0.1: 1897 | dependencies: 1898 | callsites: 3.1.0 1899 | 1900 | path-exists@4.0.0: {} 1901 | 1902 | path-is-absolute@1.0.1: {} 1903 | 1904 | path-key@3.1.1: {} 1905 | 1906 | path-type@4.0.0: {} 1907 | 1908 | picocolors@1.1.1: {} 1909 | 1910 | picomatch@2.3.1: {} 1911 | 1912 | picomatch@3.0.1: {} 1913 | 1914 | prelude-ls@1.2.1: {} 1915 | 1916 | prettier@2.8.8: {} 1917 | 1918 | pretty-quick@3.3.1(prettier@2.8.8): 1919 | dependencies: 1920 | execa: 4.1.0 1921 | find-up: 4.1.0 1922 | ignore: 5.3.2 1923 | mri: 1.2.0 1924 | picocolors: 1.1.1 1925 | picomatch: 3.0.1 1926 | prettier: 2.8.8 1927 | tslib: 2.8.1 1928 | 1929 | pump@3.0.2: 1930 | dependencies: 1931 | end-of-stream: 1.4.4 1932 | once: 1.4.0 1933 | 1934 | punycode@2.3.1: {} 1935 | 1936 | queue-microtask@1.2.3: {} 1937 | 1938 | react-dom@0.0.0-experimental-6639ed3b3-20240111(react@0.0.0-experimental-6639ed3b3-20240111): 1939 | dependencies: 1940 | loose-envify: 1.4.0 1941 | react: 0.0.0-experimental-6639ed3b3-20240111 1942 | scheduler: 0.0.0-experimental-6639ed3b3-20240111 1943 | 1944 | react@0.0.0-experimental-6639ed3b3-20240111: 1945 | dependencies: 1946 | loose-envify: 1.4.0 1947 | 1948 | resolve-from@4.0.0: {} 1949 | 1950 | reusify@1.1.0: {} 1951 | 1952 | rimraf@3.0.2: 1953 | dependencies: 1954 | glob: 7.2.3 1955 | 1956 | run-parallel@1.2.0: 1957 | dependencies: 1958 | queue-microtask: 1.2.3 1959 | 1960 | scheduler@0.0.0-experimental-6639ed3b3-20240111: 1961 | dependencies: 1962 | loose-envify: 1.4.0 1963 | 1964 | semver@7.7.2: {} 1965 | 1966 | sharp@0.33.5: 1967 | dependencies: 1968 | color: 4.2.3 1969 | detect-libc: 2.0.4 1970 | semver: 7.7.2 1971 | optionalDependencies: 1972 | '@img/sharp-darwin-arm64': 0.33.5 1973 | '@img/sharp-darwin-x64': 0.33.5 1974 | '@img/sharp-libvips-darwin-arm64': 1.0.4 1975 | '@img/sharp-libvips-darwin-x64': 1.0.4 1976 | '@img/sharp-libvips-linux-arm': 1.0.5 1977 | '@img/sharp-libvips-linux-arm64': 1.0.4 1978 | '@img/sharp-libvips-linux-s390x': 1.0.4 1979 | '@img/sharp-libvips-linux-x64': 1.0.4 1980 | '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 1981 | '@img/sharp-libvips-linuxmusl-x64': 1.0.4 1982 | '@img/sharp-linux-arm': 0.33.5 1983 | '@img/sharp-linux-arm64': 0.33.5 1984 | '@img/sharp-linux-s390x': 0.33.5 1985 | '@img/sharp-linux-x64': 0.33.5 1986 | '@img/sharp-linuxmusl-arm64': 0.33.5 1987 | '@img/sharp-linuxmusl-x64': 0.33.5 1988 | '@img/sharp-wasm32': 0.33.5 1989 | '@img/sharp-win32-ia32': 0.33.5 1990 | '@img/sharp-win32-x64': 0.33.5 1991 | 1992 | shebang-command@2.0.0: 1993 | dependencies: 1994 | shebang-regex: 3.0.0 1995 | 1996 | shebang-regex@3.0.0: {} 1997 | 1998 | signal-exit@3.0.7: {} 1999 | 2000 | simple-markdown@0.7.3: 2001 | dependencies: 2002 | '@types/react': 18.3.21 2003 | 2004 | simple-swizzle@0.2.2: 2005 | dependencies: 2006 | is-arrayish: 0.3.2 2007 | 2008 | slash@3.0.0: {} 2009 | 2010 | strip-ansi@6.0.1: 2011 | dependencies: 2012 | ansi-regex: 5.0.1 2013 | 2014 | strip-final-newline@2.0.0: {} 2015 | 2016 | strip-json-comments@3.1.1: {} 2017 | 2018 | supports-color@7.2.0: 2019 | dependencies: 2020 | has-flag: 4.0.0 2021 | 2022 | text-table@0.2.0: {} 2023 | 2024 | to-regex-range@5.0.1: 2025 | dependencies: 2026 | is-number: 7.0.0 2027 | 2028 | ts-mixer@6.0.4: {} 2029 | 2030 | ts-node@10.9.2(@types/node@20.17.47)(typescript@5.8.3): 2031 | dependencies: 2032 | '@cspotcode/source-map-support': 0.8.1 2033 | '@tsconfig/node10': 1.0.11 2034 | '@tsconfig/node12': 1.0.11 2035 | '@tsconfig/node14': 1.0.3 2036 | '@tsconfig/node16': 1.0.4 2037 | '@types/node': 20.17.47 2038 | acorn: 8.14.1 2039 | acorn-walk: 8.3.4 2040 | arg: 4.1.3 2041 | create-require: 1.1.1 2042 | diff: 4.0.2 2043 | make-error: 1.3.6 2044 | typescript: 5.8.3 2045 | v8-compile-cache-lib: 3.0.1 2046 | yn: 3.1.1 2047 | 2048 | tslib@1.14.1: {} 2049 | 2050 | tslib@2.8.1: {} 2051 | 2052 | tsutils@3.21.0(typescript@5.8.3): 2053 | dependencies: 2054 | tslib: 1.14.1 2055 | typescript: 5.8.3 2056 | 2057 | twemoji-parser@14.0.0: {} 2058 | 2059 | twemoji@14.0.2: 2060 | dependencies: 2061 | fs-extra: 8.1.0 2062 | jsonfile: 5.0.0 2063 | twemoji-parser: 14.0.0 2064 | universalify: 0.1.2 2065 | 2066 | type-check@0.4.0: 2067 | dependencies: 2068 | prelude-ls: 1.2.1 2069 | 2070 | type-fest@0.20.2: {} 2071 | 2072 | typescript@5.8.3: {} 2073 | 2074 | undici-types@6.19.8: {} 2075 | 2076 | undici@6.21.1: {} 2077 | 2078 | undici@7.9.0: {} 2079 | 2080 | universalify@0.1.2: {} 2081 | 2082 | uri-js@4.4.1: 2083 | dependencies: 2084 | punycode: 2.3.1 2085 | 2086 | v8-compile-cache-lib@3.0.1: {} 2087 | 2088 | which@2.0.2: 2089 | dependencies: 2090 | isexe: 2.0.0 2091 | 2092 | word-wrap@1.2.5: {} 2093 | 2094 | wrappy@1.0.2: {} 2095 | 2096 | ws@8.18.2: {} 2097 | 2098 | yn@3.1.1: {} 2099 | 2100 | yocto-queue@0.1.0: {} 2101 | -------------------------------------------------------------------------------- /src/downloader/images.ts: -------------------------------------------------------------------------------- 1 | import type { APIAttachment, APIMessage, Awaitable } from 'discord.js'; 2 | import type { WebpOptions } from 'sharp'; 3 | import { request } from 'undici'; 4 | import debug from 'debug'; 5 | 6 | /** 7 | * Callback used to save an image attachment. 8 | * The returned string is the URL that will be used in the transcript. 9 | * 10 | * `undefined` indicates to use the original attachment URL. 11 | * `null` indicates to not include the attachment in the transcript. 12 | * `string` indicates to use the returned URL as the attachment URL (base64 or remote image). 13 | */ 14 | export type ResolveImageCallback = ( 15 | attachment: APIAttachment, 16 | message: APIMessage 17 | ) => Awaitable; 18 | 19 | /** 20 | * Builder to build a image saving callback. 21 | */ 22 | export class TranscriptImageDownloader { 23 | private static log = debug('discord-html-transcripts:TranscriptImageDownloader'); 24 | private log = TranscriptImageDownloader.log; 25 | 26 | private maxFileSize?: number; // in kilobytes 27 | private compression?: { 28 | quality: number; // 1-100 29 | convertToWebP: boolean; 30 | options: Omit; 31 | }; 32 | 33 | /** 34 | * Sets the maximum file size for *each* individual image. 35 | * @param size The maximum file size in kilobytes 36 | */ 37 | withMaxSize(size: number) { 38 | this.maxFileSize = size; 39 | return this; 40 | } 41 | 42 | /** 43 | * Sets the compression quality for each image. This requires `sharp` to be installed. 44 | * Optionally, images can be converted to WebP format which is smaller in size. 45 | * @param quality The quality of the image (1 lowest - 100 highest). Lower quality means smaller file size. 46 | * @param convertToWebP Whether to convert the image to WebP format 47 | */ 48 | withCompression(quality = 80, convertToWebP = false, options: Omit = {}) { 49 | if (quality < 1 || quality > 100) throw new Error('Quality must be between 1 and 100'); 50 | 51 | // try and import sharp 52 | import('sharp').catch((err) => { 53 | console.error(err); 54 | console.error( 55 | `[discord-html-transcripts] Failed to import 'sharp'. Image compression requires the 'sharp' package to be installed. Either install sharp or remove the compression options.` 56 | ); 57 | }); 58 | 59 | this.compression = { quality, convertToWebP, options }; 60 | return this; 61 | } 62 | 63 | /** 64 | * Builds the image saving callback. 65 | */ 66 | build(): ResolveImageCallback { 67 | return async (attachment) => { 68 | // if the attachment is not an image, return null 69 | if (!attachment.width || !attachment.height) return undefined; 70 | 71 | // if the max file size is set, check if the file size is within the limit 72 | if (this.maxFileSize && attachment.size > this.maxFileSize * 1024) return undefined; 73 | 74 | // fetch the image 75 | this.log(`Fetching attachment ${attachment.id}: ${attachment.url}`); 76 | const response = await request(attachment.url).catch((err) => { 77 | console.error(`[discord-html-transcripts] Failed to download image for transcript: `, err); 78 | return null; 79 | }); 80 | 81 | if (!response) return undefined; 82 | 83 | const mimetype = response.headers['content-type']; 84 | const buffer = await response.body.arrayBuffer().then((res) => Buffer.from(res)); 85 | this.log(`Finished fetching ${attachment.id} (${buffer.length} bytes)`); 86 | 87 | // if the compression options are set, compress the image 88 | if (this.compression) { 89 | const sharp = await import('sharp'); 90 | 91 | this.log(`Compressing ${attachment.id} with 'sharp'`); 92 | const sharpbuf = await sharp 93 | .default(buffer) 94 | .webp({ 95 | quality: this.compression.quality, 96 | force: this.compression.convertToWebP, 97 | effort: 2, 98 | ...this.compression.options, 99 | }) 100 | .toBuffer({ resolveWithObject: true }); 101 | this.log(`Finished compressing ${attachment.id} (${sharpbuf.info.size} bytes)`); 102 | 103 | return `data:image/${sharpbuf.info.format};base64,${sharpbuf.data.toString('base64')}`; 104 | } 105 | 106 | // return the base64 string 107 | return `data:${mimetype};base64,${buffer.toString('base64')}`; 108 | }; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/generator/index.tsx: -------------------------------------------------------------------------------- 1 | import { type Awaitable, type Channel, type Message, type Role, type User } from 'discord.js'; 2 | import ReactDOMServer from 'react-dom/server'; 3 | import React from 'react'; 4 | import { buildProfiles } from '../utils/buildProfiles'; 5 | import { revealSpoiler, scrollToMessage } from '../static/client'; 6 | import { readFileSync } from 'fs'; 7 | import path from 'path'; 8 | import { renderToString } from '@derockdev/discord-components-core/hydrate'; 9 | import { streamToString } from '../utils/utils'; 10 | import DiscordMessages from './transcript'; 11 | import type { ResolveImageCallback } from '../downloader/images'; 12 | 13 | // read the package.json file and get the @derockdev/discord-components-core version 14 | let discordComponentsVersion = '^3.6.1'; 15 | 16 | try { 17 | const packagePath = path.join(__dirname, '..', '..', 'package.json'); 18 | const packageJSON = JSON.parse(readFileSync(packagePath, 'utf8')); 19 | discordComponentsVersion = packageJSON.dependencies['@derockdev/discord-components-core'] ?? discordComponentsVersion; 20 | // eslint-disable-next-line no-empty 21 | } catch {} // ignore errors 22 | 23 | export type RenderMessageContext = { 24 | messages: Message[]; 25 | channel: Channel; 26 | 27 | callbacks: { 28 | resolveImageSrc: ResolveImageCallback; 29 | resolveChannel: (channelId: string) => Awaitable; 30 | resolveUser: (userId: string) => Awaitable; 31 | resolveRole: (roleId: string) => Awaitable; 32 | }; 33 | 34 | poweredBy?: boolean; 35 | footerText?: string; 36 | saveImages: boolean; 37 | favicon: 'guild' | string; 38 | hydrate: boolean; 39 | }; 40 | 41 | export default async function render({ messages, channel, callbacks, ...options }: RenderMessageContext) { 42 | const profiles = buildProfiles(messages); 43 | 44 | // NOTE: this renders a STATIC site with no interactivity 45 | // if interactivity is needed, switch to renderToPipeableStream and use hydrateRoot on client. 46 | const stream = ReactDOMServer.renderToStaticNodeStream( 47 | 48 | 49 | 50 | 51 | 52 | {/* favicon */} 53 | 64 | 65 | {/* title */} 66 | {channel.isDMBased() ? 'Direct Messages' : channel.name} 67 | 68 | {/* message reference handler */} 69 | 83 | {/* component library */} 84 | 88 | 89 | )} 90 | 91 | 92 | 98 | 99 | 100 | 101 | {/* Make sure the script runs after the DOM has loaded */} 102 | {options.hydrate && } 103 | 104 | ); 105 | 106 | const markup = await streamToString(stream); 107 | 108 | if (options.hydrate) { 109 | const result = await renderToString(markup, { 110 | beforeHydrate: async (document) => { 111 | document.defaultView.$discordMessage = { 112 | profiles: await profiles, 113 | }; 114 | }, 115 | }); 116 | 117 | return result.html; 118 | } 119 | 120 | return markup; 121 | } 122 | -------------------------------------------------------------------------------- /src/generator/renderers/attachment.tsx: -------------------------------------------------------------------------------- 1 | import { DiscordAttachment, DiscordAttachments } from '@derockdev/discord-components-react'; 2 | import React from 'react'; 3 | import type { APIAttachment, APIMessage, Attachment as AttachmentType, Message } from 'discord.js'; 4 | import type { RenderMessageContext } from '..'; 5 | import type { AttachmentTypes } from '../../types'; 6 | import { formatBytes } from '../../utils/utils'; 7 | 8 | /** 9 | * Renders all attachments for a message 10 | * @param message 11 | * @param context 12 | * @returns 13 | */ 14 | export async function Attachments(props: { message: Message; context: RenderMessageContext }) { 15 | if (props.message.attachments.size === 0) return <>; 16 | 17 | return ( 18 | 19 | {props.message.attachments.map((attachment, id) => ( 20 | 21 | ))} 22 | 23 | ); 24 | } 25 | 26 | // "audio" | "video" | "image" | "file" 27 | function getAttachmentType(attachment: AttachmentType): AttachmentTypes { 28 | const type = attachment.contentType?.split('/')?.[0] ?? 'unknown'; 29 | if (['audio', 'video', 'image'].includes(type)) return type as AttachmentTypes; 30 | return 'file'; 31 | } 32 | 33 | /** 34 | * Renders one Discord Attachment 35 | * @param props - the attachment and rendering context 36 | */ 37 | export async function Attachment({ 38 | attachment, 39 | context, 40 | message, 41 | }: { 42 | attachment: AttachmentType; 43 | context: RenderMessageContext; 44 | message: Message; 45 | }) { 46 | let url = attachment.url; 47 | const name = attachment.name; 48 | const width = attachment.width; 49 | const height = attachment.height; 50 | 51 | const type = getAttachmentType(attachment); 52 | 53 | // if the attachment is an image, download it to a data url 54 | if (type === 'image') { 55 | const downloaded = await context.callbacks.resolveImageSrc( 56 | attachment.toJSON() as APIAttachment, 57 | message.toJSON() as APIMessage 58 | ); 59 | 60 | if (downloaded !== null) { 61 | url = downloaded ?? url; 62 | } 63 | } 64 | 65 | return ( 66 | 76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /src/generator/renderers/components.tsx: -------------------------------------------------------------------------------- 1 | import { DiscordActionRow, DiscordButton } from '@derockdev/discord-components-react'; 2 | import { ButtonStyle, ComponentType, type MessageActionRowComponent, type ActionRow } from 'discord.js'; 3 | import React from 'react'; 4 | import { parseDiscordEmoji } from '../../utils/utils'; 5 | 6 | export default function ComponentRow({ row, id }: { row: ActionRow; id: number }) { 7 | return ( 8 | 9 | {row.components.map((component, id) => ( 10 | 11 | ))} 12 | 13 | ); 14 | } 15 | 16 | const ButtonStyleMapping = { 17 | [ButtonStyle.Primary]: 'primary', 18 | [ButtonStyle.Secondary]: 'secondary', 19 | [ButtonStyle.Success]: 'success', 20 | [ButtonStyle.Danger]: 'destructive', 21 | [ButtonStyle.Link]: 'secondary', 22 | } as const; 23 | 24 | export function Component({ component, id }: { component: MessageActionRowComponent; id: number }) { 25 | if (component.type === ComponentType.Button) { 26 | return ( 27 | 33 | {component.label} 34 | 35 | ); 36 | } 37 | 38 | return undefined; 39 | } 40 | -------------------------------------------------------------------------------- /src/generator/renderers/content.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | DiscordBold, 3 | DiscordCodeBlock, 4 | DiscordCustomEmoji, 5 | DiscordInlineCode, 6 | DiscordItalic, 7 | DiscordMention, 8 | DiscordQuote, 9 | DiscordSpoiler, 10 | DiscordTime, 11 | DiscordUnderlined, 12 | } from '@derockdev/discord-components-react'; 13 | import parse, { type RuleTypesExtended } from 'discord-markdown-parser'; 14 | import { ChannelType, type APIMessageComponentEmoji } from 'discord.js'; 15 | import React from 'react'; 16 | import type { ASTNode } from 'simple-markdown'; 17 | import { ASTNode as MessageASTNodes } from 'simple-markdown'; 18 | import type { SingleASTNode } from 'simple-markdown'; 19 | import type { RenderMessageContext } from '../'; 20 | import { parseDiscordEmoji } from '../../utils/utils'; 21 | 22 | export enum RenderType { 23 | EMBED, 24 | REPLY, 25 | NORMAL, 26 | WEBHOOK, 27 | } 28 | 29 | type RenderContentContext = RenderMessageContext & { 30 | type: RenderType; 31 | 32 | _internal?: { 33 | largeEmojis?: boolean; 34 | }; 35 | }; 36 | 37 | /** 38 | * Renders discord markdown content 39 | * @param content - The content to render 40 | * @param context - The context to render the content in 41 | * @returns 42 | */ 43 | export default async function MessageContent({ content, context }: { content: string; context: RenderContentContext }) { 44 | if (context.type === RenderType.REPLY && content.length > 180) content = content.slice(0, 180) + '...'; 45 | 46 | // parse the markdown 47 | const parsed = parse( 48 | content, 49 | context.type === RenderType.EMBED || context.type === RenderType.WEBHOOK ? 'extended' : 'normal' 50 | ); 51 | 52 | // check if the parsed content is only emojis 53 | const isOnlyEmojis = parsed.every( 54 | (node) => ['emoji', 'twemoji'].includes(node.type) || (node.type === 'text' && node.content.trim().length === 0) 55 | ); 56 | if (isOnlyEmojis) { 57 | // now check if there are less than or equal to 25 emojis 58 | const emojis = parsed.filter((node) => ['emoji', 'twemoji'].includes(node.type)); 59 | if (emojis.length <= 25) { 60 | context._internal = { 61 | largeEmojis: true, 62 | }; 63 | } 64 | } 65 | 66 | return ; 67 | } 68 | 69 | // This function can probably be combined into the MessageSingleASTNode function 70 | async function MessageASTNodes({ 71 | nodes, 72 | context, 73 | }: { 74 | nodes: ASTNode; 75 | context: RenderContentContext; 76 | }): Promise { 77 | if (Array.isArray(nodes)) { 78 | return ( 79 | <> 80 | {nodes.map((node, i) => ( 81 | 82 | ))} 83 | 84 | ); 85 | } else { 86 | return ; 87 | } 88 | } 89 | 90 | export async function MessageSingleASTNode({ node, context }: { node: SingleASTNode; context: RenderContentContext }) { 91 | if (!node) return null; 92 | 93 | const type = node.type as RuleTypesExtended; 94 | 95 | switch (type) { 96 | case 'text': 97 | return node.content; 98 | 99 | case 'link': 100 | return ( 101 | 102 | 103 | 104 | ); 105 | 106 | case 'url': 107 | case 'autolink': 108 | return ( 109 | 110 | 111 | 112 | ); 113 | 114 | case 'blockQuote': 115 | if (context.type === RenderType.REPLY) { 116 | return ; 117 | } 118 | 119 | return ( 120 | 121 | 122 | 123 | ); 124 | 125 | case 'br': 126 | case 'newline': 127 | if (context.type === RenderType.REPLY) return ' '; 128 | return
; 129 | 130 | case 'channel': { 131 | const id = node.id as string; 132 | const channel = await context.callbacks.resolveChannel(id); 133 | 134 | return ( 135 | 136 | {channel ? (channel.isDMBased() ? 'DM Channel' : channel.name) : `<#${id}>`} 137 | 138 | ); 139 | } 140 | 141 | case 'role': { 142 | const id = node.id as string; 143 | const role = await context.callbacks.resolveRole(id); 144 | 145 | return ( 146 | 147 | {role ? role.name : `<@&${id}>`} 148 | 149 | ); 150 | } 151 | 152 | case 'user': { 153 | const id = node.id as string; 154 | const user = await context.callbacks.resolveUser(id); 155 | 156 | return {user ? user.displayName ?? user.username : `<@${id}>`}; 157 | } 158 | 159 | case 'here': 160 | case 'everyone': 161 | return ( 162 | 163 | {`@${type}`} 164 | 165 | ); 166 | 167 | case 'codeBlock': 168 | if (context.type !== RenderType.REPLY) { 169 | return ; 170 | } 171 | return {node.content}; 172 | 173 | case 'inlineCode': 174 | return {node.content}; 175 | 176 | case 'em': 177 | return ( 178 | 179 | 180 | 181 | ); 182 | 183 | case 'strong': 184 | return ( 185 | 186 | 187 | 188 | ); 189 | 190 | case 'underline': 191 | return ( 192 | 193 | 194 | 195 | ); 196 | 197 | case 'strikethrough': 198 | return ( 199 | 200 | 201 | 202 | ); 203 | 204 | case 'emoticon': 205 | return typeof node.content === 'string' ? ( 206 | node.content 207 | ) : ( 208 | 209 | ); 210 | 211 | case 'spoiler': 212 | return ( 213 | 214 | 215 | 216 | ); 217 | 218 | case 'emoji': 219 | case 'twemoji': 220 | return ( 221 | 227 | ); 228 | 229 | case 'timestamp': 230 | return ; 231 | 232 | default: { 233 | console.log(`Unknown node type: ${type}`, node); 234 | return typeof node.content === 'string' ? ( 235 | node.content 236 | ) : ( 237 | 238 | ); 239 | } 240 | } 241 | } 242 | 243 | export function getChannelType(channelType: ChannelType): 'channel' | 'voice' | 'thread' | 'forum' { 244 | switch (channelType) { 245 | case ChannelType.GuildCategory: 246 | case ChannelType.GuildAnnouncement: 247 | case ChannelType.GuildText: 248 | return 'channel'; 249 | case ChannelType.GuildVoice: 250 | case ChannelType.GuildStageVoice: 251 | return 'voice'; 252 | case ChannelType.PublicThread: 253 | case ChannelType.PrivateThread: 254 | case ChannelType.AnnouncementThread: 255 | return 'thread'; 256 | case ChannelType.GuildForum: 257 | return 'forum'; 258 | default: 259 | return 'channel'; 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/generator/renderers/embed.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | DiscordEmbed as DiscordEmbedComponent, 3 | DiscordEmbedDescription, 4 | DiscordEmbedField, 5 | DiscordEmbedFields, 6 | DiscordEmbedFooter, 7 | } from '@derockdev/discord-components-react'; 8 | import type { Embed, Message } from 'discord.js'; 9 | import React from 'react'; 10 | import type { RenderMessageContext } from '..'; 11 | import { calculateInlineIndex } from '../../utils/embeds'; 12 | import MessageContent, { RenderType } from './content'; 13 | 14 | type RenderEmbedContext = RenderMessageContext & { 15 | index: number; 16 | message: Message; 17 | }; 18 | 19 | export async function DiscordEmbed({ embed, context }: { embed: Embed; context: RenderEmbedContext }) { 20 | return ( 21 | 33 | {/* Description */} 34 | {embed.description && ( 35 | 36 | 37 | 38 | )} 39 | 40 | {/* Fields */} 41 | {embed.fields.length > 0 && ( 42 | 43 | {embed.fields.map(async (field, id) => ( 44 | 50 | 51 | 52 | ))} 53 | 54 | )} 55 | 56 | {/* Footer */} 57 | {embed.footer && ( 58 | 63 | {embed.footer.text} 64 | 65 | )} 66 | 67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /src/generator/renderers/message.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | DiscordAttachments, 3 | DiscordCommand, 4 | DiscordMessage as DiscordMessageComponent, 5 | DiscordReaction, 6 | DiscordReactions, 7 | DiscordThread, 8 | DiscordThreadMessage, 9 | } from '@derockdev/discord-components-react'; 10 | import type { Message as MessageType } from 'discord.js'; 11 | import React from 'react'; 12 | import type { RenderMessageContext } from '..'; 13 | import { parseDiscordEmoji } from '../../utils/utils'; 14 | import { Attachments } from './attachment'; 15 | import ComponentRow from './components'; 16 | import MessageContent, { RenderType } from './content'; 17 | import { DiscordEmbed } from './embed'; 18 | import MessageReply from './reply'; 19 | import DiscordSystemMessage from './systemMessage'; 20 | 21 | export default async function DiscordMessage({ 22 | message, 23 | context, 24 | }: { 25 | message: MessageType; 26 | context: RenderMessageContext; 27 | }) { 28 | if (message.system) return ; 29 | 30 | const isCrosspost = message.reference && message.reference.guildId !== message.guild?.id; 31 | 32 | return ( 33 | 42 | {/* reply */} 43 | 44 | 45 | {/* slash command */} 46 | {message.interaction && ( 47 | 52 | )} 53 | 54 | {/* message content */} 55 | {message.content && ( 56 | 60 | )} 61 | 62 | {/* attachments */} 63 | 64 | 65 | {/* message embeds */} 66 | {message.embeds.map((embed, id) => ( 67 | 68 | ))} 69 | 70 | {/* components */} 71 | {message.components.length > 0 && ( 72 | 73 | {message.components.map((component, id) => ( 74 | 75 | ))} 76 | 77 | )} 78 | 79 | {/* reactions */} 80 | {message.reactions.cache.size > 0 && ( 81 | 82 | {message.reactions.cache.map((reaction, id) => ( 83 | 89 | ))} 90 | 91 | )} 92 | 93 | {/* threads */} 94 | {message.hasThread && message.thread && ( 95 | 1 ? 's' : ''}` 101 | : 'View Thread' 102 | } 103 | > 104 | {message.thread.lastMessage ? ( 105 | 106 | 128 109 | ? message.thread.lastMessage.content.substring(0, 125) + '...' 110 | : message.thread.lastMessage.content 111 | } 112 | context={{ ...context, type: RenderType.REPLY }} 113 | /> 114 | 115 | ) : ( 116 | `Thread messages not saved.` 117 | )} 118 | 119 | )} 120 | 121 | ); 122 | } 123 | -------------------------------------------------------------------------------- /src/generator/renderers/reply.tsx: -------------------------------------------------------------------------------- 1 | import { DiscordReply } from '@derockdev/discord-components-react'; 2 | import { type Message, UserFlags } from 'discord.js'; 3 | import type { RenderMessageContext } from '..'; 4 | import React from 'react'; 5 | import MessageContent, { RenderType } from './content'; 6 | 7 | export default async function MessageReply({ message, context }: { message: Message; context: RenderMessageContext }) { 8 | if (!message.reference) return null; 9 | if (message.reference.guildId !== message.guild?.id) return null; 10 | 11 | const referencedMessage = context.messages.find((m) => m.id === message.reference!.messageId); 12 | 13 | if (!referencedMessage) return Message could not be loaded.; 14 | 15 | const isCrosspost = referencedMessage.reference && referencedMessage.reference.guildId !== message.guild?.id; 16 | const isCommand = referencedMessage.interaction !== null; 17 | 18 | return ( 19 | 0} 23 | author={ 24 | referencedMessage.member?.nickname ?? referencedMessage.author.displayName ?? referencedMessage.author.username 25 | } 26 | avatar={referencedMessage.author.avatarURL({ size: 32 }) ?? undefined} 27 | roleColor={referencedMessage.member?.displayHexColor ?? undefined} 28 | bot={!isCrosspost && referencedMessage.author.bot} 29 | verified={referencedMessage.author.flags?.has(UserFlags.VerifiedBot)} 30 | op={message?.channel?.isThread?.() && referencedMessage.author.id === message?.channel?.ownerId} 31 | server={isCrosspost ?? undefined} 32 | command={isCommand} 33 | > 34 | {referencedMessage.content ? ( 35 | 36 | 37 | 38 | ) : isCommand ? ( 39 | Click to see command. 40 | ) : ( 41 | Click to see attachment. 42 | )} 43 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /src/generator/renderers/systemMessage.tsx: -------------------------------------------------------------------------------- 1 | import { DiscordReaction, DiscordReactions, DiscordSystemMessage } from '@derockdev/discord-components-react'; 2 | import { MessageType, type GuildMember, type Message, type User } from 'discord.js'; 3 | import React from 'react'; 4 | import { parseDiscordEmoji } from '../../utils/utils'; 5 | 6 | export default async function SystemMessage({ message }: { message: Message }) { 7 | switch (message.type) { 8 | case MessageType.RecipientAdd: 9 | case MessageType.UserJoin: 10 | return ( 11 | 12 | 13 | 14 | ); 15 | 16 | case MessageType.ChannelPinnedMessage: 17 | return ( 18 | 19 | 20 | {message.author.displayName ?? message.author.username} 21 | {' '} 22 | pinned a message to this channel. 23 | {/* reactions */} 24 | {message.reactions.cache.size > 0 && ( 25 | 26 | {message.reactions.cache.map((reaction, id) => ( 27 | 33 | ))} 34 | 35 | )} 36 | 37 | ); 38 | 39 | case MessageType.GuildBoost: 40 | case MessageType.GuildBoostTier1: 41 | case MessageType.GuildBoostTier2: 42 | case MessageType.GuildBoostTier3: 43 | return ( 44 | 45 | 46 | {message.author.displayName ?? message.author.username} 47 | {' '} 48 | boosted the server! 49 | 50 | ); 51 | 52 | case MessageType.ThreadStarterMessage: 53 | return ( 54 | 55 | 56 | {message.author.displayName ?? message.author.username} 57 | {' '} 58 | started a thread: {message.content} 59 | 60 | ); 61 | 62 | default: 63 | return undefined; 64 | } 65 | } 66 | 67 | export function Highlight({ children, color }: { children: React.ReactNode; color?: string }) { 68 | return {children}; 69 | } 70 | 71 | const allJoinMessages = [ 72 | '{user} just joined the server - glhf!', 73 | '{user} just joined. Everyone, look busy!', 74 | '{user} just joined. Can I get a heal?', 75 | '{user} joined your party.', 76 | '{user} joined. You must construct additional pylons.', 77 | 'Ermagherd. {user} is here.', 78 | 'Welcome, {user}. Stay awhile and listen.', 79 | 'Welcome, {user}. We were expecting you ( ͡° ͜ʖ ͡°)', 80 | 'Welcome, {user}. We hope you brought pizza.', 81 | 'Welcome {user}. Leave your weapons by the door.', 82 | 'A wild {user} appeared.', 83 | 'Swoooosh. {user} just landed.', 84 | 'Brace yourselves {user} just joined the server.', 85 | '{user} just joined. Hide your bananas.', 86 | '{user} just arrived. Seems OP - please nerf.', 87 | '{user} just slid into the server.', 88 | 'A {user} has spawned in the server.', 89 | 'Big {user} showed up!', 90 | "Where's {user}? In the server!", 91 | '{user} hopped into the server. Kangaroo!!', 92 | '{user} just showed up. Hold my beer.', 93 | 'Challenger approaching - {user} has appeared!', 94 | "It's a bird! It's a plane! Nevermind, it's just {user}.", 95 | "It's {user}! Praise the sun! \\\\[T]/", 96 | 'Never gonna give {user} up. Never gonna let {user} down.', 97 | 'Ha! {user} has joined! You activated my trap card!', 98 | 'Cheers, love! {user} is here!', 99 | 'Hey! Listen! {user} has joined!', 100 | "We've been expecting you {user}", 101 | "It's dangerous to go alone, take {user}!", 102 | "{user} has joined the server! It's super effective!", 103 | 'Cheers, love! {user} is here!', 104 | '{user} is here, as the prophecy foretold.', 105 | "{user} has arrived. Party's over.", 106 | 'Ready player {user}', 107 | '{user} is here to kick butt and chew bubblegum. And {user} is all out of gum.', 108 | "Hello. Is it {user} you're looking for?", 109 | ]; 110 | 111 | export function JoinMessage({ member, fallbackUser }: { member: GuildMember | null; fallbackUser: User }) { 112 | const randomMessage = allJoinMessages[Math.floor(Math.random() * allJoinMessages.length)]; 113 | 114 | return randomMessage 115 | .split('{user}') 116 | .flatMap((item, i) => [ 117 | item, 118 | 119 | {member?.nickname ?? fallbackUser.displayName ?? fallbackUser.username} 120 | , 121 | ]) 122 | .slice(0, -1); 123 | } 124 | -------------------------------------------------------------------------------- /src/generator/transcript.tsx: -------------------------------------------------------------------------------- 1 | import { DiscordHeader, DiscordMessages as DiscordMessagesComponent } from '@derockdev/discord-components-react'; 2 | import { ChannelType } from 'discord.js'; 3 | import React from 'react'; 4 | import type { RenderMessageContext } from '.'; 5 | import MessageContent, { RenderType } from './renderers/content'; 6 | import DiscordMessage from './renderers/message'; 7 | 8 | /** 9 | * The core transcript component. 10 | * Expects window.$discordMessage.profiles to be set for profile information. 11 | * 12 | * @param props Messages, channel details, callbacks, etc. 13 | * @returns 14 | */ 15 | export default async function DiscordMessages({ messages, channel, callbacks, ...options }: RenderMessageContext) { 16 | return ( 17 | 18 | {/* header */} 19 | 30 | {channel.isThread() ? ( 31 | `Thread channel in ${channel.parent?.name ?? 'Unknown Channel'}` 32 | ) : channel.isDMBased() ? ( 33 | `Direct Messages` 34 | ) : channel.isVoiceBased() ? ( 35 | `Voice Text Channel for ${channel.name}` 36 | ) : channel.type === ChannelType.GuildCategory ? ( 37 | `Category Channel` 38 | ) : 'topic' in channel && channel.topic ? ( 39 | 43 | ) : ( 44 | `This is the start of #${channel.name} channel.` 45 | )} 46 | 47 | 48 | {/* body */} 49 | {messages.map((message) => ( 50 | 51 | ))} 52 | 53 | {/* footer */} 54 |
55 | {options.footerText 56 | ? options.footerText 57 | .replaceAll('{number}', messages.length.toString()) 58 | .replaceAll('{s}', messages.length > 1 ? 's' : '') 59 | : `Exported ${messages.length} message${messages.length > 1 ? 's' : ''}.`}{' '} 60 | {options.poweredBy ? ( 61 | 62 | Powered by{' '} 63 | 64 | discord-html-transcripts 65 | 66 | . 67 | 68 | ) : null} 69 |
70 |
71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { AttachmentBuilder, version, Collection, type Channel, type Message, type TextBasedChannel } from 'discord.js'; 2 | import DiscordMessages from './generator'; 3 | import { 4 | ExportReturnType, 5 | type CreateTranscriptOptions, 6 | type GenerateFromMessagesOptions, 7 | type ObjectType, 8 | } from './types'; 9 | import { TranscriptImageDownloader, type ResolveImageCallback } from './downloader/images'; 10 | 11 | // re-exports 12 | export { default as DiscordMessages } from './generator/transcript'; 13 | export { TranscriptImageDownloader } from './downloader/images'; 14 | 15 | // version check 16 | const versionPrefix = version.split('.')[0]; 17 | 18 | if (versionPrefix !== '14' && versionPrefix !== '15') { 19 | console.error( 20 | `[discord-html-transcripts] Versions v3.x.x of discord-html-transcripts are only compatible with discord.js v14.x.x and v15.x.x, and you are using v${version}.` + 21 | ` For v13.x.x support, please install discord-html-transcripts v2.x.x using "npm install discord-html-transcripts@^2".` 22 | ); 23 | process.exit(1); 24 | } 25 | 26 | /** 27 | * 28 | * @param messages The messages to generate a transcript from 29 | * @param channel The channel the messages are from (used for header and guild name) 30 | * @param options The options to use when generating the transcript 31 | * @returns The generated transcript 32 | */ 33 | export async function generateFromMessages( 34 | messages: Message[] | Collection, 35 | channel: Channel, 36 | options: GenerateFromMessagesOptions = {} 37 | ): Promise> { 38 | // turn messages into an array 39 | const transformedMessages = messages instanceof Collection ? Array.from(messages.values()) : messages; 40 | 41 | // figure out how the user wants images saved 42 | let resolveImageSrc: ResolveImageCallback = options.callbacks?.resolveImageSrc ?? ((attachment) => attachment.url); 43 | if (options.saveImages) { 44 | if (options.callbacks?.resolveImageSrc) { 45 | console.warn( 46 | `[discord-html-transcripts] You have specified both saveImages and resolveImageSrc, please only specify one. resolveImageSrc will be used.` 47 | ); 48 | } else { 49 | resolveImageSrc = new TranscriptImageDownloader().build(); 50 | console.log('Using default downloader'); 51 | } 52 | } 53 | 54 | // render the messages 55 | const html = await DiscordMessages({ 56 | messages: transformedMessages, 57 | channel, 58 | saveImages: options.saveImages ?? false, 59 | callbacks: { 60 | resolveImageSrc, 61 | resolveChannel: async (id) => channel.client.channels.fetch(id).catch(() => null), 62 | resolveUser: async (id) => channel.client.users.fetch(id).catch(() => null), 63 | resolveRole: channel.isDMBased() ? () => null : async (id) => channel.guild?.roles.fetch(id).catch(() => null), 64 | 65 | ...(options.callbacks ?? {}), 66 | }, 67 | poweredBy: options.poweredBy ?? true, 68 | footerText: options.footerText ?? 'Exported {number} message{s}.', 69 | favicon: options.favicon ?? 'guild', 70 | hydrate: options.hydrate ?? false, 71 | }); 72 | 73 | // get the time it took to render the messages 74 | // const renderTime = process.hrtime(startTime); 75 | // console.log( 76 | // `[discord-html-transcripts] Rendered ${transformedMessages.length} messages in ${renderTime[0]}s ${ 77 | // renderTime[1] / 1000000 78 | // }ms` 79 | // ); 80 | 81 | // return the html in the specified format 82 | if (options.returnType === ExportReturnType.Buffer) { 83 | return Buffer.from(html) as unknown as ObjectType; 84 | } 85 | 86 | if (options.returnType === ExportReturnType.String) { 87 | return html as unknown as ObjectType; 88 | } 89 | 90 | return new AttachmentBuilder(Buffer.from(html), { 91 | name: options.filename ?? `transcript-${channel.id}.html`, 92 | }) as unknown as ObjectType; 93 | } 94 | 95 | /** 96 | * 97 | * @param channel The channel to create a transcript from 98 | * @param options The options to use when creating the transcript 99 | * @returns The generated transcript 100 | */ 101 | export async function createTranscript( 102 | channel: TextBasedChannel, 103 | options: CreateTranscriptOptions = {} 104 | ): Promise> { 105 | // validate type 106 | if (!channel.isTextBased()) { 107 | // @ts-expect-error(2339): run-time check 108 | throw new TypeError(`Provided channel must be text-based, received ${channel.type}`); 109 | } 110 | 111 | // fetch messages 112 | let allMessages: Message[] = []; 113 | let lastMessageId: string | undefined; 114 | const { limit, filter } = options; 115 | const resolvedLimit = typeof limit === 'undefined' || limit === -1 ? Infinity : limit; 116 | 117 | // until there are no more messages, keep fetching 118 | // eslint-disable-next-line no-constant-condition 119 | while (true) { 120 | // create fetch options 121 | const fetchLimitOptions = { limit: 100, before: lastMessageId }; 122 | if (!lastMessageId) delete fetchLimitOptions.before; 123 | 124 | // fetch messages 125 | const messages = await channel.messages.fetch(fetchLimitOptions); 126 | const filteredMessages = typeof filter === 'function' ? messages.filter(filter) : messages; 127 | 128 | // add the messages to the array 129 | allMessages.push(...filteredMessages.values()); 130 | // Get the last key of 'messages', not 'filteredMessages' because you will be refetching the same messages 131 | lastMessageId = messages.lastKey(); 132 | 133 | // if there are no more messages, break 134 | if (messages.size < 100) break; 135 | 136 | // if the limit has been reached, break 137 | if (allMessages.length >= resolvedLimit) break; 138 | } 139 | 140 | if (resolvedLimit < allMessages.length) allMessages = allMessages.slice(0, limit); 141 | 142 | // generate the transcript 143 | return generateFromMessages(allMessages.reverse(), channel, options); 144 | } 145 | 146 | export default { 147 | createTranscript, 148 | generateFromMessages, 149 | }; 150 | export * from './types'; 151 | -------------------------------------------------------------------------------- /src/static/client.ts: -------------------------------------------------------------------------------- 1 | // TODO: create some sort of build system to compile this file 2 | 3 | /* 4 | // whenever user clicks on element with data-goto attribute, scroll to that message 5 | document.addEventListener('click', (e) => { 6 | const target = e.target; 7 | if(!target) return; 8 | 9 | const goto = target?.getAttribute('data-goto'); 10 | 11 | if (goto) { 12 | const message = document.getElementById(`m-\${goto}`); 13 | if (message) { 14 | message.scrollIntoView({ behavior: 'smooth', block: 'center' }); 15 | message.style.backgroundColor = 'rgba(148, 156, 247, 0.1)'; 16 | message.style.transition = 'background-color 0.5s ease'; 17 | setTimeout(() => { 18 | message.style.backgroundColor = 'transparent'; 19 | }, 1000); 20 | } else { 21 | console.warn(`Message \${goto} not found.`); 22 | } 23 | } 24 | }); 25 | */ 26 | export const scrollToMessage = 27 | 'document.addEventListener("click",t=>{let e=t.target;if(!e)return;let o=e?.getAttribute("data-goto");if(o){let r=document.getElementById(`m-${o}`);r?(r.scrollIntoView({behavior:"smooth",block:"center"}),r.style.backgroundColor="rgba(148, 156, 247, 0.1)",r.style.transition="background-color 0.5s ease",setTimeout(()=>{r.style.backgroundColor="transparent"},1e3)):console.warn("Message ${goto} not found.")}});'; 28 | 29 | export const revealSpoiler = 30 | 'const s=document.querySelectorAll(".discord-spoiler");s.forEach(s=>s.addEventListener("click",()=>{if(s.classList.contains("discord-spoiler")){s.classList.remove("discord-spoiler");s.classList.add("discord-spoiler--revealed");}}));'; 31 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { AttachmentBuilder, Message } from 'discord.js'; 2 | import type { RenderMessageContext } from './generator'; 3 | 4 | export type AttachmentTypes = 'audio' | 'video' | 'image' | 'file'; 5 | 6 | export enum ExportReturnType { 7 | Buffer = 'buffer', 8 | String = 'string', 9 | Attachment = 'attachment', 10 | } 11 | 12 | export type ObjectType = T extends ExportReturnType.Buffer 13 | ? Buffer 14 | : T extends ExportReturnType.String 15 | ? string 16 | : AttachmentBuilder; 17 | 18 | export type GenerateFromMessagesOptions = Partial<{ 19 | /** 20 | * The type of object to return 21 | * @default ExportReturnType.ATTACHMENT 22 | */ 23 | returnType: T; 24 | 25 | /** 26 | * Downloads images and encodes them as base64 data urls 27 | * @default false 28 | */ 29 | saveImages: boolean; 30 | 31 | /** 32 | * Callbacks for resolving channels, users, and roles 33 | */ 34 | callbacks: Partial; 35 | 36 | /** 37 | * The name of the file to return if returnType is ExportReturnType.ATTACHMENT 38 | * @default 'transcript-{channel-id}.html' 39 | */ 40 | filename: string; 41 | 42 | /** 43 | * Whether to include the "Powered by discord-html-transcripts" footer 44 | * @default true 45 | */ 46 | poweredBy: boolean; 47 | 48 | /** 49 | * The message right before "Powered by" text. Remember to put the {s} 50 | * @default 'Exported {number} message{s}.' 51 | */ 52 | footerText: string; 53 | 54 | /** 55 | * Whether to show the guild icon or a custom icon as the favicon 56 | * 'guild' - use the guild icon 57 | * or pass in a url to use a custom icon 58 | * @default "guild" 59 | */ 60 | favicon: 'guild' | string; 61 | 62 | /** 63 | * Whether to hydrate the html server-side 64 | * @default false - the returned html will be hydrated client-side 65 | */ 66 | hydrate: boolean; 67 | }>; 68 | 69 | export type CreateTranscriptOptions = Partial< 70 | GenerateFromMessagesOptions & { 71 | /** 72 | * The max amount of messages to fetch. Use `-1` to recursively fetch. 73 | */ 74 | limit: number; 75 | 76 | /** 77 | * Filter messages of the channel 78 | * @default (() => true) 79 | */ 80 | filter: (message: Message) => boolean; 81 | } 82 | >; 83 | -------------------------------------------------------------------------------- /src/utils/buildProfiles.ts: -------------------------------------------------------------------------------- 1 | import { type GuildMember, type Message, type User, UserFlags } from 'discord.js'; 2 | 3 | export type Profile = { 4 | author: string; // author of the message 5 | avatar?: string; // avatar of the author 6 | roleColor?: string; // role color of the author 7 | roleIcon?: string; // role color of the author 8 | roleName?: string; // role name of the author 9 | 10 | bot?: boolean; // is the author a bot 11 | verified?: boolean; // is the author verified 12 | }; 13 | 14 | export async function buildProfiles(messages: Message[]) { 15 | const profiles: Record = {}; 16 | 17 | // loop through messages 18 | for (const message of messages) { 19 | // add all users 20 | const author = message.author; 21 | if (!profiles[author.id]) { 22 | // add profile 23 | profiles[author.id] = buildProfile(message.member, author); 24 | } 25 | 26 | // add interaction users 27 | if (message.interaction) { 28 | const user = message.interaction.user; 29 | if (!profiles[user.id]) { 30 | profiles[user.id] = buildProfile(null, user); 31 | } 32 | } 33 | 34 | // threads 35 | if (message.thread && message.thread.lastMessage) { 36 | profiles[message.thread.lastMessage.author.id] = buildProfile( 37 | message.thread.lastMessage.member, 38 | message.thread.lastMessage.author 39 | ); 40 | } 41 | } 42 | 43 | // return as a JSON 44 | return profiles; 45 | } 46 | 47 | function buildProfile(member: GuildMember | null, author: User) { 48 | return { 49 | author: member?.nickname ?? author.displayName ?? author.username, 50 | avatar: member?.displayAvatarURL({ size: 64 }) ?? author.displayAvatarURL({ size: 64 }), 51 | roleColor: member?.displayHexColor, 52 | roleIcon: member?.roles.icon?.iconURL() ?? undefined, 53 | roleName: member?.roles.hoist?.name ?? undefined, 54 | bot: author.bot, 55 | verified: author.flags?.has(UserFlags.VerifiedBot), 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /src/utils/embeds.ts: -------------------------------------------------------------------------------- 1 | import type { APIEmbedField } from 'discord.js'; 2 | 3 | export function calculateInlineIndex(fields: APIEmbedField[], currentFieldIndex: number) { 4 | const startIndex = currentFieldIndex - 1; 5 | 6 | for (let i = startIndex; i >= 0; i--) { 7 | const field = fields[i]; 8 | if (!field) continue; 9 | 10 | if (field.inline === false) { 11 | const amount = startIndex - i; 12 | return (amount % 3) + 1; 13 | } 14 | } 15 | 16 | return (currentFieldIndex % 3) + 1; 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/extend.ts: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from 'react'; 2 | import type { ASTNode, ParserRule } from 'simple-markdown'; 3 | import type { State } from './types'; 4 | 5 | type AdditionalRule = Partial & { 6 | react: (node: ASTNode, output: (node: ASTNode, state?: unknown) => string, state: State) => ReactNode; 7 | }; 8 | 9 | export const extend = (additionalRules: AdditionalRule, defaultRule: ParserRule): AdditionalRule => { 10 | return Object.assign({}, defaultRule, additionalRules); 11 | }; 12 | -------------------------------------------------------------------------------- /src/utils/types.d.ts: -------------------------------------------------------------------------------- 1 | export type State = { 2 | key?: string | number | undefined; 3 | inline?: boolean | undefined; 4 | 5 | callbacks: { 6 | resolveChannel: (channelId: string) => string; 7 | resolveUser: (userId: string) => string; 8 | resolveEmoji: (emojiId: string) => string; 9 | resolveRole: (roleId: string) => string; 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import type { APIMessageComponentEmoji, Emoji } from 'discord.js'; 2 | import twemoji from 'twemoji'; 3 | 4 | export function isDefined(value: T | undefined | null): value is T { 5 | return value !== undefined && value !== null; 6 | } 7 | 8 | export function formatBytes(bytes: number, decimals = 2) { 9 | if (bytes === 0) return '0 Bytes'; 10 | 11 | const k = 1024; 12 | const dm = decimals < 0 ? 0 : decimals; 13 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; 14 | 15 | const i = Math.floor(Math.log(bytes) / Math.log(k)); 16 | 17 | return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; 18 | } 19 | 20 | export function parseDiscordEmoji(emoji: Emoji | APIMessageComponentEmoji) { 21 | if (emoji.id) { 22 | return `https://cdn.discordapp.com/emojis/${emoji.id}.${emoji.animated ? 'gif' : 'png'}`; 23 | } 24 | 25 | const codepoints = twemoji.convert 26 | .toCodePoint( 27 | emoji.name!.indexOf(String.fromCharCode(0x200d)) < 0 ? emoji.name!.replace(/\uFE0F/g, '') : emoji.name! 28 | ) 29 | .toLowerCase(); 30 | 31 | return `https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/svg/${codepoints}.svg`; 32 | } 33 | 34 | /** 35 | * Converts a stream to a string 36 | * @param stream - The stream to convert 37 | */ 38 | export function streamToString(stream: NodeJS.ReadableStream) { 39 | const chunks: Buffer[] = []; 40 | 41 | return new Promise((resolve, reject) => { 42 | stream.on('data', (chunk) => chunks.push(chunk)); 43 | stream.on('error', reject); 44 | stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Tests 2 | 3 | not much here 4 | -------------------------------------------------------------------------------- /tests/generate.ts: -------------------------------------------------------------------------------- 1 | import * as discord from 'discord.js'; 2 | import { createTranscript } from '../src'; 3 | 4 | import { config } from 'dotenv'; 5 | config(); 6 | 7 | const { GuildMessages, Guilds, MessageContent } = discord.GatewayIntentBits; 8 | 9 | const client = new discord.Client({ 10 | intents: [GuildMessages, Guilds, MessageContent], 11 | }); 12 | 13 | client.on('ready', async () => { 14 | console.log('Fetching channel: ', process.env.CHANNEL!); 15 | const channel = await client.channels.fetch(process.env.CHANNEL!); 16 | 17 | if (!channel || !channel.isTextBased()) { 18 | console.error('Invalid channel provided.'); 19 | process.exit(1); 20 | } 21 | 22 | console.time('transcript'); 23 | 24 | const attachment = await createTranscript(channel, { 25 | // options go here 26 | }); 27 | 28 | console.timeEnd('transcript'); 29 | 30 | await (channel as discord.TextChannel).send({ 31 | content: 'Here is the transcript', 32 | files: [attachment], 33 | }); 34 | 35 | client.destroy(); 36 | process.exit(0); 37 | }); 38 | 39 | client.login(process.env.TOKEN!); 40 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "skipLibCheck": true 6 | }, 7 | "include": ["src", "tests", "tsup.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "types": ["@types/node", "react/experimental", "@types/react-dom"], 6 | "strict": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "esModuleInterop": true, 9 | "module": "commonjs", 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": true, 15 | "outDir": "dist", 16 | "sourceMap": true, 17 | "allowUnreachableCode": false, 18 | "allowUnusedLabels": false, 19 | "exactOptionalPropertyTypes": false, 20 | "noImplicitOverride": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "noUnusedParameters": true, 23 | "pretty": true, 24 | "moduleResolution": "node", 25 | "jsx": "react", 26 | "incremental": true, 27 | "declaration": true, 28 | "skipLibCheck": true 29 | }, 30 | "include": ["src"], 31 | "exclude": ["node_modules"] 32 | } 33 | --------------------------------------------------------------------------------