├── .github
└── workflows
│ └── deploy.yml
├── .gitignore
├── LICENSE
├── README.md
├── biome.json
├── demo.png
├── index.html
├── package.json
├── pnpm-lock.yaml
├── public
└── happy.png
├── scripts
└── console-snippet.js
├── src
├── App.tsx
├── index.css
├── main.tsx
└── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to GitHub Pages
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | build:
10 | name: Build Vite app
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v4
15 | - uses: actions/setup-node@v4
16 | - run: corepack enable
17 | - run: pnpm install
18 | - run: pnpm build
19 | - uses: actions/upload-artifact@v4
20 | with:
21 | name: dist
22 | path: ./dist
23 |
24 | deploy:
25 | name: Deploy Vite app
26 | needs: build
27 | runs-on: ubuntu-latest
28 | if: github.ref == 'refs/heads/main'
29 |
30 | steps:
31 | - uses: actions/download-artifact@v4
32 | with:
33 | name: dist
34 | path: ./dist
35 |
36 | - name: Deploy to GitHub Pages
37 | uses: peaceiris/actions-gh-pages@v3
38 | with:
39 | github_token: ${{ secrets.GITHUB_TOKEN }}
40 | publish_dir: ./dist
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
26 | friends*.json
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Benjamin Richeson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Discord Friend Graph
2 |
3 | Create a graph of your friends and mutual friends.
4 |
5 | 
6 |
7 | ### Getting Started
8 | Run [this console script](scripts/console-snippet.js) in your Discord dev tools console. Paste the output into a `.json` file, and select it on the website
9 |
10 | > [!CAUTION]
11 | > Use at your own risk.
12 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
3 |
4 | "files": {
5 | "ignore": ["./build", "./dist", "./node_modules"]
6 | },
7 |
8 | "organizeImports": {
9 | "enabled": true
10 | },
11 |
12 | "linter": {
13 | "enabled": true,
14 | "rules": {
15 | "recommended": true,
16 | "a11y": {},
17 |
18 | "complexity": {
19 | "noForEach": "warn",
20 | "noWith": "error",
21 | "noUselessLabel": "warn",
22 | "noUselessRename": "warn",
23 | "useArrowFunction": "warn",
24 | "noUselessConstructor": "warn",
25 | "noMultipleSpacesInRegularExpressionLiterals": "warn"
26 | },
27 |
28 | "correctness": {
29 | "noUnusedVariables": "warn"
30 | },
31 |
32 | "nursery": {},
33 |
34 | "performance": {},
35 |
36 | "security": {},
37 |
38 | "style": {
39 | "useEnumInitializers": "off",
40 | "noNonNullAssertion": "off"
41 | },
42 |
43 | "suspicious": {}
44 | },
45 | "ignore": ["./scripts/console-snippet.js"]
46 | },
47 |
48 | "formatter": {
49 | "formatWithErrors": true,
50 | "indentStyle": "space",
51 | "indentWidth": 2,
52 | "lineEnding": "lf",
53 | "lineWidth": 80
54 | },
55 |
56 | "javascript": {
57 | "formatter": {
58 | "arrowParentheses": "asNeeded",
59 | "bracketSameLine": false,
60 | "bracketSpacing": false,
61 | "jsxQuoteStyle": "single",
62 | "quoteProperties": "asNeeded",
63 | "quoteStyle": "single",
64 | "semicolons": "always",
65 | "trailingComma": "es5"
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Benricheson101/discord-friend-graph/ba3ff4ab93ead288423cb08ff7fa18b2930fe801/demo.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Frens :3
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "discord-friend-graph",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "lint": "biome lint .",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "react": "^18.2.0",
14 | "react-dom": "^18.2.0",
15 | "react-force-graph-2d": "^1.25.4",
16 | "react-force-graph-3d": "^1.24.2"
17 | },
18 | "devDependencies": {
19 | "@biomejs/biome": "^1.7.0",
20 | "@types/react": "^18.2.66",
21 | "@types/react-dom": "^18.2.22",
22 | "@vitejs/plugin-react-swc": "^3.5.0",
23 | "typescript": "^5.2.2",
24 | "vite": "^5.2.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: '6.0'
2 |
3 | settings:
4 | autoInstallPeers: true
5 | excludeLinksFromLockfile: false
6 |
7 | dependencies:
8 | react:
9 | specifier: ^18.2.0
10 | version: 18.2.0
11 | react-dom:
12 | specifier: ^18.2.0
13 | version: 18.2.0(react@18.2.0)
14 | react-force-graph-2d:
15 | specifier: ^1.25.4
16 | version: 1.25.4(react@18.2.0)
17 | react-force-graph-3d:
18 | specifier: ^1.24.2
19 | version: 1.24.2(react@18.2.0)
20 |
21 | devDependencies:
22 | '@biomejs/biome':
23 | specifier: ^1.7.0
24 | version: 1.7.0
25 | '@types/react':
26 | specifier: ^18.2.66
27 | version: 18.2.79
28 | '@types/react-dom':
29 | specifier: ^18.2.22
30 | version: 18.2.25
31 | '@vitejs/plugin-react-swc':
32 | specifier: ^3.5.0
33 | version: 3.6.0(vite@5.2.8)
34 | typescript:
35 | specifier: ^5.2.2
36 | version: 5.4.5
37 | vite:
38 | specifier: ^5.2.0
39 | version: 5.2.8
40 |
41 | packages:
42 |
43 | /3d-force-graph@1.73.3:
44 | resolution: {integrity: sha512-azb65Lwn2yr/fJ4+qrxjmstVxogjzwJIZL/fdboCKBg6ph/FLW+xdvYFEBZW92XxBn1C8yRKS3d2VkVT3BzLSw==}
45 | engines: {node: '>=12'}
46 | dependencies:
47 | accessor-fn: 1.5.0
48 | kapsule: 1.14.5
49 | three: 0.163.0
50 | three-forcegraph: 1.41.13(three@0.163.0)
51 | three-render-objects: 1.29.3(three@0.163.0)
52 | dev: false
53 |
54 | /@babel/runtime@7.24.4:
55 | resolution: {integrity: sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==}
56 | engines: {node: '>=6.9.0'}
57 | dependencies:
58 | regenerator-runtime: 0.14.1
59 | dev: false
60 |
61 | /@biomejs/biome@1.7.0:
62 | resolution: {integrity: sha512-mejiRhnAq6UrXtYvjWJUKdstcT58n0/FfKemFf3d2Ou0HxOdS88HQmWtQ/UgyZvOEPD572YbFTb6IheyROpqkw==}
63 | engines: {node: '>=14.21.3'}
64 | hasBin: true
65 | requiresBuild: true
66 | optionalDependencies:
67 | '@biomejs/cli-darwin-arm64': 1.7.0
68 | '@biomejs/cli-darwin-x64': 1.7.0
69 | '@biomejs/cli-linux-arm64': 1.7.0
70 | '@biomejs/cli-linux-arm64-musl': 1.7.0
71 | '@biomejs/cli-linux-x64': 1.7.0
72 | '@biomejs/cli-linux-x64-musl': 1.7.0
73 | '@biomejs/cli-win32-arm64': 1.7.0
74 | '@biomejs/cli-win32-x64': 1.7.0
75 | dev: true
76 |
77 | /@biomejs/cli-darwin-arm64@1.7.0:
78 | resolution: {integrity: sha512-12TaeaKHU4SAZt0fQJ2bYk1jUb4foope7LmgDE5p3c0uMxd3mFkg1k7G721T+K6UHYULcSOQDsNNM8DhYi8Irg==}
79 | engines: {node: '>=14.21.3'}
80 | cpu: [arm64]
81 | os: [darwin]
82 | requiresBuild: true
83 | dev: true
84 | optional: true
85 |
86 | /@biomejs/cli-darwin-x64@1.7.0:
87 | resolution: {integrity: sha512-6Qq1BSIB0cpp0cQNqO/+EiUV7FE3jMpF6w7+AgIBXp0oJxUWb2Ff0RDZdO9bfzkimXD58j0vGpNHMGnCcjDV2Q==}
88 | engines: {node: '>=14.21.3'}
89 | cpu: [x64]
90 | os: [darwin]
91 | requiresBuild: true
92 | dev: true
93 | optional: true
94 |
95 | /@biomejs/cli-linux-arm64-musl@1.7.0:
96 | resolution: {integrity: sha512-pwIY80nU7SAxrVVZ6HD9ah1pruwh9ZqlSR0Nvbg4ZJqQa0POhiB+RJx7+/1Ml2mTZdrl8kb/YiwQpD16uwb5wg==}
97 | engines: {node: '>=14.21.3'}
98 | cpu: [arm64]
99 | os: [linux]
100 | requiresBuild: true
101 | dev: true
102 | optional: true
103 |
104 | /@biomejs/cli-linux-arm64@1.7.0:
105 | resolution: {integrity: sha512-GwSci7xBJ2j1CrdDXDUVXnUtrvypEz/xmiYPpFeVdlX5p95eXx+7FekPPbJfhGGw5WKSsKZ+V8AAlbN+kUwJWw==}
106 | engines: {node: '>=14.21.3'}
107 | cpu: [arm64]
108 | os: [linux]
109 | requiresBuild: true
110 | dev: true
111 | optional: true
112 |
113 | /@biomejs/cli-linux-x64-musl@1.7.0:
114 | resolution: {integrity: sha512-KzCA0mW4LSbCd7XZWaEJvTOTTBjfJoVEXkfq1fsXxww1HB+ww5PGMbhbIcbYCsj2CTJUifeD5hOkyuBVppU1xQ==}
115 | engines: {node: '>=14.21.3'}
116 | cpu: [x64]
117 | os: [linux]
118 | requiresBuild: true
119 | dev: true
120 | optional: true
121 |
122 | /@biomejs/cli-linux-x64@1.7.0:
123 | resolution: {integrity: sha512-1y+odKQsyHcw0JCGRuqhbx7Y6jxOVSh4lGIVDdJxW1b55yD22DY1kcMEfhUte6f95OIc2uqfkwtiI6xQAiZJdw==}
124 | engines: {node: '>=14.21.3'}
125 | cpu: [x64]
126 | os: [linux]
127 | requiresBuild: true
128 | dev: true
129 | optional: true
130 |
131 | /@biomejs/cli-win32-arm64@1.7.0:
132 | resolution: {integrity: sha512-AvLDUYZBpOUFgS/mni4VruIoVV3uSGbKSkZQBPXsHgL0w4KttLll3NBrVanmWxOHsom6C6ocHLyfAY8HUc8TXg==}
133 | engines: {node: '>=14.21.3'}
134 | cpu: [arm64]
135 | os: [win32]
136 | requiresBuild: true
137 | dev: true
138 | optional: true
139 |
140 | /@biomejs/cli-win32-x64@1.7.0:
141 | resolution: {integrity: sha512-Pylm00BAAuLVb40IH9PC17432BTsY8K4pSUvhvgR1eaalnMaD6ug9SYJTTzKDbT6r24MPAGCTiSZERyhGkGzFQ==}
142 | engines: {node: '>=14.21.3'}
143 | cpu: [x64]
144 | os: [win32]
145 | requiresBuild: true
146 | dev: true
147 | optional: true
148 |
149 | /@esbuild/aix-ppc64@0.20.2:
150 | resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==}
151 | engines: {node: '>=12'}
152 | cpu: [ppc64]
153 | os: [aix]
154 | requiresBuild: true
155 | dev: true
156 | optional: true
157 |
158 | /@esbuild/android-arm64@0.20.2:
159 | resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==}
160 | engines: {node: '>=12'}
161 | cpu: [arm64]
162 | os: [android]
163 | requiresBuild: true
164 | dev: true
165 | optional: true
166 |
167 | /@esbuild/android-arm@0.20.2:
168 | resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==}
169 | engines: {node: '>=12'}
170 | cpu: [arm]
171 | os: [android]
172 | requiresBuild: true
173 | dev: true
174 | optional: true
175 |
176 | /@esbuild/android-x64@0.20.2:
177 | resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==}
178 | engines: {node: '>=12'}
179 | cpu: [x64]
180 | os: [android]
181 | requiresBuild: true
182 | dev: true
183 | optional: true
184 |
185 | /@esbuild/darwin-arm64@0.20.2:
186 | resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==}
187 | engines: {node: '>=12'}
188 | cpu: [arm64]
189 | os: [darwin]
190 | requiresBuild: true
191 | dev: true
192 | optional: true
193 |
194 | /@esbuild/darwin-x64@0.20.2:
195 | resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==}
196 | engines: {node: '>=12'}
197 | cpu: [x64]
198 | os: [darwin]
199 | requiresBuild: true
200 | dev: true
201 | optional: true
202 |
203 | /@esbuild/freebsd-arm64@0.20.2:
204 | resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==}
205 | engines: {node: '>=12'}
206 | cpu: [arm64]
207 | os: [freebsd]
208 | requiresBuild: true
209 | dev: true
210 | optional: true
211 |
212 | /@esbuild/freebsd-x64@0.20.2:
213 | resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==}
214 | engines: {node: '>=12'}
215 | cpu: [x64]
216 | os: [freebsd]
217 | requiresBuild: true
218 | dev: true
219 | optional: true
220 |
221 | /@esbuild/linux-arm64@0.20.2:
222 | resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==}
223 | engines: {node: '>=12'}
224 | cpu: [arm64]
225 | os: [linux]
226 | requiresBuild: true
227 | dev: true
228 | optional: true
229 |
230 | /@esbuild/linux-arm@0.20.2:
231 | resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==}
232 | engines: {node: '>=12'}
233 | cpu: [arm]
234 | os: [linux]
235 | requiresBuild: true
236 | dev: true
237 | optional: true
238 |
239 | /@esbuild/linux-ia32@0.20.2:
240 | resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==}
241 | engines: {node: '>=12'}
242 | cpu: [ia32]
243 | os: [linux]
244 | requiresBuild: true
245 | dev: true
246 | optional: true
247 |
248 | /@esbuild/linux-loong64@0.20.2:
249 | resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==}
250 | engines: {node: '>=12'}
251 | cpu: [loong64]
252 | os: [linux]
253 | requiresBuild: true
254 | dev: true
255 | optional: true
256 |
257 | /@esbuild/linux-mips64el@0.20.2:
258 | resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==}
259 | engines: {node: '>=12'}
260 | cpu: [mips64el]
261 | os: [linux]
262 | requiresBuild: true
263 | dev: true
264 | optional: true
265 |
266 | /@esbuild/linux-ppc64@0.20.2:
267 | resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==}
268 | engines: {node: '>=12'}
269 | cpu: [ppc64]
270 | os: [linux]
271 | requiresBuild: true
272 | dev: true
273 | optional: true
274 |
275 | /@esbuild/linux-riscv64@0.20.2:
276 | resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==}
277 | engines: {node: '>=12'}
278 | cpu: [riscv64]
279 | os: [linux]
280 | requiresBuild: true
281 | dev: true
282 | optional: true
283 |
284 | /@esbuild/linux-s390x@0.20.2:
285 | resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==}
286 | engines: {node: '>=12'}
287 | cpu: [s390x]
288 | os: [linux]
289 | requiresBuild: true
290 | dev: true
291 | optional: true
292 |
293 | /@esbuild/linux-x64@0.20.2:
294 | resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==}
295 | engines: {node: '>=12'}
296 | cpu: [x64]
297 | os: [linux]
298 | requiresBuild: true
299 | dev: true
300 | optional: true
301 |
302 | /@esbuild/netbsd-x64@0.20.2:
303 | resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==}
304 | engines: {node: '>=12'}
305 | cpu: [x64]
306 | os: [netbsd]
307 | requiresBuild: true
308 | dev: true
309 | optional: true
310 |
311 | /@esbuild/openbsd-x64@0.20.2:
312 | resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==}
313 | engines: {node: '>=12'}
314 | cpu: [x64]
315 | os: [openbsd]
316 | requiresBuild: true
317 | dev: true
318 | optional: true
319 |
320 | /@esbuild/sunos-x64@0.20.2:
321 | resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==}
322 | engines: {node: '>=12'}
323 | cpu: [x64]
324 | os: [sunos]
325 | requiresBuild: true
326 | dev: true
327 | optional: true
328 |
329 | /@esbuild/win32-arm64@0.20.2:
330 | resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==}
331 | engines: {node: '>=12'}
332 | cpu: [arm64]
333 | os: [win32]
334 | requiresBuild: true
335 | dev: true
336 | optional: true
337 |
338 | /@esbuild/win32-ia32@0.20.2:
339 | resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==}
340 | engines: {node: '>=12'}
341 | cpu: [ia32]
342 | os: [win32]
343 | requiresBuild: true
344 | dev: true
345 | optional: true
346 |
347 | /@esbuild/win32-x64@0.20.2:
348 | resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==}
349 | engines: {node: '>=12'}
350 | cpu: [x64]
351 | os: [win32]
352 | requiresBuild: true
353 | dev: true
354 | optional: true
355 |
356 | /@rollup/rollup-android-arm-eabi@4.14.3:
357 | resolution: {integrity: sha512-X9alQ3XM6I9IlSlmC8ddAvMSyG1WuHk5oUnXGw+yUBs3BFoTizmG1La/Gr8fVJvDWAq+zlYTZ9DBgrlKRVY06g==}
358 | cpu: [arm]
359 | os: [android]
360 | requiresBuild: true
361 | dev: true
362 | optional: true
363 |
364 | /@rollup/rollup-android-arm64@4.14.3:
365 | resolution: {integrity: sha512-eQK5JIi+POhFpzk+LnjKIy4Ks+pwJ+NXmPxOCSvOKSNRPONzKuUvWE+P9JxGZVxrtzm6BAYMaL50FFuPe0oWMQ==}
366 | cpu: [arm64]
367 | os: [android]
368 | requiresBuild: true
369 | dev: true
370 | optional: true
371 |
372 | /@rollup/rollup-darwin-arm64@4.14.3:
373 | resolution: {integrity: sha512-Od4vE6f6CTT53yM1jgcLqNfItTsLt5zE46fdPaEmeFHvPs5SjZYlLpHrSiHEKR1+HdRfxuzXHjDOIxQyC3ptBA==}
374 | cpu: [arm64]
375 | os: [darwin]
376 | requiresBuild: true
377 | dev: true
378 | optional: true
379 |
380 | /@rollup/rollup-darwin-x64@4.14.3:
381 | resolution: {integrity: sha512-0IMAO21axJeNIrvS9lSe/PGthc8ZUS+zC53O0VhF5gMxfmcKAP4ESkKOCwEi6u2asUrt4mQv2rjY8QseIEb1aw==}
382 | cpu: [x64]
383 | os: [darwin]
384 | requiresBuild: true
385 | dev: true
386 | optional: true
387 |
388 | /@rollup/rollup-linux-arm-gnueabihf@4.14.3:
389 | resolution: {integrity: sha512-ge2DC7tHRHa3caVEoSbPRJpq7azhG+xYsd6u2MEnJ6XzPSzQsTKyXvh6iWjXRf7Rt9ykIUWHtl0Uz3T6yXPpKw==}
390 | cpu: [arm]
391 | os: [linux]
392 | requiresBuild: true
393 | dev: true
394 | optional: true
395 |
396 | /@rollup/rollup-linux-arm-musleabihf@4.14.3:
397 | resolution: {integrity: sha512-ljcuiDI4V3ySuc7eSk4lQ9wU8J8r8KrOUvB2U+TtK0TiW6OFDmJ+DdIjjwZHIw9CNxzbmXY39wwpzYuFDwNXuw==}
398 | cpu: [arm]
399 | os: [linux]
400 | requiresBuild: true
401 | dev: true
402 | optional: true
403 |
404 | /@rollup/rollup-linux-arm64-gnu@4.14.3:
405 | resolution: {integrity: sha512-Eci2us9VTHm1eSyn5/eEpaC7eP/mp5n46gTRB3Aar3BgSvDQGJZuicyq6TsH4HngNBgVqC5sDYxOzTExSU+NjA==}
406 | cpu: [arm64]
407 | os: [linux]
408 | requiresBuild: true
409 | dev: true
410 | optional: true
411 |
412 | /@rollup/rollup-linux-arm64-musl@4.14.3:
413 | resolution: {integrity: sha512-UrBoMLCq4E92/LCqlh+blpqMz5h1tJttPIniwUgOFJyjWI1qrtrDhhpHPuFxULlUmjFHfloWdixtDhSxJt5iKw==}
414 | cpu: [arm64]
415 | os: [linux]
416 | requiresBuild: true
417 | dev: true
418 | optional: true
419 |
420 | /@rollup/rollup-linux-powerpc64le-gnu@4.14.3:
421 | resolution: {integrity: sha512-5aRjvsS8q1nWN8AoRfrq5+9IflC3P1leMoy4r2WjXyFqf3qcqsxRCfxtZIV58tCxd+Yv7WELPcO9mY9aeQyAmw==}
422 | cpu: [ppc64]
423 | os: [linux]
424 | requiresBuild: true
425 | dev: true
426 | optional: true
427 |
428 | /@rollup/rollup-linux-riscv64-gnu@4.14.3:
429 | resolution: {integrity: sha512-sk/Qh1j2/RJSX7FhEpJn8n0ndxy/uf0kI/9Zc4b1ELhqULVdTfN6HL31CDaTChiBAOgLcsJ1sgVZjWv8XNEsAQ==}
430 | cpu: [riscv64]
431 | os: [linux]
432 | requiresBuild: true
433 | dev: true
434 | optional: true
435 |
436 | /@rollup/rollup-linux-s390x-gnu@4.14.3:
437 | resolution: {integrity: sha512-jOO/PEaDitOmY9TgkxF/TQIjXySQe5KVYB57H/8LRP/ux0ZoO8cSHCX17asMSv3ruwslXW/TLBcxyaUzGRHcqg==}
438 | cpu: [s390x]
439 | os: [linux]
440 | requiresBuild: true
441 | dev: true
442 | optional: true
443 |
444 | /@rollup/rollup-linux-x64-gnu@4.14.3:
445 | resolution: {integrity: sha512-8ybV4Xjy59xLMyWo3GCfEGqtKV5M5gCSrZlxkPGvEPCGDLNla7v48S662HSGwRd6/2cSneMQWiv+QzcttLrrOA==}
446 | cpu: [x64]
447 | os: [linux]
448 | requiresBuild: true
449 | dev: true
450 | optional: true
451 |
452 | /@rollup/rollup-linux-x64-musl@4.14.3:
453 | resolution: {integrity: sha512-s+xf1I46trOY10OqAtZ5Rm6lzHre/UiLA1J2uOhCFXWkbZrJRkYBPO6FhvGfHmdtQ3Bx793MNa7LvoWFAm93bg==}
454 | cpu: [x64]
455 | os: [linux]
456 | requiresBuild: true
457 | dev: true
458 | optional: true
459 |
460 | /@rollup/rollup-win32-arm64-msvc@4.14.3:
461 | resolution: {integrity: sha512-+4h2WrGOYsOumDQ5S2sYNyhVfrue+9tc9XcLWLh+Kw3UOxAvrfOrSMFon60KspcDdytkNDh7K2Vs6eMaYImAZg==}
462 | cpu: [arm64]
463 | os: [win32]
464 | requiresBuild: true
465 | dev: true
466 | optional: true
467 |
468 | /@rollup/rollup-win32-ia32-msvc@4.14.3:
469 | resolution: {integrity: sha512-T1l7y/bCeL/kUwh9OD4PQT4aM7Bq43vX05htPJJ46RTI4r5KNt6qJRzAfNfM+OYMNEVBWQzR2Gyk+FXLZfogGw==}
470 | cpu: [ia32]
471 | os: [win32]
472 | requiresBuild: true
473 | dev: true
474 | optional: true
475 |
476 | /@rollup/rollup-win32-x64-msvc@4.14.3:
477 | resolution: {integrity: sha512-/BypzV0H1y1HzgYpxqRaXGBRqfodgoBBCcsrujT6QRcakDQdfU+Lq9PENPh5jB4I44YWq+0C2eHsHya+nZY1sA==}
478 | cpu: [x64]
479 | os: [win32]
480 | requiresBuild: true
481 | dev: true
482 | optional: true
483 |
484 | /@swc/core-darwin-arm64@1.4.14:
485 | resolution: {integrity: sha512-8iPfLhYNspBl836YYsfv6ErXwDUqJ7IMieddV3Ey/t/97JAEAdNDUdtTKDtbyP0j/Ebyqyn+fKcqwSq7rAof0g==}
486 | engines: {node: '>=10'}
487 | cpu: [arm64]
488 | os: [darwin]
489 | requiresBuild: true
490 | dev: true
491 | optional: true
492 |
493 | /@swc/core-darwin-x64@1.4.14:
494 | resolution: {integrity: sha512-9CqSj8uRZ92cnlgAlVaWMaJJBdxtNvCzJxaGj5KuIseeG6Q0l1g+qk8JcU7h9dAsH9saHTNwNFBVGKQo0W0ujg==}
495 | engines: {node: '>=10'}
496 | cpu: [x64]
497 | os: [darwin]
498 | requiresBuild: true
499 | dev: true
500 | optional: true
501 |
502 | /@swc/core-linux-arm-gnueabihf@1.4.14:
503 | resolution: {integrity: sha512-mfd5JArPITTzMjcezH4DwMw+BdjBV1y25Khp8itEIpdih9ei+fvxOOrDYTN08b466NuE2dF2XuhKtRLA7fXArQ==}
504 | engines: {node: '>=10'}
505 | cpu: [arm]
506 | os: [linux]
507 | requiresBuild: true
508 | dev: true
509 | optional: true
510 |
511 | /@swc/core-linux-arm64-gnu@1.4.14:
512 | resolution: {integrity: sha512-3Lqlhlmy8MVRS9xTShMaPAp0oyUt0KFhDs4ixJsjdxKecE0NJSV/MInuDmrkij1C8/RQ2wySRlV9np5jK86oWw==}
513 | engines: {node: '>=10'}
514 | cpu: [arm64]
515 | os: [linux]
516 | requiresBuild: true
517 | dev: true
518 | optional: true
519 |
520 | /@swc/core-linux-arm64-musl@1.4.14:
521 | resolution: {integrity: sha512-n0YoCa64TUcJrbcXIHIHDWQjdUPdaXeMHNEu7yyBtOpm01oMGTKP3frsUXIABLBmAVWtKvqit4/W1KVKn5gJzg==}
522 | engines: {node: '>=10'}
523 | cpu: [arm64]
524 | os: [linux]
525 | requiresBuild: true
526 | dev: true
527 | optional: true
528 |
529 | /@swc/core-linux-x64-gnu@1.4.14:
530 | resolution: {integrity: sha512-CGmlwLWbfG1dB4jZBJnp2IWlK5xBMNLjN7AR5kKA3sEpionoccEnChOEvfux1UdVJQjLRKuHNV9yGyqGBTpxfQ==}
531 | engines: {node: '>=10'}
532 | cpu: [x64]
533 | os: [linux]
534 | requiresBuild: true
535 | dev: true
536 | optional: true
537 |
538 | /@swc/core-linux-x64-musl@1.4.14:
539 | resolution: {integrity: sha512-xq4npk8YKYmNwmr8fbvF2KP3kUVdZYfXZMQnW425gP3/sn+yFQO8Nd0bGH40vOVQn41kEesSe0Z5O/JDor2TgQ==}
540 | engines: {node: '>=10'}
541 | cpu: [x64]
542 | os: [linux]
543 | requiresBuild: true
544 | dev: true
545 | optional: true
546 |
547 | /@swc/core-win32-arm64-msvc@1.4.14:
548 | resolution: {integrity: sha512-imq0X+gU9uUe6FqzOQot5gpKoaC00aCUiN58NOzwp0QXEupn8CDuZpdBN93HiZswfLruu5jA1tsc15x6v9p0Yg==}
549 | engines: {node: '>=10'}
550 | cpu: [arm64]
551 | os: [win32]
552 | requiresBuild: true
553 | dev: true
554 | optional: true
555 |
556 | /@swc/core-win32-ia32-msvc@1.4.14:
557 | resolution: {integrity: sha512-cH6QpXMw5D3t+lpx6SkErHrxN0yFzmQ0lgNAJxoDRiaAdDbqA6Col8UqUJwUS++Ul6aCWgNhCdiEYehPaoyDPA==}
558 | engines: {node: '>=10'}
559 | cpu: [ia32]
560 | os: [win32]
561 | requiresBuild: true
562 | dev: true
563 | optional: true
564 |
565 | /@swc/core-win32-x64-msvc@1.4.14:
566 | resolution: {integrity: sha512-FmZ4Tby4wW65K/36BKzmuu7mlq7cW5XOxzvufaSNVvQ5PN4OodAlqPjToe029oma4Av+ykJiif64scMttyNAzg==}
567 | engines: {node: '>=10'}
568 | cpu: [x64]
569 | os: [win32]
570 | requiresBuild: true
571 | dev: true
572 | optional: true
573 |
574 | /@swc/core@1.4.14:
575 | resolution: {integrity: sha512-tHXg6OxboUsqa/L7DpsCcFnxhLkqN/ht5pCwav1HnvfthbiNIJypr86rNx4cUnQDJepETviSqBTIjxa7pSpGDQ==}
576 | engines: {node: '>=10'}
577 | requiresBuild: true
578 | peerDependencies:
579 | '@swc/helpers': ^0.5.0
580 | peerDependenciesMeta:
581 | '@swc/helpers':
582 | optional: true
583 | dependencies:
584 | '@swc/counter': 0.1.3
585 | '@swc/types': 0.1.6
586 | optionalDependencies:
587 | '@swc/core-darwin-arm64': 1.4.14
588 | '@swc/core-darwin-x64': 1.4.14
589 | '@swc/core-linux-arm-gnueabihf': 1.4.14
590 | '@swc/core-linux-arm64-gnu': 1.4.14
591 | '@swc/core-linux-arm64-musl': 1.4.14
592 | '@swc/core-linux-x64-gnu': 1.4.14
593 | '@swc/core-linux-x64-musl': 1.4.14
594 | '@swc/core-win32-arm64-msvc': 1.4.14
595 | '@swc/core-win32-ia32-msvc': 1.4.14
596 | '@swc/core-win32-x64-msvc': 1.4.14
597 | dev: true
598 |
599 | /@swc/counter@0.1.3:
600 | resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
601 | dev: true
602 |
603 | /@swc/types@0.1.6:
604 | resolution: {integrity: sha512-/JLo/l2JsT/LRd80C3HfbmVpxOAJ11FO2RCEslFrgzLltoP9j8XIbsyDcfCt2WWyX+CM96rBoNM+IToAkFOugg==}
605 | dependencies:
606 | '@swc/counter': 0.1.3
607 | dev: true
608 |
609 | /@tweenjs/tween.js@23.1.1:
610 | resolution: {integrity: sha512-ZpboH7pCPPeyBWKf8c7TJswtCEQObFo3bOBYalm99NzZarATALYCo5OhbCa/n4RQyJyHfhkdx+hNrdL5ByFYDw==}
611 | dev: false
612 |
613 | /@types/estree@1.0.5:
614 | resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
615 | dev: true
616 |
617 | /@types/prop-types@15.7.12:
618 | resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==}
619 | dev: true
620 |
621 | /@types/react-dom@18.2.25:
622 | resolution: {integrity: sha512-o/V48vf4MQh7juIKZU2QGDfli6p1+OOi5oXx36Hffpc9adsHeXjVp8rHuPkjd8VT8sOJ2Zp05HR7CdpGTIUFUA==}
623 | dependencies:
624 | '@types/react': 18.2.79
625 | dev: true
626 |
627 | /@types/react@18.2.79:
628 | resolution: {integrity: sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==}
629 | dependencies:
630 | '@types/prop-types': 15.7.12
631 | csstype: 3.1.3
632 | dev: true
633 |
634 | /@vitejs/plugin-react-swc@3.6.0(vite@5.2.8):
635 | resolution: {integrity: sha512-XFRbsGgpGxGzEV5i5+vRiro1bwcIaZDIdBRP16qwm+jP68ue/S8FJTBEgOeojtVDYrbSua3XFp71kC8VJE6v+g==}
636 | peerDependencies:
637 | vite: ^4 || ^5
638 | dependencies:
639 | '@swc/core': 1.4.14
640 | vite: 5.2.8
641 | transitivePeerDependencies:
642 | - '@swc/helpers'
643 | dev: true
644 |
645 | /accessor-fn@1.5.0:
646 | resolution: {integrity: sha512-dml7D96DY/K5lt4Ra2jMnpL9Bhw5HEGws4p1OAIxFFj9Utd/RxNfEO3T3f0QIWFNwQU7gNxH9snUfqF/zNkP/w==}
647 | engines: {node: '>=12'}
648 | dev: false
649 |
650 | /bezier-js@6.1.4:
651 | resolution: {integrity: sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==}
652 | dev: false
653 |
654 | /canvas-color-tracker@1.2.1:
655 | resolution: {integrity: sha512-i5clg2pEdaWqHuEM/B74NZNLkHh5+OkXbA/T4iaBiaNDagkOCXkLNrhqUfdUugsRwuaNRU20e/OygzxWRor3yg==}
656 | engines: {node: '>=12'}
657 | dependencies:
658 | tinycolor2: 1.6.0
659 | dev: false
660 |
661 | /csstype@3.1.3:
662 | resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
663 | dev: true
664 |
665 | /d3-array@3.2.4:
666 | resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
667 | engines: {node: '>=12'}
668 | dependencies:
669 | internmap: 2.0.3
670 | dev: false
671 |
672 | /d3-binarytree@1.0.2:
673 | resolution: {integrity: sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==}
674 | dev: false
675 |
676 | /d3-color@3.1.0:
677 | resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
678 | engines: {node: '>=12'}
679 | dev: false
680 |
681 | /d3-dispatch@3.0.1:
682 | resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
683 | engines: {node: '>=12'}
684 | dev: false
685 |
686 | /d3-drag@3.0.0:
687 | resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
688 | engines: {node: '>=12'}
689 | dependencies:
690 | d3-dispatch: 3.0.1
691 | d3-selection: 3.0.0
692 | dev: false
693 |
694 | /d3-ease@3.0.1:
695 | resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
696 | engines: {node: '>=12'}
697 | dev: false
698 |
699 | /d3-force-3d@3.0.5:
700 | resolution: {integrity: sha512-tdwhAhoTYZY/a6eo9nR7HP3xSW/C6XvJTbeRpR92nlPzH6OiE+4MliN9feuSFd0tPtEUo+191qOhCTWx3NYifg==}
701 | engines: {node: '>=12'}
702 | dependencies:
703 | d3-binarytree: 1.0.2
704 | d3-dispatch: 3.0.1
705 | d3-octree: 1.0.2
706 | d3-quadtree: 3.0.1
707 | d3-timer: 3.0.1
708 | dev: false
709 |
710 | /d3-format@3.1.0:
711 | resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
712 | engines: {node: '>=12'}
713 | dev: false
714 |
715 | /d3-interpolate@3.0.1:
716 | resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
717 | engines: {node: '>=12'}
718 | dependencies:
719 | d3-color: 3.1.0
720 | dev: false
721 |
722 | /d3-octree@1.0.2:
723 | resolution: {integrity: sha512-Qxg4oirJrNXauiuC94uKMbgxwnhdda9xRLl9ihq45srlJ4Ga3CSgqGcAL8iW7N5CIv4Oz8x3E734ulxyvHPvwA==}
724 | dev: false
725 |
726 | /d3-quadtree@3.0.1:
727 | resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
728 | engines: {node: '>=12'}
729 | dev: false
730 |
731 | /d3-scale-chromatic@3.1.0:
732 | resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==}
733 | engines: {node: '>=12'}
734 | dependencies:
735 | d3-color: 3.1.0
736 | d3-interpolate: 3.0.1
737 | dev: false
738 |
739 | /d3-scale@4.0.2:
740 | resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
741 | engines: {node: '>=12'}
742 | dependencies:
743 | d3-array: 3.2.4
744 | d3-format: 3.1.0
745 | d3-interpolate: 3.0.1
746 | d3-time: 3.1.0
747 | d3-time-format: 4.1.0
748 | dev: false
749 |
750 | /d3-selection@3.0.0:
751 | resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
752 | engines: {node: '>=12'}
753 | dev: false
754 |
755 | /d3-time-format@4.1.0:
756 | resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
757 | engines: {node: '>=12'}
758 | dependencies:
759 | d3-time: 3.1.0
760 | dev: false
761 |
762 | /d3-time@3.1.0:
763 | resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
764 | engines: {node: '>=12'}
765 | dependencies:
766 | d3-array: 3.2.4
767 | dev: false
768 |
769 | /d3-timer@3.0.1:
770 | resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
771 | engines: {node: '>=12'}
772 | dev: false
773 |
774 | /d3-transition@3.0.1(d3-selection@3.0.0):
775 | resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
776 | engines: {node: '>=12'}
777 | peerDependencies:
778 | d3-selection: 2 - 3
779 | dependencies:
780 | d3-color: 3.1.0
781 | d3-dispatch: 3.0.1
782 | d3-ease: 3.0.1
783 | d3-interpolate: 3.0.1
784 | d3-selection: 3.0.0
785 | d3-timer: 3.0.1
786 | dev: false
787 |
788 | /d3-zoom@3.0.0:
789 | resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
790 | engines: {node: '>=12'}
791 | dependencies:
792 | d3-dispatch: 3.0.1
793 | d3-drag: 3.0.0
794 | d3-interpolate: 3.0.1
795 | d3-selection: 3.0.0
796 | d3-transition: 3.0.1(d3-selection@3.0.0)
797 | dev: false
798 |
799 | /data-joint@1.3.1:
800 | resolution: {integrity: sha512-tMK0m4OVGqiA3zkn8JmO6YAqD8UwJqIAx4AAwFl1SKTtKAqcXePuT+n2aayiX9uITtlN3DFtKKTOxJRUc2+HvQ==}
801 | engines: {node: '>=12'}
802 | dependencies:
803 | index-array-by: 1.4.1
804 | dev: false
805 |
806 | /esbuild@0.20.2:
807 | resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==}
808 | engines: {node: '>=12'}
809 | hasBin: true
810 | requiresBuild: true
811 | optionalDependencies:
812 | '@esbuild/aix-ppc64': 0.20.2
813 | '@esbuild/android-arm': 0.20.2
814 | '@esbuild/android-arm64': 0.20.2
815 | '@esbuild/android-x64': 0.20.2
816 | '@esbuild/darwin-arm64': 0.20.2
817 | '@esbuild/darwin-x64': 0.20.2
818 | '@esbuild/freebsd-arm64': 0.20.2
819 | '@esbuild/freebsd-x64': 0.20.2
820 | '@esbuild/linux-arm': 0.20.2
821 | '@esbuild/linux-arm64': 0.20.2
822 | '@esbuild/linux-ia32': 0.20.2
823 | '@esbuild/linux-loong64': 0.20.2
824 | '@esbuild/linux-mips64el': 0.20.2
825 | '@esbuild/linux-ppc64': 0.20.2
826 | '@esbuild/linux-riscv64': 0.20.2
827 | '@esbuild/linux-s390x': 0.20.2
828 | '@esbuild/linux-x64': 0.20.2
829 | '@esbuild/netbsd-x64': 0.20.2
830 | '@esbuild/openbsd-x64': 0.20.2
831 | '@esbuild/sunos-x64': 0.20.2
832 | '@esbuild/win32-arm64': 0.20.2
833 | '@esbuild/win32-ia32': 0.20.2
834 | '@esbuild/win32-x64': 0.20.2
835 | dev: true
836 |
837 | /force-graph@1.43.5:
838 | resolution: {integrity: sha512-HveLELh9yhZXO/QOfaFS38vlwJZ/3sKu+jarfXzRmbmihSOH/BbRWnUvmg8wLFiYy6h4HlH4lkRfZRccHYmXgA==}
839 | engines: {node: '>=12'}
840 | dependencies:
841 | '@tweenjs/tween.js': 23.1.1
842 | accessor-fn: 1.5.0
843 | bezier-js: 6.1.4
844 | canvas-color-tracker: 1.2.1
845 | d3-array: 3.2.4
846 | d3-drag: 3.0.0
847 | d3-force-3d: 3.0.5
848 | d3-scale: 4.0.2
849 | d3-scale-chromatic: 3.1.0
850 | d3-selection: 3.0.0
851 | d3-zoom: 3.0.0
852 | index-array-by: 1.4.1
853 | kapsule: 1.14.5
854 | lodash-es: 4.17.21
855 | dev: false
856 |
857 | /fromentries@1.3.2:
858 | resolution: {integrity: sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==}
859 | dev: false
860 |
861 | /fsevents@2.3.3:
862 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
863 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
864 | os: [darwin]
865 | requiresBuild: true
866 | dev: true
867 | optional: true
868 |
869 | /index-array-by@1.4.1:
870 | resolution: {integrity: sha512-Zu6THdrxQdyTuT2uA5FjUoBEsFHPzHcPIj18FszN6yXKHxSfGcR4TPLabfuT//E25q1Igyx9xta2WMvD/x9P/g==}
871 | engines: {node: '>=12'}
872 | dev: false
873 |
874 | /internmap@2.0.3:
875 | resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
876 | engines: {node: '>=12'}
877 | dev: false
878 |
879 | /jerrypick@1.1.1:
880 | resolution: {integrity: sha512-XTtedPYEyVp4t6hJrXuRKr/jHj8SC4z+4K0b396PMkov6muL+i8IIamJIvZWe3jUspgIJak0P+BaWKawMYNBLg==}
881 | engines: {node: '>=12'}
882 | dev: false
883 |
884 | /js-tokens@4.0.0:
885 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
886 | dev: false
887 |
888 | /kapsule@1.14.5:
889 | resolution: {integrity: sha512-H0iSpTynUzZw3tgraDmReprpFRmH5oP5GPmaNsurSwLx2H5iCpOMIkp5q+sfhB4Tz/UJd1E1IbEE9Z6ksnJ6RA==}
890 | engines: {node: '>=12'}
891 | dependencies:
892 | lodash-es: 4.17.21
893 | dev: false
894 |
895 | /lodash-es@4.17.21:
896 | resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
897 | dev: false
898 |
899 | /loose-envify@1.4.0:
900 | resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
901 | hasBin: true
902 | dependencies:
903 | js-tokens: 4.0.0
904 | dev: false
905 |
906 | /nanoid@3.3.7:
907 | resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
908 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
909 | hasBin: true
910 | dev: true
911 |
912 | /ngraph.events@1.2.2:
913 | resolution: {integrity: sha512-JsUbEOzANskax+WSYiAPETemLWYXmixuPAlmZmhIbIj6FH/WDgEGCGnRwUQBK0GjOnVm8Ui+e5IJ+5VZ4e32eQ==}
914 | dev: false
915 |
916 | /ngraph.forcelayout@3.3.1:
917 | resolution: {integrity: sha512-MKBuEh1wujyQHFTW57y5vd/uuEOK0XfXYxm3lC7kktjJLRdt/KEKEknyOlc6tjXflqBKEuYBBcu7Ax5VY+S6aw==}
918 | dependencies:
919 | ngraph.events: 1.2.2
920 | ngraph.merge: 1.0.0
921 | ngraph.random: 1.1.0
922 | dev: false
923 |
924 | /ngraph.graph@20.0.1:
925 | resolution: {integrity: sha512-VFsQ+EMkT+7lcJO1QP8Ik3w64WbHJl27Q53EO9hiFU9CRyxJ8HfcXtfWz/U8okuoYKDctbciL6pX3vG5dt1rYA==}
926 | dependencies:
927 | ngraph.events: 1.2.2
928 | dev: false
929 |
930 | /ngraph.merge@1.0.0:
931 | resolution: {integrity: sha512-5J8YjGITUJeapsomtTALYsw7rFveYkM+lBj3QiYZ79EymQcuri65Nw3knQtFxQBU1r5iOaVRXrSwMENUPK62Vg==}
932 | dev: false
933 |
934 | /ngraph.random@1.1.0:
935 | resolution: {integrity: sha512-h25UdUN/g8U7y29TzQtRm/GvGr70lK37yQPvPKXXuVfs7gCm82WipYFZcksQfeKumtOemAzBIcT7lzzyK/edLw==}
936 | dev: false
937 |
938 | /object-assign@4.1.1:
939 | resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
940 | engines: {node: '>=0.10.0'}
941 | dev: false
942 |
943 | /picocolors@1.0.0:
944 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
945 | dev: true
946 |
947 | /polished@4.3.1:
948 | resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==}
949 | engines: {node: '>=10'}
950 | dependencies:
951 | '@babel/runtime': 7.24.4
952 | dev: false
953 |
954 | /postcss@8.4.38:
955 | resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==}
956 | engines: {node: ^10 || ^12 || >=14}
957 | dependencies:
958 | nanoid: 3.3.7
959 | picocolors: 1.0.0
960 | source-map-js: 1.2.0
961 | dev: true
962 |
963 | /prop-types@15.8.1:
964 | resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
965 | dependencies:
966 | loose-envify: 1.4.0
967 | object-assign: 4.1.1
968 | react-is: 16.13.1
969 | dev: false
970 |
971 | /react-dom@18.2.0(react@18.2.0):
972 | resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
973 | peerDependencies:
974 | react: ^18.2.0
975 | dependencies:
976 | loose-envify: 1.4.0
977 | react: 18.2.0
978 | scheduler: 0.23.0
979 | dev: false
980 |
981 | /react-force-graph-2d@1.25.4(react@18.2.0):
982 | resolution: {integrity: sha512-Y1xwa79PHVZUedfa/TO+Cboq2hIc1flA1z4o1oraOu6qMS0r421vNpfjWhJPR6qJonNme3tzeVt5boEA7Ue8sg==}
983 | engines: {node: '>=12'}
984 | peerDependencies:
985 | react: '*'
986 | dependencies:
987 | force-graph: 1.43.5
988 | prop-types: 15.8.1
989 | react: 18.2.0
990 | react-kapsule: 2.4.0(react@18.2.0)
991 | dev: false
992 |
993 | /react-force-graph-3d@1.24.2(react@18.2.0):
994 | resolution: {integrity: sha512-/tZ0BywYuj35Q84AH2WN+Cx0RIygnN5F1+EvsdAqsAMoIJ0xl4L/9aD/pwjCoWfFqi3w5wR2DQuitDXeTayZnQ==}
995 | engines: {node: '>=12'}
996 | peerDependencies:
997 | react: '*'
998 | dependencies:
999 | 3d-force-graph: 1.73.3
1000 | prop-types: 15.8.1
1001 | react: 18.2.0
1002 | react-kapsule: 2.4.0(react@18.2.0)
1003 | dev: false
1004 |
1005 | /react-is@16.13.1:
1006 | resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
1007 | dev: false
1008 |
1009 | /react-kapsule@2.4.0(react@18.2.0):
1010 | resolution: {integrity: sha512-w4Yv9CgWdj8kWGQEPNWFGJJ08dYEZHZpiaFR/DgZjCMBNqv9wus2Gy1qvHVJmJbzvAZbq6jdvFC+NYzEqAlNhQ==}
1011 | engines: {node: '>=12'}
1012 | peerDependencies:
1013 | react: '>=16.13.1'
1014 | dependencies:
1015 | fromentries: 1.3.2
1016 | jerrypick: 1.1.1
1017 | react: 18.2.0
1018 | dev: false
1019 |
1020 | /react@18.2.0:
1021 | resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
1022 | engines: {node: '>=0.10.0'}
1023 | dependencies:
1024 | loose-envify: 1.4.0
1025 | dev: false
1026 |
1027 | /regenerator-runtime@0.14.1:
1028 | resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
1029 | dev: false
1030 |
1031 | /rollup@4.14.3:
1032 | resolution: {integrity: sha512-ag5tTQKYsj1bhrFC9+OEWqb5O6VYgtQDO9hPDBMmIbePwhfSr+ExlcU741t8Dhw5DkPCQf6noz0jb36D6W9/hw==}
1033 | engines: {node: '>=18.0.0', npm: '>=8.0.0'}
1034 | hasBin: true
1035 | dependencies:
1036 | '@types/estree': 1.0.5
1037 | optionalDependencies:
1038 | '@rollup/rollup-android-arm-eabi': 4.14.3
1039 | '@rollup/rollup-android-arm64': 4.14.3
1040 | '@rollup/rollup-darwin-arm64': 4.14.3
1041 | '@rollup/rollup-darwin-x64': 4.14.3
1042 | '@rollup/rollup-linux-arm-gnueabihf': 4.14.3
1043 | '@rollup/rollup-linux-arm-musleabihf': 4.14.3
1044 | '@rollup/rollup-linux-arm64-gnu': 4.14.3
1045 | '@rollup/rollup-linux-arm64-musl': 4.14.3
1046 | '@rollup/rollup-linux-powerpc64le-gnu': 4.14.3
1047 | '@rollup/rollup-linux-riscv64-gnu': 4.14.3
1048 | '@rollup/rollup-linux-s390x-gnu': 4.14.3
1049 | '@rollup/rollup-linux-x64-gnu': 4.14.3
1050 | '@rollup/rollup-linux-x64-musl': 4.14.3
1051 | '@rollup/rollup-win32-arm64-msvc': 4.14.3
1052 | '@rollup/rollup-win32-ia32-msvc': 4.14.3
1053 | '@rollup/rollup-win32-x64-msvc': 4.14.3
1054 | fsevents: 2.3.3
1055 | dev: true
1056 |
1057 | /scheduler@0.23.0:
1058 | resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
1059 | dependencies:
1060 | loose-envify: 1.4.0
1061 | dev: false
1062 |
1063 | /source-map-js@1.2.0:
1064 | resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
1065 | engines: {node: '>=0.10.0'}
1066 | dev: true
1067 |
1068 | /three-forcegraph@1.41.13(three@0.163.0):
1069 | resolution: {integrity: sha512-tVBEnGSf0H5bL5dnebANFsjgLUDrwXXfYRXv3RfPgzuymDoo7sJRPdWIPyrkEgN0e09Hzvr4RLXkQ5FUlWpUzw==}
1070 | engines: {node: '>=12'}
1071 | peerDependencies:
1072 | three: '>=0.118.3'
1073 | dependencies:
1074 | accessor-fn: 1.5.0
1075 | d3-array: 3.2.4
1076 | d3-force-3d: 3.0.5
1077 | d3-scale: 4.0.2
1078 | d3-scale-chromatic: 3.1.0
1079 | data-joint: 1.3.1
1080 | kapsule: 1.14.5
1081 | ngraph.forcelayout: 3.3.1
1082 | ngraph.graph: 20.0.1
1083 | three: 0.163.0
1084 | tinycolor2: 1.6.0
1085 | dev: false
1086 |
1087 | /three-render-objects@1.29.3(three@0.163.0):
1088 | resolution: {integrity: sha512-CVE1w0ZvEyW3eMskmcdvZrTiCJZWeZv1BlZQ1X/FIRm0dGLfY/flzg+VH3vPYDXQQdumzj5nSqj5cSQuH4Y39g==}
1089 | engines: {node: '>=12'}
1090 | peerDependencies:
1091 | three: '*'
1092 | dependencies:
1093 | '@tweenjs/tween.js': 23.1.1
1094 | accessor-fn: 1.5.0
1095 | kapsule: 1.14.5
1096 | polished: 4.3.1
1097 | three: 0.163.0
1098 | dev: false
1099 |
1100 | /three@0.163.0:
1101 | resolution: {integrity: sha512-HlMgCb2TF/dTLRtknBnjUTsR8FsDqBY43itYop2+Zg822I+Kd0Ua2vs8CvfBVefXkBdNDrLMoRTGCIIpfCuDew==}
1102 | dev: false
1103 |
1104 | /tinycolor2@1.6.0:
1105 | resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
1106 | dev: false
1107 |
1108 | /typescript@5.4.5:
1109 | resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==}
1110 | engines: {node: '>=14.17'}
1111 | hasBin: true
1112 | dev: true
1113 |
1114 | /vite@5.2.8:
1115 | resolution: {integrity: sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==}
1116 | engines: {node: ^18.0.0 || >=20.0.0}
1117 | hasBin: true
1118 | peerDependencies:
1119 | '@types/node': ^18.0.0 || >=20.0.0
1120 | less: '*'
1121 | lightningcss: ^1.21.0
1122 | sass: '*'
1123 | stylus: '*'
1124 | sugarss: '*'
1125 | terser: ^5.4.0
1126 | peerDependenciesMeta:
1127 | '@types/node':
1128 | optional: true
1129 | less:
1130 | optional: true
1131 | lightningcss:
1132 | optional: true
1133 | sass:
1134 | optional: true
1135 | stylus:
1136 | optional: true
1137 | sugarss:
1138 | optional: true
1139 | terser:
1140 | optional: true
1141 | dependencies:
1142 | esbuild: 0.20.2
1143 | postcss: 8.4.38
1144 | rollup: 4.14.3
1145 | optionalDependencies:
1146 | fsevents: 2.3.3
1147 | dev: true
1148 |
--------------------------------------------------------------------------------
/public/happy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Benricheson101/discord-friend-graph/ba3ff4ab93ead288423cb08ff7fa18b2930fe801/public/happy.png
--------------------------------------------------------------------------------
/scripts/console-snippet.js:
--------------------------------------------------------------------------------
1 | const getMutualFriendsData = async () => {
2 | console.log(
3 | '%cOnly run this once, it may take a minute or two to finish depending on the size of your friends list',
4 | 'color:skyblue;font-size:15px'
5 | );
6 |
7 | let wpRequire;
8 | webpackChunkdiscord_app.push([[Date.now()], {}, e => (wpRequire = e)]);
9 | const mods = Object.values(wpRequire.c);
10 | const allExports = mods
11 | .flatMap(
12 | c =>
13 | (c.exports &&
14 | !c.exports[Symbol.toStringTag]?.includes('DOM') &&
15 | Object.values(c.exports)) ||
16 | []
17 | )
18 | .filter(Boolean);
19 |
20 | const _hasEveryProp = (a, props) =>
21 | a && typeof a === 'object' && props.every(p => p in a);
22 | const findByProps = (...props) =>
23 | allExports.find(e => _hasEveryProp(e, props)) ||
24 | mods.find(m => m?.exports && _hasEveryProp(m.exports, props))?.exports;
25 |
26 | const stores = mods
27 | .find(m => m?.exports?.ZP?.Store)
28 | .exports.ZP.Store.getAll()
29 | .reduce((a, c) => ((a[c.getName()] = c), a), {});
30 | const HTTP = findByProps('get', 'put', 'patch');
31 | const Endpoints = findByProps('USER_RELATIONSHIPS');
32 |
33 | const {have, need} = stores.RelationshipStore.getFriendIDs().reduce(
34 | (a, f) => {
35 | const mutualFriends = stores.UserProfileStore.getMutualFriends(f)?.map(
36 | m => m.user.id
37 | );
38 | const user = stores.UserStore.getUser(f);
39 | if (mutualFriends) {
40 | a.have.push({
41 | id: f,
42 | username: user.username,
43 | avatar: user.avatar,
44 | mutualFriends,
45 | });
46 | } else {
47 | a.need.push(f);
48 | }
49 | return a;
50 | },
51 | {have: [], need: []}
52 | );
53 |
54 | let i = 0;
55 | for (const r of need) {
56 | await new Promise(r => setTimeout(r, 450));
57 | const {body} = await HTTP.get({
58 | url: Endpoints.USER_RELATIONSHIPS(r),
59 | });
60 |
61 | const user = stores.UserStore.getUser(r);
62 | const o = {
63 | id: r,
64 | username: user.username,
65 | avatar: user.avatar,
66 | mutualFriends: body.map(b => b.id),
67 | };
68 | console.log(`[${i++}/${need.length}]`, r, o);
69 | have.push(o);
70 | }
71 |
72 | console.log('finished');
73 | const user = stores.UserStore.getCurrentUser();
74 | return {
75 | id: user.id,
76 | avatar: user.avatar,
77 | username: user.username,
78 | friends: have,
79 | };
80 | };
81 |
82 | const c = copy;
83 | const friends = await getMutualFriendsData();
84 | try {
85 | c(JSON.stringify(friends));
86 | } catch (e) {
87 | console.error(e);
88 | }
89 |
90 | console.log('%c' + JSON.stringify(friends), 'color:skyblue');
91 | console.log(
92 | "%cFriends list data should be copied to your clipboard. If not, manually copy the wall of text above (there's a copy button at the end)",
93 | 'color:red;font-size:20px'
94 | );
95 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | type ChangeEventHandler,
3 | type MouseEventHandler,
4 | useEffect,
5 | useRef,
6 | useState,
7 | } from 'react';
8 | import ForceGraph2D from 'react-force-graph-2d';
9 | import ForceGraph3D from 'react-force-graph-3d';
10 |
11 | interface User {
12 | id: string;
13 | username: string;
14 | avatar: string | null;
15 | mutualFriends: string[];
16 | }
17 |
18 | interface Friends {
19 | id: string;
20 | username: string;
21 | avatar: string | null;
22 | friends: User[];
23 | }
24 |
25 | interface Node {
26 | id: string;
27 | label: string;
28 | avatar: string | null;
29 | edges: Set;
30 | }
31 |
32 | interface Edge {
33 | source: string;
34 | target: string;
35 | id: string;
36 | color: string;
37 | }
38 |
39 | type Data = {nodes: Node[]; links: Edge[]};
40 |
41 | type DataWithUser = Data & {id: string};
42 |
43 | const hash = (str: string) => {
44 | let hash = 0;
45 | for (let i = 0, len = str.length; i < len; i++) {
46 | const chr = str.charCodeAt(i);
47 | hash = (hash << 5) - hash + chr;
48 | hash |= 0;
49 | }
50 | return Math.abs(hash);
51 | };
52 |
53 | // colors borrowed from https://raw.githubusercontent.com/tailwindlabs/tailwindcss/next/packages/tailwindcss/theme.css
54 | const colors = [
55 | '#f87171',
56 | '#ef4444',
57 | '#dc2626',
58 | '#b91c1c',
59 | '#fb923c',
60 | '#f97316',
61 | '#ea580c',
62 | '#c2410c',
63 | '#fbbf24',
64 | '#f59e0b',
65 | '#d97706',
66 | '#b45309',
67 | '#facc15',
68 | '#eab308',
69 | '#ca8a04',
70 | '#a16207',
71 | '#a3e635',
72 | '#84cc16',
73 | '#65a30d',
74 | '#4d7c0f',
75 | '#4ade80',
76 | '#22c55e',
77 | '#16a34a',
78 | '#15803d',
79 | '#34d399',
80 | '#10b981',
81 | '#059669',
82 | '#047857',
83 | '#2dd4bf',
84 | '#14b8a6',
85 | '#0d9488',
86 | '#0f766e',
87 | '#22d3ee',
88 | '#06b6d4',
89 | '#0891b2',
90 | '#0e7490',
91 | '#38bdf8',
92 | '#0ea5e9',
93 | '#0284c7',
94 | '#0369a1',
95 | '#60a5fa',
96 | '#3b82f6',
97 | '#2563eb',
98 | '#1d4ed8',
99 | '#818cf8',
100 | '#6366f1',
101 | '#4f46e5',
102 | '#4338ca',
103 | '#a78bfa',
104 | '#8b5cf6',
105 | '#7c3aed',
106 | '#6d28d9',
107 | '#c084fc',
108 | '#a855f7',
109 | '#9333ea',
110 | '#7e22ce',
111 | '#e879f9',
112 | '#d946ef',
113 | '#c026d3',
114 | '#a21caf',
115 | '#f472b6',
116 | '#ec4899',
117 | '#db2777',
118 | '#be185d',
119 | '#fb7185',
120 | '#f43f5e',
121 | '#e11d48',
122 | '#be123c',
123 | ];
124 |
125 | const randomColorFor = (s: string) => colors[hash(s) % colors.length];
126 |
127 | const makeData = (user: Friends, hideNonAdjacent: boolean): DataWithUser => {
128 | const nodes: Node[] = user.friends
129 | .map(f => ({
130 | id: f.id,
131 | label: f.username,
132 | avatar: f.avatar,
133 | edges: new Set(
134 | hideNonAdjacent ? [user.id] : f.mutualFriends.concat(user.id)
135 | ),
136 | }))
137 | .concat({
138 | id: user.id,
139 | avatar: user.avatar,
140 | label: user.username,
141 | edges: new Set(user.friends.map(f => f.id)),
142 | });
143 |
144 | const edges: Edge[] = user.friends.flatMap(
145 | f =>
146 | f.mutualFriends
147 | .map(mf =>
148 | hideNonAdjacent
149 | ? null
150 | : {
151 | id: `${f.id}-${mf}`,
152 | source: f.id,
153 | target: mf,
154 | color: '#9ca3af70',
155 | }
156 | )
157 | .concat({
158 | id: `${user.id}-${f.id}`,
159 | source: user.id,
160 | target: f.id,
161 | color: randomColorFor(user.id),
162 | })
163 | .filter(Boolean) as Edge[]
164 | );
165 |
166 | return {id: user.id, nodes, links: edges};
167 | };
168 |
169 | function App() {
170 | const [data, setData] = useState({nodes: [], links: []});
171 | const [inputData, setInputData] = useState([]);
172 |
173 | const [show3D, setShow3D] = useState(false);
174 | const [hideNonAdjacent, setHideNonAdjacent] = useState(false);
175 |
176 | useEffect(() => {
177 | const data: DataWithUser[] = inputData.map(u =>
178 | makeData(u, hideNonAdjacent)
179 | );
180 |
181 | const seenNodes = new Map();
182 | const seenEdges = new Map();
183 |
184 | for (const person of data) {
185 | for (const node of person.nodes) {
186 | const existingNode = seenNodes.get(node.id);
187 | if (existingNode) {
188 | node.edges = new Set([...node.edges, ...existingNode.edges]);
189 | }
190 |
191 | seenNodes.set(node.id, node);
192 | }
193 |
194 | for (const edge of person.links) {
195 | if (edge.source === person.id) {
196 | seenEdges.delete(`${edge.source}-${edge.target}`) ||
197 | seenEdges.delete(`${edge.target}-${edge.source}`);
198 | seenEdges.set(`${edge.source}-${edge.target}`, edge);
199 | }
200 |
201 | if (
202 | !(
203 | seenEdges.has(`${edge.source}-${edge.target}`) ||
204 | seenEdges.has(`${edge.target}-${edge.source}`)
205 | )
206 | ) {
207 | seenEdges.set(`${edge.source}-${edge.target}`, edge);
208 | }
209 | }
210 | }
211 |
212 | const merged: {nodes: Node[]; links: Edge[]} = {
213 | nodes: [...seenNodes.values()],
214 | links: [...seenEdges.values()],
215 | };
216 |
217 | setData(merged);
218 | }, [inputData, hideNonAdjacent]);
219 |
220 | const fgRef = useRef();
221 |
222 | // biome-ignore lint: i don't even know why it's complaining
223 | useEffect(() => {
224 | if (fgRef?.current) {
225 | // @ts-expect-error i can't figure out how to type this
226 | fgRef.current.d3Force?.('charge')?.strength(-2000).distanceMax(1000);
227 | }
228 | }, [fgRef.current]);
229 |
230 | const [showLabels, setShowLabels] = useState(false);
231 |
232 | const onSubmitFiles: ChangeEventHandler = async e => {
233 | const files = [];
234 | for (let i = 0; i < (e.target.files?.length || 0); i++) {
235 | const file = e.target.files?.item(i)!;
236 | files.push(file);
237 | }
238 |
239 | const usrs: Friends[] = await Promise.all(
240 | files.map(f => f.text().then(t => JSON.parse(t)))
241 | );
242 |
243 | setInputData(usrs);
244 | };
245 |
246 | const parseClipboard: MouseEventHandler = async () => {
247 | const clipboardData = await navigator.clipboard.readText();
248 | const users: Friends[] = clipboardData.split('\n').map(d => JSON.parse(d));
249 | setInputData(users);
250 | };
251 |
252 | const ForceGraph = show3D ? ForceGraph3D : ForceGraph2D;
253 |
254 | return (
255 | <>
256 | {inputData.length === 0 ? (
257 |
265 |
Add one or more JSON files to get started.
266 |
267 | Paste{' '}
268 |
269 | this script
270 | {' '}
271 | into your Discord dev console to get the data
272 |
273 |
280 |
283 |
284 | ) : (
285 | <>
286 |
292 |
299 |
300 |
307 |
308 |
317 |
318 |
325 |
326 |
Nodes: {data.nodes.length}
327 |
Edges: {data.links.length}
328 |
329 | {inputData.map(u => (
330 |
331 | {u.username}
332 |
333 | ))}
334 |
335 | edge.color}
339 | linkWidth={show3D ? 2.5 : 0.5}
340 | nodeLabel={(n: Node) => `${n.label} (${n.edges.size})`}
341 | nodeVal={(n: Node) => n.edges.size}
342 | nodeCanvasObject={
343 | showLabels
344 | ? (node, ctx, globalScale) => {
345 | const label = `${node.label} (${node.edges.size})`;
346 | const fontSize = 12 / globalScale;
347 | ctx.font = `${fontSize}px Sans-Serif`;
348 | ctx.textAlign = 'center';
349 | ctx.textBaseline = 'middle';
350 | ctx.fillStyle = window.matchMedia(
351 | '(prefers-color-scheme: light)'
352 | ).matches
353 | ? '#213547'
354 | : 'lightgray';
355 | // @ts-expect-error i have no idea how to type this
356 | ctx.fillText(label, node.x, node.y + 6);
357 | }
358 | : undefined
359 | }
360 | nodeCanvasObjectMode={() => 'after'}
361 | />
362 | >
363 | )}
364 | >
365 | );
366 | }
367 |
368 | export default App;
369 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | width: 100%;
3 | margin: 0;
4 | }
5 |
6 | input[type=file] {
7 | color: transparent;
8 | }
9 |
10 | :root {
11 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
12 | color-scheme: light dark;
13 | color: rgba(255, 255, 255, 0.87);
14 | background-color: #1A1F29;
15 |
16 | font-synthesis: none;
17 | text-rendering: optimizeLegibility;
18 | -webkit-font-smoothing: antialiased;
19 | -moz-osx-font-smoothing: grayscale;
20 | }
21 |
22 | @media (prefers-color-scheme: light) {
23 | :root {
24 | color: #213547;
25 | background-color: #ffffff;
26 | }
27 | a:hover {
28 | color: #747bff;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import App from './App.tsx';
4 | import './index.css';
5 |
6 | ReactDOM.createRoot(document.getElementById('root')!).render(
7 |
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": ["src"],
24 | "references": [{ "path": "./tsconfig.node.json" }]
25 | }
26 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true,
8 | "strict": true
9 | },
10 | "include": ["vite.config.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from 'vite';
2 | import react from '@vitejs/plugin-react-swc';
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | base: './',
8 | });
9 |
--------------------------------------------------------------------------------