From 55ff99384cd87f12c8c3b80a1847472b99846907 Mon Sep 17 00:00:00 2001 From: CoreByte <62032154+CoreBytee@users.noreply.github.com> Date: Sun, 30 Aug 2026 23:15:46 +0200 Subject: [PATCH 01/11] cleaning --- .vscode/extensions.json | 3 - .vscode/settings.json | 14 - .zed/settings.json | 46 --- biome.json | 37 --- bun.lock | 362 ----------------------- bunfig.toml | 2 - knip.json | 7 - package.json | 36 --- patches/instagram-url-direct@2.0.7.patch | 140 --------- scripts/frpc.toml | 10 - src/classes/DiscordBot.ts | 51 ---- src/classes/EMBDR.ts | 17 -- src/classes/WebServer.ts | 75 ----- src/classes/sources/BaseSource.ts | 45 --- src/classes/sources/InstagramSource.ts | 116 -------- src/index.ts | 3 - src/pages/embed.tsx | 114 ------- tsconfig.json | 32 -- 18 files changed, 1110 deletions(-) delete mode 100644 .vscode/extensions.json delete mode 100644 .vscode/settings.json delete mode 100644 .zed/settings.json delete mode 100644 biome.json delete mode 100644 bun.lock delete mode 100644 bunfig.toml delete mode 100644 knip.json delete mode 100644 package.json delete mode 100644 patches/instagram-url-direct@2.0.7.patch delete mode 100644 scripts/frpc.toml delete mode 100644 src/classes/DiscordBot.ts delete mode 100644 src/classes/EMBDR.ts delete mode 100644 src/classes/WebServer.ts delete mode 100644 src/classes/sources/BaseSource.ts delete mode 100644 src/classes/sources/InstagramSource.ts delete mode 100644 src/index.ts delete mode 100644 src/pages/embed.tsx delete mode 100644 tsconfig.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 16e8e66..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "recommendations": ["biomejs.biome"] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 1f00a59..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "editor.tabSize": 4, - "editor.insertSpaces": false, - "editor.defaultFormatter": "biomejs.biome", - "editor.formatOnPaste": true, - "files.eol": "\n", - "editor.formatOnSave": true, - "[typescript]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[html]": { - "editor.defaultFormatter": "vscode.html-language-features" - } -} diff --git a/.zed/settings.json b/.zed/settings.json deleted file mode 100644 index 9766a76..0000000 --- a/.zed/settings.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "languages": { - "JavaScript": { - "formatter": { "language_server": { "name": "biome" } }, - "code_actions_on_format": { - "source.fixAll.biome": true, - "source.organizeImports.biome": true - } - }, - "TypeScript": { - "formatter": { "language_server": { "name": "biome" } }, - "code_actions_on_format": { - "source.fixAll.biome": true, - "source.organizeImports.biome": true - } - }, - "TSX": { - "formatter": { "language_server": { "name": "biome" } }, - "code_actions_on_format": { - "source.fixAll.biome": true, - "source.organizeImports.biome": true - } - }, - "JSON": { - "formatter": { "language_server": { "name": "biome" } }, - "code_actions_on_format": { - "source.fixAll.biome": true, - "source.organizeImports.biome": true - } - }, - "JSONC": { - "formatter": { "language_server": { "name": "biome" } }, - "code_actions_on_format": { - "source.fixAll.biome": true, - "source.organizeImports.biome": true - } - }, - "CSS": { - "formatter": { "language_server": { "name": "biome" } }, - "code_actions_on_format": { - "source.fixAll.biome": true, - "source.organizeImports.biome": true - } - } - } -} diff --git a/biome.json b/biome.json deleted file mode 100644 index ee5b54b..0000000 --- a/biome.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$schema": "https://biomejs.dev/schemas/2.3.6/schema.json", - "vcs": { - "enabled": true, - "clientKind": "git", - "useIgnoreFile": true - }, - "files": { - "ignoreUnknown": false - }, - "formatter": { - "enabled": true, - "indentStyle": "tab" - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "style": { - "noNonNullAssertion": "off" - } - } - }, - "javascript": { - "formatter": { - "quoteStyle": "double" - } - }, - "assist": { - "enabled": true, - "actions": { - "source": { - "organizeImports": "on" - } - } - } -} diff --git a/bun.lock b/bun.lock deleted file mode 100644 index c28570c..0000000 --- a/bun.lock +++ /dev/null @@ -1,362 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 1, - "workspaces": { - "": { - "name": "embdr", - "dependencies": { - "@kitajs/html": "^4.2.11", - "@kitajs/ts-html-plugin": "^4.1.3", - "discord.js": "^14.25.0", - "fs-extra": "^11.3.2", - "get-urls": "^12.1.0", - "instagram-url-direct": "^2.0.7", - "just-random": "^3.2.0", - }, - "devDependencies": { - "@biomejs/biome": "2.3.6", - "@corebyte/frp": "^1.0.6", - "@types/bun": "1.3.2", - "@types/fs-extra": "^11.0.4", - "knip": "^5.70.1", - "typescript": "^5.9.3", - }, - }, - }, - "trustedDependencies": [ - "@biomejs/biome", - "@corebyte/frp", - ], - "patchedDependencies": { - "instagram-url-direct@2.0.7": "patches/instagram-url-direct@2.0.7.patch", - }, - "packages": { - "@biomejs/biome": ["@biomejs/biome@2.3.6", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.6", "@biomejs/cli-darwin-x64": "2.3.6", "@biomejs/cli-linux-arm64": "2.3.6", "@biomejs/cli-linux-arm64-musl": "2.3.6", "@biomejs/cli-linux-x64": "2.3.6", "@biomejs/cli-linux-x64-musl": "2.3.6", "@biomejs/cli-win32-arm64": "2.3.6", "@biomejs/cli-win32-x64": "2.3.6" }, "bin": { "biome": "bin/biome" } }, "sha512-oqUhWyU6tae0MFsr/7iLe++QWRg+6jtUhlx9/0GmCWDYFFrK366sBLamNM7D9Y+c7YSynUFKr8lpEp1r6Sk7eA=="], - - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-P4JWE5d8UayBxYe197QJwyW4ZHp0B+zvRIGCusOm1WbxmlhpAQA1zEqQuunHgSIzvyEEp4TVxiKGXNFZPg7r9Q=="], - - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-I4rTebj+F/L9K93IU7yTFs8nQ6EhaCOivxduRha4w4WEZK80yoZ8OAdR1F33m4yJ/NfUuTUbP/Wjs+vKjlCoWA=="], - - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-JjYy83eVBnvuINZiqyFO7xx72v8Srh4hsgaacSBCjC22DwM6+ZvnX1/fj8/SBiLuUOfZ8YhU2pfq2Dzakeyg1A=="], - - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-oK1NpIXIixbJ/4Tcx40cwiieqah6rRUtMGOHDeK2ToT7yUFVEvXUGRKqH0O4hqZ9tW8TcXNZKfgRH6xrsjVtGg=="], - - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.6", "", { "os": "linux", "cpu": "x64" }, "sha512-ZjPXzy5yN9wusIoX+8Zp4p6cL8r0NzJCXg/4r1KLVveIPXd2jKVlqZ6ZyzEq385WwU3OX5KOwQYLQsOc788waQ=="], - - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.6", "", { "os": "linux", "cpu": "x64" }, "sha512-QvxB8GHQeaO4FCtwJpJjCgJkbHBbWxRHUxQlod+xeaYE6gtJdSkYkuxdKAQUZEOIsec+PeaDAhW9xjzYbwmOFA=="], - - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-YM7hLHpwjdt8R7+O2zS1Vo2cKgqEeptiXB1tWW1rgjN5LlpZovBVKtg7zfwfRrFx3i08aNZThYpTcowpTlczug=="], - - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.6", "", { "os": "win32", "cpu": "x64" }, "sha512-psgNEYgMAobY5h+QHRBVR9xvg2KocFuBKm6axZWB/aD12NWhQjiVFQUjV6wMXhlH4iT0Q9c3yK5JFRiDC/rzHA=="], - - "@corebyte/frp": ["@corebyte/frp@1.0.6", "", { "dependencies": { "p-event": "^6.0.1", "terminal-size": "^4.0.0" }, "peerDependencies": { "typescript": "^5" }, "bin": { "frpc": ".dist/cli/frpc.js", "frps": ".dist/cli/frps.js", "frp": ".dist/cli/frp.js" } }, "sha512-W+7hf5ld7n2l/u0PpDjXkGq6e6O6Wrhk+jBaveSHGAAMg35cgXbuDCuGND2BzJosEONeXoNcXIRi7/J1ZkQbPA=="], - - "@discordjs/builders": ["@discordjs/builders@1.13.0", "", { "dependencies": { "@discordjs/formatters": "^0.6.1", "@discordjs/util": "^1.1.1", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "^0.38.31", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-COK0uU6ZaJI+LA67H/rp8IbEkYwlZf3mAoBI5wtPh5G5cbEQGNhVpzINg2f/6+q/YipnNIKy6fJDg6kMUKUw4Q=="], - - "@discordjs/collection": ["@discordjs/collection@1.5.3", "", {}, "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="], - - "@discordjs/formatters": ["@discordjs/formatters@0.6.2", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ=="], - - "@discordjs/rest": ["@discordjs/rest@2.6.0", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.38.16", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w=="], - - "@discordjs/util": ["@discordjs/util@1.2.0", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg=="], - - "@discordjs/ws": ["@discordjs/ws@1.2.3", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "^0.38.1", "tslib": "^2.6.2", "ws": "^8.17.0" } }, "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw=="], - - "@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], - - "@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], - - "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], - - "@kitajs/html": ["@kitajs/html@4.2.11", "", { "dependencies": { "csstype": "^3.1.3" } }, "sha512-gOe+zzCZKN2fPT1FUK32mHsr21ILcAOUUux/yDqQthInW8egN8RuxVp+zP3KhwWETVACkurBiKV9RWuNw+ceiw=="], - - "@kitajs/ts-html-plugin": ["@kitajs/ts-html-plugin@4.1.3", "", { "dependencies": { "chalk": "^5.6.2", "tslib": "^2.8.1", "yargs": "^18.0.0" }, "peerDependencies": { "@kitajs/html": "^4.2.10", "typescript": "^5.6.2" }, "bin": { "ts-html-plugin": "dist/cli.js", "xss-scan": "dist/cli.js" } }, "sha512-NlYrID5yMxfRKiO1eiiSC4MWveKe0ffoCJOZm4idNOqwimmLXr0g1NmvCcquOU2XLRrgzynxZqw6rhwR5CY5Nw=="], - - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" } }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="], - - "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], - - "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], - - "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - - "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.14.0", "", { "os": "android", "cpu": "arm" }, "sha512-jB47iZ/thvhE+USCLv+XY3IknBbkKr/p7OBsQDTHode/GPw+OHRlit3NQ1bjt1Mj8V2CS7iHdSDYobZ1/0gagQ=="], - - "@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.14.0", "", { "os": "android", "cpu": "arm64" }, "sha512-XFJ9t7d/Cz+dWLyqtTy3Xrekz+qqN4hmOU2iOUgr7u71OQsPUHIIeS9/wKanEK0l413gPwapIkyc5x9ltlOtyw=="], - - "@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.14.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gwehBS9smA1mzK8frDsmUCHz+6baJVwkKF6qViHhoqA3kRKvIZ3k6WNP4JmF19JhOiGxRcoPa8gZRfzNgXwP2A=="], - - "@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.14.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-5wwJvfuoahKiAqqAsMLOI28rqdh3P2K7HkjIWUXNMWAZq6ErX0L5rwJzu6T32+Zxw3k18C7R9IS4wDq/3Ar+6w=="], - - "@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.14.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-MWTt+LOQNcQ6fa+Uu5VikkihLi1PSIrQqqp0QD44k2AORasNWl0jRGBTcMSBIgNe82qEQWYvlGzvOEEOBp01Og=="], - - "@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.14.0", "", { "os": "linux", "cpu": "arm" }, "sha512-b6/IBqYrS3o0XiLVBsnex/wK8pTTK+hbGfAMOHVU6p7DBpwPPLgC/tav4IXoOIUCssTFz7aWh/xtUok0swn8VQ=="], - - "@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.14.0", "", { "os": "linux", "cpu": "arm" }, "sha512-o2Qh5+y5YoqVK6YfzkalHdpmQ5bkbGGxuLg1pZLQ1Ift0x+Vix7DaFEpdCl5Z9xvYXogd/TwOlL0TPl4+MTFLA=="], - - "@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.14.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-lk8mCSg0Tg4sEG73RiPjb7keGcEPwqQnBHX3Z+BR2SWe+qNHpoHcyFMNafzSvEC18vlxC04AUSoa6kJl/C5zig=="], - - "@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.14.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-KykeIVhCM7pn93ABa0fNe8vk4XvnbfZMELne2s6P9tdJH9KMBsCFBi7a2BmSdUtTqWCAJokAcm46lpczU52Xaw=="], - - "@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.14.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-QqPPWAcZU/jHAuam4f3zV8OdEkYRPD2XR0peVet3hoMMgsihR3Lhe7J/bLclmod297FG0+OgBYQVMh2nTN6oWA=="], - - "@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.14.0", "", { "os": "linux", "cpu": "none" }, "sha512-DunWA+wafeG3hj1NADUD3c+DRvmyVNqF5LSHVUWA2bzswqmuEZXl3VYBSzxfD0j+UnRTFYLxf27AMptoMsepYg=="], - - "@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.14.0", "", { "os": "linux", "cpu": "none" }, "sha512-4SRvwKTTk2k67EQr9Ny4NGf/BhlwggCI1CXwBbA9IV4oP38DH8b+NAPxDY0ySGRsWbPkG92FYOqM4AWzG4GSgA=="], - - "@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.14.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-hZKvkbsurj4JOom//R1Ab2MlC4cGeVm5zzMt4IsS3XySQeYjyMJ5TDZ3J5rQ8bVj3xi4FpJU2yFZ72GApsHQ6A=="], - - "@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.14.0", "", { "os": "linux", "cpu": "x64" }, "sha512-hABxQXFXJurivw+0amFdeEcK67cF1BGBIN1+sSHzq3TRv4RoG8n5q2JE04Le2n2Kpt6xg4Y5+lcv+rb2mCJLgQ=="], - - "@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.14.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Ln73wUB5migZRvC7obAAdqVwvFvk7AUs2JLt4g9QHr8FnqivlsjpUC9Nf2ssrybdjyQzEMjttUxPZz6aKPSAHw=="], - - "@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.14.0", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.0.7" }, "cpu": "none" }, "sha512-z+NbELmCOKNtWOqEB5qDfHXOSWB3kGQIIehq6nHtZwHLzdVO2oBq6De/ayhY3ygriC1XhgaIzzniY7jgrNl4Kw=="], - - "@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.14.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Ft0+qd7HSO61qCTLJ4LCdBGZkpKyDj1rG0OVSZL1DxWQoh97m7vEHd7zAvUtw8EcWjOMBQuX4mfRap/x2MOCpQ=="], - - "@oxc-resolver/binding-win32-ia32-msvc": ["@oxc-resolver/binding-win32-ia32-msvc@11.14.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-o54jYNSfGdPxHSvXEhZg8FOV3K99mJ1f7hb1alRFb+Yec1GQXNrJXxZPIxNMYeFT13kwAWB7zuQ0HZLnDHFxfw=="], - - "@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.14.0", "", { "os": "win32", "cpu": "x64" }, "sha512-j97icaORyM6A7GjgmUzfn7V+KGzVvctRA+eAlJb0c2OQNaETFxl6BXZdnGBDb+6oA0Y4Sr/wnekd1kQ0aVyKGg=="], - - "@sapphire/async-queue": ["@sapphire/async-queue@1.5.5", "", {}, "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="], - - "@sapphire/shapeshift": ["@sapphire/shapeshift@4.0.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" } }, "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg=="], - - "@sapphire/snowflake": ["@sapphire/snowflake@3.5.3", "", {}, "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="], - - "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], - - "@types/bun": ["@types/bun@1.3.2", "", { "dependencies": { "bun-types": "1.3.2" } }, "sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg=="], - - "@types/fs-extra": ["@types/fs-extra@11.0.4", "", { "dependencies": { "@types/jsonfile": "*", "@types/node": "*" } }, "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ=="], - - "@types/jsonfile": ["@types/jsonfile@6.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ=="], - - "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], - - "@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="], - - "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - - "@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.7", "", {}, "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g=="], - - "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - - "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], - - "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], - - "axios": ["axios@1.13.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA=="], - - "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - - "bun-types": ["bun-types@1.3.2", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg=="], - - "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], - - "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], - - "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - - "cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="], - - "clone-regexp": ["clone-regexp@3.0.0", "", { "dependencies": { "is-regexp": "^3.0.0" } }, "sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw=="], - - "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], - - "convert-hrtime": ["convert-hrtime@5.0.0", "", {}, "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg=="], - - "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - - "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], - - "discord-api-types": ["discord-api-types@0.38.34", "", {}, "sha512-muq7xKGznj5MSFCzuIm/2TO7DpttuomUTemVM82fRqgnMl70YRzEyY24jlbiV6R9tzOTq6A6UnZ0bsfZeKD38Q=="], - - "discord.js": ["discord.js@14.25.1", "", { "dependencies": { "@discordjs/builders": "^1.13.0", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.2", "@discordjs/rest": "^2.6.0", "@discordjs/util": "^1.2.0", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.33", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g=="], - - "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - - "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], - - "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], - - "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - - "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], - - "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], - - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - - "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], - - "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], - - "fd-package-json": ["fd-package-json@2.0.0", "", { "dependencies": { "walk-up-path": "^4.0.0" } }, "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ=="], - - "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], - - "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], - - "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], - - "formatly": ["formatly@0.3.0", "", { "dependencies": { "fd-package-json": "^2.0.0" }, "bin": { "formatly": "bin/index.mjs" } }, "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w=="], - - "fs-extra": ["fs-extra@11.3.2", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A=="], - - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - - "function-timeout": ["function-timeout@0.1.1", "", {}, "sha512-0NVVC0TaP7dSTvn1yMiy6d6Q8gifzbvQafO46RtLG/kHJUBNd+pVRGOBoK44wNBvtSPUJRfdVvkFdD3p0xvyZg=="], - - "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], - - "get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="], - - "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], - - "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - - "get-urls": ["get-urls@12.1.0", "", { "dependencies": { "normalize-url": "^8.0.0", "super-regex": "^0.2.0", "url-regex-safe": "^4.0.0" } }, "sha512-qHO+QmPiI1bEw0Y/m+WMAAx/UoEEXLZwEx0DVaKMtlHNrKbMeV960LryIpd+E2Ykb9XkVHmVtpbCsmul3GhR0g=="], - - "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], - - "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - - "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], - - "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - - "instagram-url-direct": ["instagram-url-direct@2.0.7", "", { "dependencies": { "axios": "^1.7.7", "qs": "^6.13.0" } }, "sha512-SU+wrwY+6w9v53V6kFyfpNB55I8cfEHH9Qv55jP891/TW9AgiYtCtvmyK0EBbwslbgUsG+iT2pnt6UcucZuITA=="], - - "ip-regex": ["ip-regex@4.3.0", "", {}, "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q=="], - - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - - "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], - - "is-regexp": ["is-regexp@3.1.0", "", {}, "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA=="], - - "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], - - "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], - - "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], - - "just-random": ["just-random@3.2.0", "", {}, "sha512-RMf8vbtCfLIbAEHvIPu2FwMkpB/JudGyk/VPfqPItcRgt7k8QnV+Aa7s7kRFPo+bavQkUi8Yg1x/ooW6Ttyb9A=="], - - "knip": ["knip@5.70.2", "", { "dependencies": { "@nodelib/fs.walk": "^1.2.3", "fast-glob": "^3.3.3", "formatly": "^0.3.0", "jiti": "^2.6.0", "js-yaml": "^4.1.1", "minimist": "^1.2.8", "oxc-resolver": "^11.13.2", "picocolors": "^1.1.1", "picomatch": "^4.0.1", "smol-toml": "^1.5.2", "strip-json-comments": "5.0.3", "zod": "^4.1.11" }, "peerDependencies": { "@types/node": ">=18", "typescript": ">=5.0.4 <7" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-LI7DbeVnk7h9+FAet5KzzHNdDwJyqDa2+cn4uQfZYTfpuVjEqtGmYD9r5b9JEuOs4eVkf/7sskNhWXxELm3C/Q=="], - - "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], - - "lodash.snakecase": ["lodash.snakecase@4.1.1", "", {}, "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="], - - "magic-bytes.js": ["magic-bytes.js@1.12.1", "", {}, "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA=="], - - "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], - - "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], - - "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - - "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - - "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - - "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], - - "normalize-url": ["normalize-url@8.1.0", "", {}, "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w=="], - - "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], - - "oxc-resolver": ["oxc-resolver@11.14.0", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.14.0", "@oxc-resolver/binding-android-arm64": "11.14.0", "@oxc-resolver/binding-darwin-arm64": "11.14.0", "@oxc-resolver/binding-darwin-x64": "11.14.0", "@oxc-resolver/binding-freebsd-x64": "11.14.0", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.14.0", "@oxc-resolver/binding-linux-arm-musleabihf": "11.14.0", "@oxc-resolver/binding-linux-arm64-gnu": "11.14.0", "@oxc-resolver/binding-linux-arm64-musl": "11.14.0", "@oxc-resolver/binding-linux-ppc64-gnu": "11.14.0", "@oxc-resolver/binding-linux-riscv64-gnu": "11.14.0", "@oxc-resolver/binding-linux-riscv64-musl": "11.14.0", "@oxc-resolver/binding-linux-s390x-gnu": "11.14.0", "@oxc-resolver/binding-linux-x64-gnu": "11.14.0", "@oxc-resolver/binding-linux-x64-musl": "11.14.0", "@oxc-resolver/binding-wasm32-wasi": "11.14.0", "@oxc-resolver/binding-win32-arm64-msvc": "11.14.0", "@oxc-resolver/binding-win32-ia32-msvc": "11.14.0", "@oxc-resolver/binding-win32-x64-msvc": "11.14.0" } }, "sha512-i4wNrqhOd+4YdHJfHglHtFiqqSxXuzFA+RUqmmWN1aMD3r1HqUSrIhw17tSO4jwKfhLs9uw1wzFPmvMsWacStg=="], - - "p-event": ["p-event@6.0.1", "", { "dependencies": { "p-timeout": "^6.1.2" } }, "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w=="], - - "p-timeout": ["p-timeout@6.1.4", "", {}, "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg=="], - - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - - "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], - - "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], - - "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], - - "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], - - "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], - - "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], - - "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], - - "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], - - "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], - - "smol-toml": ["smol-toml@1.5.2", "", {}, "sha512-QlaZEqcAH3/RtNyet1IPIYPsEWAaYyXXv1Krsi+1L/QHppjX4Ifm8MQsBISz9vE8cHicIq3clogsheili5vhaQ=="], - - "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], - - "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], - - "strip-json-comments": ["strip-json-comments@5.0.3", "", {}, "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw=="], - - "super-regex": ["super-regex@0.2.0", "", { "dependencies": { "clone-regexp": "^3.0.0", "function-timeout": "^0.1.0", "time-span": "^5.1.0" } }, "sha512-WZzIx3rC1CvbMDloLsVw0lkZVKJWbrkJ0k1ghKFmcnPrW1+jWbgTkTEWVtD9lMdmI4jZEz40+naBxl1dCUhXXw=="], - - "terminal-size": ["terminal-size@4.0.0", "", {}, "sha512-rcdty1xZ2/BkWa4ANjWRp4JGpda2quksXIHgn5TMjNBPZfwzJIgR68DKfSYiTL+CZWowDX/sbOo5ME/FRURvYQ=="], - - "time-span": ["time-span@5.1.0", "", { "dependencies": { "convert-hrtime": "^5.0.0" } }, "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA=="], - - "tlds": ["tlds@1.261.0", "", { "bin": { "tlds": "bin.js" } }, "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA=="], - - "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], - - "ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="], - - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "undici": ["undici@6.21.3", "", {}, "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw=="], - - "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - - "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], - - "url-regex-safe": ["url-regex-safe@4.0.0", "", { "dependencies": { "ip-regex": "4.3.0", "tlds": "^1.242.0" }, "peerDependencies": { "re2": "^1.20.1" }, "optionalPeers": ["re2"] }, "sha512-BrnFCWKNFrFnRzKD66NtJqQepfJrUHNPvPxE5y5NSAhXBb4OlobQjt7907Jm4ItPiXaeX+dDWMkcnOd4jR9N8A=="], - - "walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="], - - "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], - - "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], - - "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], - - "yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="], - - "yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="], - - "zod": ["zod@4.1.13", "", {}, "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig=="], - - "@discordjs/rest/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], - - "@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], - - "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - } -} diff --git a/bunfig.toml b/bunfig.toml deleted file mode 100644 index 57f75ba..0000000 --- a/bunfig.toml +++ /dev/null @@ -1,2 +0,0 @@ -[install] -linker = "hoisted" diff --git a/knip.json b/knip.json deleted file mode 100644 index d82312d..0000000 --- a/knip.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "./node_modules/knip/schema-jsonc.json", - "ignoreDependencies": ["@corebyte/frp"], - "rules": { - "exports": "off" - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 4e7fbd4..0000000 --- a/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "embdr", - "module": "src/index.ts", - "type": "module", - "scripts": { - "start": "bun run .", - "dev": "bun run --watch .", - "frp": "bun run --env-file .env frpc -c ./scripts/frpc.toml", - "lint": "biome check --fix", - "knip": "knip-bun" - }, - "devDependencies": { - "@biomejs/biome": "2.3.6", - "@corebyte/frp": "^1.0.6", - "@types/bun": "1.3.2", - "@types/fs-extra": "^11.0.4", - "typescript": "^5.9.3", - "knip": "^5.70.1" - }, - "dependencies": { - "@kitajs/html": "^4.2.11", - "@kitajs/ts-html-plugin": "^4.1.3", - "discord.js": "^14.25.0", - "fs-extra": "^11.3.2", - "get-urls": "^12.1.0", - "instagram-url-direct": "^2.0.7", - "just-random": "^3.2.0" - }, - "trustedDependencies": [ - "@biomejs/biome", - "@corebyte/frp" - ], - "patchedDependencies": { - "instagram-url-direct@2.0.7": "patches/instagram-url-direct@2.0.7.patch" - } -} diff --git a/patches/instagram-url-direct@2.0.7.patch b/patches/instagram-url-direct@2.0.7.patch deleted file mode 100644 index 18d7091..0000000 --- a/patches/instagram-url-direct@2.0.7.patch +++ /dev/null @@ -1,140 +0,0 @@ -diff --git a/dist/instagram.js b/dist/instagram.js -index 6bb6eaa84127ebdebd1933d4bc5bcf50fa6bdda5..c019696e8209e9ff0c1726421e729d18f071b03f 100644 ---- a/dist/instagram.js -+++ b/dist/instagram.js -@@ -4,15 +4,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) { - }; - Object.defineProperty(exports, "__esModule", { value: true }); - exports.instagramGetUrl = instagramGetUrl; --const axios_1 = __importDefault(require("axios")); -+exports.instagramGetId = getShortcode; -+exports.instagramGetRedirect = checkRedirect; - const qs_1 = __importDefault(require("qs")); - //Main function --async function instagramGetUrl(url_media, config = { retries: 5, delay: 1000 }) { -+async function instagramGetUrl(id_media, config = { retries: 5, delay: 1000 }) { - return new Promise(async (resolve, reject) => { - try { -- url_media = await checkRedirect(url_media); -- const SHORTCODE = getShortcode(url_media); -- const INSTAGRAM_REQUEST = await instagramRequest(SHORTCODE, config.retries, config.delay); -+ const INSTAGRAM_REQUEST = await instagramRequest(id_media, config.retries, config.delay, config.proxy); - const OUTPUT_DATA = createOutputData(INSTAGRAM_REQUEST); - resolve(OUTPUT_DATA); - } -@@ -22,11 +21,14 @@ async function instagramGetUrl(url_media, config = { retries: 5, delay: 1000 }) - }); - } - //Utilities --async function checkRedirect(url) { -+async function checkRedirect(url, proxy) { - let split_url = url.split("/"); - if (split_url.includes("share")) { -- let res = await axios_1.default.get(url); -- return res.request.path; -+ const response = await fetch(url, { -+ method: 'GET', -+ proxy, -+ }) -+ return response.url; - } - return url; - } -@@ -83,19 +85,17 @@ function getShortcode(url) { - throw new Error(`Failed to obtain shortcode: ${err.message}`); - } - } --async function getCSRFToken() { -+async function getCSRFToken(proxy) { - try { -- let config = { -- method: 'GET', -- url: 'https://www.instagram.com/', -- }; - const token = await new Promise((resolve, reject) => { -- axios_1.default.request(config).then((response) => { -- if (!response.headers['set-cookie']) { -+ fetch('https://www.instagram.com/', { -+ method: 'GET', -+ proxy, -+ }).then((response) => { -+ if (!response.headers.get('set-cookie')) { - reject(new Error('CSRF token not found in response headers.')); -- } -- else { -- const csrfCookie = response.headers['set-cookie'][0]; -+ } else { -+ const csrfCookie = response.headers.get('set-cookie')[0]; - const csrfToken = csrfCookie.split(";")[0].replace("csrftoken=", ''); - resolve(csrfToken); - } -@@ -117,7 +117,7 @@ function isSidecar(requestData) { - throw new Error(`Failed sidecar verification: ${err.message}`); - } - } --async function instagramRequest(shortcode, retries, delay) { -+async function instagramRequest(shortcode, retries, delay, proxy) { - var _a; - try { - const BASE_URL = "https://www.instagram.com/graphql/query"; -@@ -131,18 +131,17 @@ async function instagramRequest(shortcode, retries, delay) { - }), - 'doc_id': INSTAGRAM_DOCUMENT_ID - }); -- const token = await getCSRFToken(); -- let config = { -- method: 'post', -- maxBodyLength: Infinity, -- url: BASE_URL, -- headers: { -- 'X-CSRFToken': token, -- 'Content-Type': 'application/x-www-form-urlencoded', -- }, -- data: dataBody -- }; -- const { data } = await axios_1.default.request(config); -+ const token = await getCSRFToken(proxy); -+ const response = await fetch(BASE_URL, { -+ proxy, -+ method: 'POST', -+ headers: { -+ 'X-CSRFToken': token, -+ 'Content-Type': 'application/x-www-form-urlencoded', -+ }, -+ body: dataBody -+ }) -+ const data = await response.json(); - if (!((_a = data.data) === null || _a === void 0 ? void 0 : _a.xdt_shortcode_media)) - throw new Error("Only posts/reels supported, check if your link is valid."); - return data.data.xdt_shortcode_media; -diff --git a/dist/types/instagram.d.ts b/dist/types/instagram.d.ts -index a132503ece1623a404c3613f2340eb44a5993cb1..61eb385bb1a646e3b63ff20a20ba370da9dc5e2e 100644 ---- a/dist/types/instagram.d.ts -+++ b/dist/types/instagram.d.ts -@@ -24,7 +24,13 @@ export interface InstagramResponse { - export interface InstagramError { - error: string; - } --export declare function instagramGetUrl(url_media: string, config?: { -+ -+export declare function instagramGetUrl(id_media: string, config?: { - retries: number; - delay: number; -+ proxy?: string; - }): Promise; -+ -+export declare function instagramGetId(url_media:string): Promise; -+ -+export declare function instagramGetRedirect(url_media:string, proxy?: string): Promise; -diff --git a/package.json b/package.json -index bf293eee1fe2d4b956e97614972c773620ceb90a..003c8ca8fdb5b21ede47811255385a28ba0df7f1 100644 ---- a/package.json -+++ b/package.json -@@ -43,7 +43,6 @@ - }, - "homepage": "https://github.com/victorsouzaleal/instagram-direct-url#readme", - "dependencies": { -- "axios": "^1.7.7", - "qs": "^6.13.0" - }, - "devDependencies": { diff --git a/scripts/frpc.toml b/scripts/frpc.toml deleted file mode 100644 index f6dc099..0000000 --- a/scripts/frpc.toml +++ /dev/null @@ -1,10 +0,0 @@ -serverAddr = "{{ .Envs.FRP_SERVER_ADDRESS }}" -serverPort = 7000 -auth.token = "{{ .Envs.FRP_TOKEN }}" - -[[proxies]] -name = "EMBDR Test" -type = "tcp" -localIP = "127.0.0.1" -localPort = {{ .Envs.WEBSERVER_PORT }} -remotePort = {{ .Envs.FRP_REMOTE_PORT }} \ No newline at end of file diff --git a/src/classes/DiscordBot.ts b/src/classes/DiscordBot.ts deleted file mode 100644 index c41e1e4..0000000 --- a/src/classes/DiscordBot.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { env } from "bun"; -import { Client, MessageFlags } from "discord.js"; -import getUrls from "get-urls"; -import random from "just-random"; -import type BaseSource from "./sources/BaseSource"; - -export default class DiscordBot { - client: Client; - constructor(sources: BaseSource[]) { - this.client = new Client({ intents: [53608447] }); - - console.info("[discord] logging in to discord"); - this.client.login(env.DISCORD_TOKEN as string); - this.client.on("clientReady", () => { - console.info("[discord] connected to discord"); - }); - - this.client.on("messageCreate", async (message) => { - if (message.author.bot) return; - if (!message.inGuild()) return; - - const content = message.content; - const urls = getUrls(content); - - for (const urlString of urls) { - const url = new URL(urlString); - for (const source of sources) { - const match = await source.match(url); - if (!match) continue; - - const id = await source.extractId(url); - if (!id) continue; - - console.info(`[discord] embedding ${id} from ${source.name}`); - const response = random(source.messages); - message.suppressEmbeds(true); - message.reply({ - content: `[${response}](${env.WEBSERVER_URL}/e/${source.short}/${id})`, - flags: [MessageFlags.SuppressNotifications], - allowedMentions: { - repliedUser: false, - users: [], - roles: [], - }, - }); - return; - } - } - }); - } -} diff --git a/src/classes/EMBDR.ts b/src/classes/EMBDR.ts deleted file mode 100644 index 1510845..0000000 --- a/src/classes/EMBDR.ts +++ /dev/null @@ -1,17 +0,0 @@ -import DiscordBot from "./DiscordBot"; -import type BaseSource from "./sources/BaseSource"; -import InstagramSource from "./sources/InstagramSource"; -import WebServer from "./WebServer"; - -export default class EMBDR { - sources: BaseSource[]; - discord: DiscordBot; - webServer: WebServer; - constructor() { - console.info("[embdr] starting..."); - this.sources = [new InstagramSource()]; - - this.discord = new DiscordBot(this.sources); - this.webServer = new WebServer(this.sources); - } -} diff --git a/src/classes/WebServer.ts b/src/classes/WebServer.ts deleted file mode 100644 index 973ad5d..0000000 --- a/src/classes/WebServer.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { env } from "bun"; -import Embed from "../pages/embed"; -import type BaseSource from "./sources/BaseSource"; - -export default class WebServer { - constructor(sources: BaseSource[]) { - const host = env.WEBSERVER_URL as string; - const port = env.WEBSERVER_PORT as string; - - console.info(`[webserver] listening on ${port} at ${host}`); - Bun.serve({ - idleTimeout: 30, - port: env.WEBSERVER_PORT as unknown as number, - routes: { - "/e/:sourceId/:mediaId": async (request) => { - const { sourceId, mediaId } = request.params; - - const source = sources.find((s) => s.short === sourceId); - if (!source) - return new Response( - `The source with id "${sourceId}" is unknown.`, - { status: 404 }, - ); - - const media = await source.extractMedia(mediaId); - if (!media) - return new Response( - `The media with id "${mediaId}" is unknown for source "${sourceId}".`, - { status: 404 }, - ); - - return new Response(Embed(media) as string, { - headers: { - "Content-Type": "text/html", - }, - }); - }, - - "/m/:sourceId/:mediaId/:mediaIndex": async (request) => { - const { sourceId, mediaId, mediaIndex } = request.params; - - const source = sources.find((s) => s.short === sourceId); - if (!source) - return new Response( - `The source with id "${sourceId}" is unknown.`, - { status: 404 }, - ); - - const media = await source.extractMedia(mediaId); - if (!media) - return new Response( - `The media with id "${mediaId}" is unknown for source "${sourceId}".`, - { status: 404 }, - ); - - const index = parseInt(mediaIndex, 10); - if (Number.isNaN(index) || index < 0 || index >= media.media.length) - return new Response( - `The media index "${mediaIndex}" is invalid for media "${mediaId}" on source "${sourceId}".`, - { status: 404 }, - ); - - const selectedMedia = media.media[index]!; - - return new Response(null, { - status: 302, - headers: { - Location: selectedMedia.url, - }, - }); - }, - }, - }); - } -} diff --git a/src/classes/sources/BaseSource.ts b/src/classes/sources/BaseSource.ts deleted file mode 100644 index 04bb18a..0000000 --- a/src/classes/sources/BaseSource.ts +++ /dev/null @@ -1,45 +0,0 @@ -type MaybePromise = T | Promise; - -export type MediaInformation = { - id: string; - source: string; - title: string; - description: string; - author: string; - url: string; - media: { - url: string; - type: "video" | "image"; - dimensions: { - width: number; - height: number; - }; - }[]; -}; - -export default abstract class BaseSource { - abstract readonly name: string; - abstract readonly short: string; - abstract readonly messages: string[]; - - /** - * - * @param url URL to match - * @returns if the url matched this source - */ - abstract match(url: URL): MaybePromise; - - /** - * - * @param url URL to extract id from - * @returns extracted id or null if not found - */ - abstract extractId(url: URL): MaybePromise; - - /** - * - * @param id id to extract data from - * @returns MediaInformation object - */ - abstract extractMedia(id: string): MaybePromise; -} diff --git a/src/classes/sources/InstagramSource.ts b/src/classes/sources/InstagramSource.ts deleted file mode 100644 index 9e41dbc..0000000 --- a/src/classes/sources/InstagramSource.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { Database } from "bun:sqlite"; -import { env } from "bun"; -import { ensureDirSync } from "fs-extra"; -import { - instagramGetId, - instagramGetRedirect, - instagramGetUrl, -} from "instagram-url-direct"; -import BaseSource, { type MediaInformation } from "./BaseSource"; - -const PROXY_URL = env.PROXY_URL || undefined; - -ensureDirSync("./cache"); -const cache = new Database("./cache/instagram.db"); - -cache.run(` - CREATE TABLE IF NOT EXISTS "posts" ( - id TEXT PRIMARY KEY, - data TEXT NOT NULL - ) -`); - -export default class InstagramSource extends BaseSource { - name = "Instagram"; - short = "ig"; - messages = [ - "Beep beep, your embed is ready!", - "Embed? Op je muil gauw!", - "Here's your spicy Instagram drop!", - "Fresh from the 'Gram, just for you!", - "Look what I found on Insta!", - "Your daily dose of Instagram, served hot!", - "Instagram magic, coming right up!", - "Another banger from the 'Gram!", - "Sliding into your DMs with this embed!", - "Insta-nt delivery, enjoy your post!", - "Want to see dead people? Check this out!", - ]; - - async match(url: URL) { - return ( - url.hostname === "www.instagram.com" || - url.hostname === "instagram.com" || - url.hostname === "ddinstagram.com" - ); - } - - async extractId(url: URL) { - try { - const redirected = await instagramGetRedirect(url.toString(), PROXY_URL); - const id = await instagramGetId(redirected); - return id.split("?")[0]!; - } catch { - return null; - } - } - - async extractMedia(id: string) { - const cached = await this.readCache(id); - if (cached) { - console.info(`[instagram] got post ${id} from cache`); - return cached; - } - - const data = await this.requestPost(id); - if (!data) return null; - - cache - .prepare( - ` - INSERT OR REPLACE INTO "posts" ("id", "data") VALUES (?, ?) - `, - ) - .run(id, JSON.stringify(data)); - - console.info(`[instagram] got post ${id} from the api`); - - return data; - } - - private async readCache(id: string) { - const row = await cache - .query(`SELECT "data" from "posts" where "id" = ?`) - .get(id); - - const data = row as { data: string } | undefined; - if (!data) return null; - return JSON.parse(data.data); - } - - private async requestPost(id: string): Promise { - try { - const data = await instagramGetUrl(id, { - retries: 5, - delay: 1000, - proxy: PROXY_URL, - }); - return { - id: id, - source: this.short, - title: `Post by ${data.post_info.owner_fullname}`, - description: data.post_info.caption, - author: data.post_info.owner_username, - url: `https://www.instagram.com/p/${id}`, - media: data.media_details.map((media) => ({ - type: media.type as "video" | "image", - url: media.url, - dimensions: media.dimensions, - })), - }; - } catch (e) { - console.info(`[instagram] requesting post ${id} failed: ${e}`); - return null; - } - } -} diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index ce8d155..0000000 --- a/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import EMBDR from "./classes/EMBDR"; - -new EMBDR(); diff --git a/src/pages/embed.tsx b/src/pages/embed.tsx deleted file mode 100644 index d643fdb..0000000 --- a/src/pages/embed.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { env } from "bun"; -import type { MediaInformation } from "../classes/sources/BaseSource"; - -export default function Embed(data: MediaInformation) { - return ( - - - EMBDR - - {/*Type*/} - - - - {/*Title*/} - - - - {/*Author*/} - - - {/*Description*/} - - - {/*Site name*/} - - - - {/*URL*/} - - - {/*Theme color*/} - - - {data.media.map((media, index) => { - switch (media.type) { - case "video": - return ( - <> - {/*Video*/} - - - - {/*Secure Video*/} - - - {/*Width*/} - - - - {/*Height*/} - - - - ); - - case "image": - return ( - <> - {/*Image*/} - - - - {/*Width*/} - - - {/*Height*/} - - - ); - - default: - return null; - } - })} - - {/* Redirect immediately */} - - - - You are being redirected... - - - ); -} diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 8d7106d..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "compilerOptions": { - // Environment setup & latest features - "lib": ["ESNext"], - "target": "ESNext", - "module": "ESNext", - "moduleDetection": "force", - "allowJs": true, - - // Jsx - "jsx": "react-jsx", - "jsxImportSource": "@kitajs/html", - "plugins": [{ "name": "@kitajs/ts-html-plugin" }], - - // Bundler mode - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "noEmit": true, - - // Best practices - "strict": true, - "skipLibCheck": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true, - - // Some stricter flags (disabled by default) - "noUnusedLocals": false, - "noUnusedParameters": false, - "noPropertyAccessFromIndexSignature": false - } -} From 513b0360a83c74cb6cadca783bd6ade8e0b274b3 Mon Sep 17 00:00:00 2001 From: CoreByte <62032154+CoreBytee@users.noreply.github.com> Date: Tue, 1 Sep 2026 00:21:19 +0200 Subject: [PATCH 02/11] setup rust rework --- .env.example | 5 - .gitignore | 7 +- AGENTS.md | 1 + Cargo.lock | 2170 ++++++++++++++++++++ Cargo.toml | 10 + packages/embdr/Cargo.toml | 30 + packages/embdr/src/main.rs | 191 ++ packages/embdr/src/sources/instagram.rs | 78 + packages/embdr/src/sources/mod.rs | 70 + packages/embdr/src/utility/extract_urls.rs | 11 + packages/embdr/src/utility/mod.rs | 1 + packages/instagram-client/Cargo.toml | 17 + packages/instagram-client/src/lib.rs | 620 ++++++ 13 files changed, 3201 insertions(+), 10 deletions(-) create mode 100644 AGENTS.md create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 packages/embdr/Cargo.toml create mode 100644 packages/embdr/src/main.rs create mode 100644 packages/embdr/src/sources/instagram.rs create mode 100644 packages/embdr/src/sources/mod.rs create mode 100644 packages/embdr/src/utility/extract_urls.rs create mode 100644 packages/embdr/src/utility/mod.rs create mode 100644 packages/instagram-client/Cargo.toml create mode 100644 packages/instagram-client/src/lib.rs diff --git a/.env.example b/.env.example index 9011eec..1b1d8aa 100644 --- a/.env.example +++ b/.env.example @@ -7,8 +7,3 @@ DISCORD_TOKEN= # Proxy PROXY_URL= - -# FRP -FRP_SERVER_ADDRESS= -FRP_TOKEN= -FRP_REMOTE_PORT= diff --git a/.gitignore b/.gitignore index 0bb3fa2..11b6a29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,5 @@ # Secrets /.env -# Bun -/node_modules - -# Cache -/cache +# Cargo +/target diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0a63af0 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +- No abbreviations in variable, function or other names. diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..9d816a6 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2170 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "brotli-decompressor" +version = "5.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cc" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "combine" +version = "4.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "cookie" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" +dependencies = [ + "cookie", + "document-features", + "idna", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "embdr" +version = "0.1.0" +dependencies = [ + "async-trait", + "dotenvy", + "instagram-client", + "rustls", + "tokio", + "tracing", + "tracing-subscriber", + "twilight-gateway", + "twilight-http", + "twilight-model", + "twilight-util", + "url", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "h2" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-platform-verifier", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "instagram-client" +version = "0.1.0" +dependencies = [ + "reqwest", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "url", + "urlencoding", +] + +[[package]] +name = "ipnet" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "openssl" +version = "0.10.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "publicsuffix" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" +dependencies = [ + "idna", + "psl-types", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "cookie", + "cookie_store", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "libc", + "pin-project-lite", + "slab", + "tokio", +] + +[[package]] +name = "tokio-websockets" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52efb639344a7c6adb8e62c6f3d2c19c001ff1b79a5041ba1c6ed42e19c6aa5" +dependencies = [ + "base64", + "bytes", + "fastrand", + "futures-core", + "futures-sink", + "http", + "httparse", + "rustls-pki-types", + "rustls-platform-verifier", + "sha1_smol", + "simdutf8", + "tokio", + "tokio-rustls", + "tokio-util", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "twilight-gateway" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59267541c31f888c1587da5e7cbab182ae6efd98faedd9ea2e35a4eef43ff204" +dependencies = [ + "bitflags", + "fastrand", + "futures-core", + "futures-sink", + "serde", + "serde_json", + "tokio", + "tokio-websockets", + "tracing", + "twilight-gateway-queue", + "twilight-http", + "twilight-model", + "zstd-safe", +] + +[[package]] +name = "twilight-gateway-queue" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366a73fe47f61a3d522c3aaf70475e60634b0ae59e7b94272ed7496fffa7ceb7" +dependencies = [ + "tokio", + "tracing", +] + +[[package]] +name = "twilight-http" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b868001d7bfb953732b3f1cd92f63cb88cd2ea902f590b1d084c66aea60e48" +dependencies = [ + "brotli-decompressor", + "fastrand", + "http", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "percent-encoding", + "rustls", + "serde", + "serde_json", + "tokio", + "tracing", + "twilight-http-ratelimiting", + "twilight-model", + "twilight-validate", +] + +[[package]] +name = "twilight-http-ratelimiting" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0515b0c30814068a7540fcb5f58b634259ca453fa335d42c3b2c8f2b06ac6a59" +dependencies = [ + "hashbrown 0.16.1", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "twilight-model" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf6bb7b93a7f765d89b3388cc710c0ae16104579e06bb30ea1ee6bd41420a8b" +dependencies = [ + "bitflags", + "serde", + "serde-value", + "serde_repr", + "time", +] + +[[package]] +name = "twilight-util" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e71de79d48ce27aba1f591b10c84677f1317489c2ea154d9b8ed01efa8bceb53" +dependencies = [ + "twilight-model", + "twilight-validate", +] + +[[package]] +name = "twilight-validate" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6a27472e023e3841d1c4e4e20253ed796e8440aada8b5205b8544f1172e661d" +dependencies = [ + "twilight-model", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a8cda64 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[workspace] +resolver = "3" +members = [ + "packages/embdr", + "packages/instagram-client", +] +default-members = ["packages/embdr"] + +[workspace.dependencies] +instagram-client = { path = "packages/instagram-client" } diff --git a/packages/embdr/Cargo.toml b/packages/embdr/Cargo.toml new file mode 100644 index 0000000..0f5c1e3 --- /dev/null +++ b/packages/embdr/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "embdr" +version = "0.1.0" +edition = "2024" + +[dependencies] +instagram-client.workspace = true + +# Environment +dotenvy = "0.15.7" + +# Async +tokio = { version = "1.53.1", features = ["full"] } + +# Logging +tracing = "0.1.44" +tracing-subscriber = "0.3.23" + +# Discord +twilight-gateway = "0.17.1" +twilight-http = "0.17.1" +twilight-model = "0.17.1" +twilight-util = { version = "0.17.0", features = ["builder"] } + +# TLS crypto provider for rustls (used by twilight) +rustls = { version = "0.23", default-features = false, features = ["ring", "logging", "std", "tls12"] } + +# Other +async-trait = "0.1.92" +url = "2.5.8" diff --git a/packages/embdr/src/main.rs b/packages/embdr/src/main.rs new file mode 100644 index 0000000..f292343 --- /dev/null +++ b/packages/embdr/src/main.rs @@ -0,0 +1,191 @@ +use std::sync::Arc; + +use twilight_gateway::{Event, EventTypeFlags, Intents, Shard, ShardId, StreamExt}; +use twilight_http::Client as HttpClient; +use twilight_model::channel::message::{ + Component, MessageFlags, + component::{MediaGallery, MediaGalleryItem, UnfurledMediaItem}, +}; +use twilight_util::builder::message::{ContainerBuilder, TextDisplayBuilder}; + +use crate::{sources::Sources, utility::extract_urls::extract_urls}; + +mod sources; +mod utility; + +struct EMBDR { + sources: Arc, + gateway: Shard, + http: Arc, +} + +impl EMBDR { + fn new(token: String) -> Self { + let sources = Arc::new(sources::get_sources()); + + let intents = Intents::GUILD_MESSAGES | Intents::MESSAGE_CONTENT; + + let gateway = Shard::new(ShardId::ONE, token.clone(), intents); + let http = Arc::new(HttpClient::new(token.clone())); + + Self { + sources, + gateway, + http, + } + } + + async fn poll_event(&mut self) -> bool { + let Some(item) = self.gateway.next_event(EventTypeFlags::all()).await else { + return false; + }; + + let Ok(event) = item else { + tracing::warn!(source = ?item.unwrap_err(), "error receiving event"); + return true; + }; + + let sources = self.sources.clone(); + tokio::spawn(EMBDR::handle_event(event, sources, self.http.clone())); + true + } + + async fn handle_event(event: Event, sources: Arc, http: Arc) { + match event { + Event::MessageCreate(message) => { + let urls = extract_urls(&message.content); + let embeddable_urls = urls + .iter() + .filter(|url| sources.iter().any(|source| source.predicate(url))) + .collect::>(); + + if embeddable_urls.is_empty() { + return; + } + + let mut components: Vec = Vec::new(); + + let source = sources + .iter() + .find(|source| source.predicate(embeddable_urls[0])) + .expect("No source found for the embeddable URL (should not happen)"); + + let media_data_result = source.extract_media(embeddable_urls[0]).await; + if let Ok(media_data) = media_data_result { + println!("Media data: {:#?}", media_data); + if embeddable_urls.len() > 1 { + components.push( + ContainerBuilder::new() + .component(TextDisplayBuilder::new(":information_source: Multiple embeddable links found. Only showing the first embed.").build()) + .build() + .into() + ); + } + + let mut container = ContainerBuilder::new().accent_color(Some(source.color())); + + container = container.component( + TextDisplayBuilder::new(format!( + "## Post by [{}]({})", + media_data.author.name, media_data.author.url + )) + .build(), + ); + + if let Some(description) = media_data.description.clone() { + container = + container.component(TextDisplayBuilder::new(description).build()); + } + + if media_data.items.len() > 0 { + container = container.component(MediaGallery { + id: None, + items: media_data + .items + .iter() + .map(|item| MediaGalleryItem { + description: None, + spoiler: None, + media: UnfurledMediaItem { + url: item.url.clone(), + proxy_url: None, + content_type: None, + width: None, + height: None, + }, + }) + .collect(), + }); + } + + if media_data.properties.len() > 0 { + let properties_text = media_data + .properties + .iter() + .map(|property| match property { + sources::MediaProperty::LikeCount(count) => { + format!("{} {}", property.emoji(), count) + } + sources::MediaProperty::CommentCount(count) => { + format!("{} {}", property.emoji(), count) + } + }) + .collect::>() + .join(" • "); + + container = + container.component(TextDisplayBuilder::new(properties_text).build()); + } + + components.push(container.build().into()); + } else { + components.push( + ContainerBuilder::new() + .component( + TextDisplayBuilder::new( + ":warning: Failed to extract media from the provided link.", + ) + .build(), + ) + .build() + .into(), + ); + } + + let result = http + .create_message(message.channel_id) + .reply(message.id) + .flags(MessageFlags::IS_COMPONENTS_V2 | MessageFlags::SUPPRESS_NOTIFICATIONS) + .components(&components) + .await; + + if let Err(err) = result { + tracing::error!(error = ?err, "Failed to send embed message"); + } + } + Event::Ready(event) => { + tracing::info!("Gateway is ready. Logged in as {}", event.user.name); + } + _ => {} + } + } +} + +#[tokio::main] +async fn main() { + // Initialize tracing subscriber for logging + tracing_subscriber::fmt::init(); + + // Load env vars + dotenvy::dotenv().expect("Failed to load environment variables from .env file"); + + // Install the ring crypto provider for rustls (required by twilight) + rustls::crypto::ring::default_provider() + .install_default() + .expect("Failed to install rustls CryptoProvider"); + + // Load EMBDR + let token = std::env::var("DISCORD_TOKEN").expect("DISCORD_TOKEN not set in .env file"); + let mut embdr = EMBDR::new(token.clone()); + while embdr.poll_event().await {} +} diff --git a/packages/embdr/src/sources/instagram.rs b/packages/embdr/src/sources/instagram.rs new file mode 100644 index 0000000..be95a79 --- /dev/null +++ b/packages/embdr/src/sources/instagram.rs @@ -0,0 +1,78 @@ +use url::Url; + +use instagram_client::InstagramClient; + +use crate::sources::{MediaAuthor, MediaData, MediaItem, MediaProperty, Source}; + +pub struct InstagramSource { + instagram_client: InstagramClient, +} + +impl InstagramSource { + pub fn new() -> Self { + let instagram_client = InstagramClient::new().expect("Failed to create instagram client"); + Self { instagram_client } + } +} + +#[async_trait::async_trait] +impl Source for InstagramSource { + fn name(&self) -> String { + "Instagram".to_string() + } + + fn id(&self) -> String { + "instagram".to_string() + } + + fn short_id(&self) -> String { + "ig".to_string() + } + + fn color(&self) -> u32 { + 0xE1306C + } + + fn predicate(&self, url: &Url) -> bool { + let Some(hostname) = url.host_str() else { + return false; + }; + + let hostname_matches = hostname == "www.instagram.com" + || hostname == "instagram.com" + || hostname == "ddinstagram.com"; + + return hostname_matches; + } + + async fn extract_media( + &self, + url: &Url, + ) -> Result> { + let instagram_post = self.instagram_client.fetch_from_url(url.as_str()).await?; + println!("Instagram post: {:#?}", instagram_post); + + Ok(MediaData { + id: instagram_post.shortcode.clone(), + author: MediaAuthor { + name: instagram_post.owner.username.clone(), + url: format!( + "https://www.instagram.com/{}/", + instagram_post.owner.username + ), + }, + description: instagram_post.caption.clone().into(), + items: instagram_post + .media_items + .into_iter() + .map(|item| MediaItem { + url: item.url().into(), + }) + .collect(), + properties: vec![ + MediaProperty::LikeCount(instagram_post.like_count), + MediaProperty::CommentCount(instagram_post.comment_count), + ], + }) + } +} diff --git a/packages/embdr/src/sources/mod.rs b/packages/embdr/src/sources/mod.rs new file mode 100644 index 0000000..9dd1921 --- /dev/null +++ b/packages/embdr/src/sources/mod.rs @@ -0,0 +1,70 @@ +use url::Url; + +use crate::sources::instagram::InstagramSource; + +mod instagram; + +#[async_trait::async_trait] +pub trait Source { + /// Returns the name of the source. + fn name(&self) -> String; + + /// Returns the unique identifier of the source. + fn id(&self) -> String; + + /// Returns the short identifier of the source. + fn short_id(&self) -> String; + + /// Returns the color associated with the source. + fn color(&self) -> u32; + + /// Returns true if the source can handle the given URL. + fn predicate(&self, url: &Url) -> bool; + + /// Returns the media data for the given URL. + async fn extract_media( + &self, + url: &Url, + ) -> Result>; +} + +pub fn get_sources() -> Sources { + vec![Box::new(InstagramSource::new())] +} + +pub type Sources = Vec>; + +#[derive(Debug)] +pub struct MediaData { + pub id: String, + pub author: MediaAuthor, + pub description: Option, + pub items: Vec, + pub properties: Vec, +} + +#[derive(Debug)] +pub struct MediaAuthor { + pub name: String, + pub url: String, +} + +#[derive(Debug)] +pub struct MediaItem { + pub url: String, +} + +#[derive(Debug)] +pub enum MediaProperty { + LikeCount(u64), + CommentCount(u64), +} + +impl MediaProperty { + pub fn emoji(&self) -> String { + match self { + MediaProperty::LikeCount(_) => "❤️".to_string(), + MediaProperty::CommentCount(_) => "💬".to_string(), + } + } +} diff --git a/packages/embdr/src/utility/extract_urls.rs b/packages/embdr/src/utility/extract_urls.rs new file mode 100644 index 0000000..947353d --- /dev/null +++ b/packages/embdr/src/utility/extract_urls.rs @@ -0,0 +1,11 @@ +use url::Url; + +pub fn extract_urls(string: &String) -> Vec { + let mut urls = Vec::new(); + for word in string.split_whitespace() { + if let Ok(url) = Url::parse(word) { + urls.push(url); + } + } + urls +} diff --git a/packages/embdr/src/utility/mod.rs b/packages/embdr/src/utility/mod.rs new file mode 100644 index 0000000..fca9663 --- /dev/null +++ b/packages/embdr/src/utility/mod.rs @@ -0,0 +1 @@ +pub mod extract_urls; diff --git a/packages/instagram-client/Cargo.toml b/packages/instagram-client/Cargo.toml new file mode 100644 index 0000000..d7c6089 --- /dev/null +++ b/packages/instagram-client/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "instagram-client" +version = "0.1.0" +edition = "2024" + +[dependencies] +reqwest = { version = "0.12", features = ["cookies", "json"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +url = "2" +thiserror = "2" +tracing = "0.1" +tokio = { version = "1", features = ["time", "macros", "rt-multi-thread", "sync"] } +urlencoding = "2" + +[dev-dependencies] +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/packages/instagram-client/src/lib.rs b/packages/instagram-client/src/lib.rs new file mode 100644 index 0000000..99a209d --- /dev/null +++ b/packages/instagram-client/src/lib.rs @@ -0,0 +1,620 @@ +use reqwest::header::{HeaderMap, HeaderValue, REFERER, USER_AGENT}; +use reqwest::{Client, StatusCode}; +use serde::Deserialize; +use serde_json::json; +use thiserror::Error; +use tokio::sync::OnceCell; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +mod string_or_number { + use serde::{self, Deserializer}; + + pub fn deserialize<'de, D>(deserializer: D) -> std::result::Result, D::Error> + where + D: Deserializer<'de>, + { + use serde::de; + + struct StringOrNumberVisitor; + + impl<'de> de::Visitor<'de> for StringOrNumberVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a string or number that can be parsed as u64, or null") + } + + fn visit_none(self) -> std::result::Result + where + E: de::Error, + { + Ok(None) + } + + fn visit_unit(self) -> std::result::Result + where + E: de::Error, + { + Ok(None) + } + + fn visit_u64(self, value: u64) -> std::result::Result + where + E: de::Error, + { + Ok(Some(value)) + } + + fn visit_i64(self, value: i64) -> std::result::Result + where + E: de::Error, + { + Ok(Some(value as u64)) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: de::Error, + { + if value.is_empty() { + Ok(None) + } else { + value.parse::().map(Some).map_err(de::Error::custom) + } + } + } + + deserializer.deserialize_any(StringOrNumberVisitor) + } +} + +// --------------------------------------------------------------------------- +// Errors +// --------------------------------------------------------------------------- + +#[derive(Debug, Error)] +pub enum InstagramError { + #[error("HTTP request failed: {0}")] + Reqwest(#[from] reqwest::Error), + + #[error("CSRF token not found in response")] + NoCsrfToken, + + #[error("GraphQL request failed with status {status}: {body}")] + GraphQl { status: StatusCode, body: String }, + + #[error("API returned no items for shortcode {shortcode}")] + NoItems { shortcode: String }, + + #[error("Rate limited — retries exhausted")] + RateLimited, + + #[error("Failed to resolve share URL")] + ShareUrlResolveFailed, + + #[error("Missing data in response")] + MissingData, + + #[error("JSON parse error: {0}")] + Json(#[from] serde_json::Error), +} + +pub type Result = std::result::Result; + +// --------------------------------------------------------------------------- +// Types — raw API response (v1/iPhone format) +// --------------------------------------------------------------------------- + +#[derive(Debug, Clone, Deserialize)] +pub struct RawMediaItem { + #[serde(default, deserialize_with = "string_or_number::deserialize")] + pub pk: Option, + pub code: Option, + pub media_type: Option, + pub original_width: Option, + pub original_height: Option, + pub video_duration: Option, + pub image_versions2: Option, + pub video_versions: Option>, + pub user: Option, + pub caption: Option, + pub like_count: Option, + pub comment_count: Option, + pub play_count: Option, + pub carousel_media: Option>, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct ImageVersions { + pub candidates: Option>, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct ImageCandidate { + pub url: Option, + pub width: Option, + pub height: Option, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct VideoVersion { + pub url: Option, + pub width: Option, + pub height: Option, + pub id: Option, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Owner { + #[serde(default, deserialize_with = "string_or_number::deserialize")] + pub pk: Option, + pub username: Option, + pub full_name: Option, + pub is_verified: Option, + pub is_private: Option, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Caption { + pub text: Option, +} + +// --------------------------------------------------------------------------- +// Types — normalized / public API +// --------------------------------------------------------------------------- + +#[derive(Debug, Clone)] +pub struct InstagramPost { + pub shortcode: String, + pub media_type: MediaType, + pub media_items: Vec, + pub thumbnail_url: Option, + pub caption: String, + pub owner: MediaOwner, + pub like_count: u64, + pub comment_count: u64, + pub dimensions: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum MediaType { + Image, + Video, + Sidecar, +} + +impl From for MediaType { + fn from(v: u32) -> Self { + match v { + 1 => MediaType::Image, + 2 => MediaType::Video, + 8 => MediaType::Sidecar, + _ => MediaType::Image, + } + } +} + +#[derive(Debug, Clone)] +pub struct MediaOwner { + pub username: String, + pub full_name: String, + pub is_verified: bool, + pub is_private: bool, +} + +#[derive(Debug, Clone)] +pub struct Dimensions { + pub width: u32, + pub height: u32, +} + +#[derive(Debug, Clone)] +pub enum CarouselItem { + Image { url: String }, + Video { url: String }, +} + +impl CarouselItem { + pub fn url(&self) -> &str { + match self { + Self::Image { url } | Self::Video { url } => url, + } + } +} + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const DOC_ID_FETCH_MEDIA: &str = "27128499623469141"; +const DOC_ID_FETCH_PLAY_COUNT: &str = "27234427476213202"; + +const BASE_URL: &str = "https://www.instagram.com"; +const GRAPHQL_URL: &str = "https://www.instagram.com/graphql/query"; + +const DEFAULT_USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36"; + +const MAX_RETRIES: u32 = 3; + +// --------------------------------------------------------------------------- +// CSRF token +// --------------------------------------------------------------------------- + +/// Fetch an anonymous session from instagram.com and extract the `csrftoken` +/// cookie value. +pub async fn get_csrf_token(client: &Client) -> Result { + let resp = client + .get(BASE_URL) + .headers(default_headers(None)) + .send() + .await?; + + let cookies: Vec = resp + .headers() + .get_all(reqwest::header::SET_COOKIE) + .iter() + .filter_map(|v| v.to_str().ok().map(String::from)) + .collect(); + let combined = cookies.join("; "); + + combined + .split(';') + .find_map(|c| { + let c = c.trim(); + c.strip_prefix("csrftoken=").map(String::from) + }) + .ok_or(InstagramError::NoCsrfToken) +} + +// --------------------------------------------------------------------------- +// Shortcode extraction +// --------------------------------------------------------------------------- + +/// Extract the shortcode from various Instagram URL formats. +pub fn extract_shortcode(url: &str) -> String { + let parts: Vec<&str> = url.split('/').collect(); + let tags = ["p", "reel", "tv", "reels"]; + let idx = parts.iter().position(|s| tags.contains(s)).unwrap_or(0) + 1; + parts.get(idx).unwrap_or(&"").to_string() +} + +// --------------------------------------------------------------------------- +// Share URL resolution +// --------------------------------------------------------------------------- + +/// Resolve an `/share/` redirect to the canonical Instagram URL. + +// --------------------------------------------------------------------------- +// GraphQL helpers +// --------------------------------------------------------------------------- + +fn default_headers(csrf: Option<&str>) -> HeaderMap { + let mut headers = HeaderMap::new(); + headers.insert(USER_AGENT, HeaderValue::from_static(DEFAULT_USER_AGENT)); + headers.insert( + REFERER, + HeaderValue::from_static("https://www.instagram.com/"), + ); + if let Some(csrf) = csrf { + headers.insert( + "X-CSRFToken", + HeaderValue::from_str(csrf).unwrap_or(HeaderValue::from_static("")), + ); + } + headers.insert("X-IG-App-ID", HeaderValue::from_static("936619743392459")); + headers.insert( + "X-Requested-With", + HeaderValue::from_static("XMLHttpRequest"), + ); + headers +} + +/// Perform a GraphQL POST with automatic retry on 429 / 403. +async fn graphql_post( + client: &Client, + csrf: &str, + doc_id: &str, + variables: serde_json::Value, +) -> Result { + let body_str = format!( + "variables={}&doc_id={}", + urlencoding::encode(&variables.to_string()), + doc_id, + ); + + let headers = default_headers(Some(csrf)); + + let mut last_status = None; + for attempt in 0..MAX_RETRIES { + let resp = client + .post(GRAPHQL_URL) + .headers(headers.clone()) + .header( + reqwest::header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ) + .body(body_str.clone()) + .send() + .await?; + + let status = resp.status(); + if status == StatusCode::TOO_MANY_REQUESTS || status == StatusCode::FORBIDDEN { + last_status = Some(status); + let wait_ms = 1000 * 2u64.pow(attempt); + tracing::warn!( + "GraphQL request got {} — retrying in {}ms (attempt {})", + status, + wait_ms, + attempt + 1, + ); + tokio::time::sleep(std::time::Duration::from_millis(wait_ms)).await; + continue; + } + + if status.is_success() { + return resp.json::().await.map_err(Into::into); + } + + let body = resp.text().await.unwrap_or_default(); + return Err(InstagramError::GraphQl { status, body }); + } + + // All retries exhausted + if let Some(status) = last_status { + if status == StatusCode::TOO_MANY_REQUESTS { + return Err(InstagramError::RateLimited); + } + return Err(InstagramError::GraphQl { + status, + body: "retries exhausted (403)".into(), + }); + } + + Err(InstagramError::RateLimited) +} + +// --------------------------------------------------------------------------- +// Normalize helper +// --------------------------------------------------------------------------- + +fn normalize_item(item: &RawMediaItem) -> InstagramPost { + let media_type: MediaType = item.media_type.unwrap_or(1).into(); + let is_video = media_type == MediaType::Video; + + let image_url = item + .image_versions2 + .as_ref() + .and_then(|iv| iv.candidates.as_ref()) + .and_then(|c| c.first()) + .and_then(|c| c.url.clone()); + + let video_url = item + .video_versions + .as_ref() + .and_then(|v| v.first()) + .and_then(|v| v.url.clone()); + + let owner = item.user.as_ref().map(|u| MediaOwner { + username: u.username.clone().unwrap_or_default(), + full_name: u.full_name.clone().unwrap_or_default(), + is_verified: u.is_verified.unwrap_or(false), + is_private: u.is_private.unwrap_or(false), + }); + + let media_items = if let Some(ref carousel) = item.carousel_media { + carousel + .iter() + .map(|child| { + let child_img = child + .image_versions2 + .as_ref() + .and_then(|iv| iv.candidates.as_ref()) + .and_then(|c| c.first()) + .and_then(|c| c.url.clone()); + let child_vid = child + .video_versions + .as_ref() + .and_then(|v| v.first()) + .and_then(|v| v.url.clone()); + if child.media_type == Some(2) { + CarouselItem::Video { + url: child_vid.unwrap_or_default(), + } + } else { + CarouselItem::Image { + url: child_img.unwrap_or_default(), + } + } + }) + .collect() + } else { + vec![if is_video { + CarouselItem::Video { + url: video_url.unwrap_or_default(), + } + } else { + CarouselItem::Image { + url: image_url.unwrap_or_default(), + } + }] + }; + + let dims = match (item.original_width, item.original_height) { + (Some(w), Some(h)) => Some(Dimensions { + width: w, + height: h, + }), + _ => None, + }; + + let thumbnail_url = if is_video { + item.image_versions2 + .as_ref() + .and_then(|iv| iv.candidates.as_ref()) + .and_then(|c| c.first()) + .and_then(|c| c.url.clone()) + } else { + None + }; + + InstagramPost { + shortcode: item.code.clone().unwrap_or_default(), + media_type, + media_items, + thumbnail_url, + caption: item + .caption + .as_ref() + .and_then(|c| c.text.clone()) + .unwrap_or_default(), + owner: owner.unwrap_or(MediaOwner { + username: String::new(), + full_name: String::new(), + is_verified: false, + is_private: false, + }), + like_count: item.like_count.unwrap_or(0), + comment_count: item.comment_count.unwrap_or(0), + dimensions: dims, + } +} + +// --------------------------------------------------------------------------- +// Client +// --------------------------------------------------------------------------- + +/// Instagram API client that manages a `reqwest::Client` with cookie store +/// and a cached CSRF token. +pub struct InstagramClient { + http_client: Client, + csrf: OnceCell, +} + +impl InstagramClient { + /// Create a new client with an internally-built `reqwest::Client` + /// (cookie store enabled). + pub fn new() -> Result { + let client = Client::builder().cookie_store(true).build()?; + Ok(Self { + http_client: client, + csrf: OnceCell::new(), + }) + } + + /// Create a new client with a provided `reqwest::Client` + pub fn new_with_http(http_client: Client) -> Self { + Self { + http_client, + csrf: OnceCell::new(), + } + } + + /// Fetch (or return cached) CSRF token from instagram.com. + pub async fn get_csrf_token(&self) -> Result<&str> { + let token = self + .csrf + .get_or_try_init(|| get_csrf_token(&self.http_client)) + .await?; + Ok(token.as_str()) + } + + /// Fetch media for a post/reel/igtv by shortcode. + pub async fn fetch_media_item(&self, shortcode: &str) -> Result { + let csrf = self.get_csrf_token().await?; + + let variables = json!({ + "shortcode": shortcode, + "__relay_internal__pv__PolarisAIGMMediaWebLabelEnabledrelayprovider": false, + }); + + let json = graphql_post(&self.http_client, &csrf, DOC_ID_FETCH_MEDIA, variables).await?; + + let items = json + .pointer("/data/xdt_api__v1__media__shortcode__web_info/items") + .and_then(|v| v.as_array()) + .ok_or_else(|| InstagramError::NoItems { + shortcode: shortcode.to_string(), + })?; + + let item: RawMediaItem = serde_json::from_value( + items + .first() + .ok_or_else(|| InstagramError::NoItems { + shortcode: shortcode.to_string(), + })? + .clone(), + )?; + + let normalized = normalize_item(&item); + + Ok(normalized) + } + + /// Fetch play count via the clips connection fallback. + /// + /// `user_id` can be a numeric PK (as `&str`) or username — the endpoint + /// expects the numeric user PK though. + pub async fn fetch_play_count( + &self, + user_id: &str, + shortcode: &str, + ) -> Result> { + let csrf = self.get_csrf_token().await?; + + let variables = json!({ + "data": { + "include_feed_video": true, + "page_size": 12, + "target_user_id": user_id, + } + }); + + let json = + graphql_post(&self.http_client, &csrf, DOC_ID_FETCH_PLAY_COUNT, variables).await?; + + let edges = json + .pointer("/data/xdt_api__v1__clips__user__connection_v2/edges") + .and_then(|v| v.as_array()); + + if let Some(edges) = edges { + for edge in edges { + let media_code = edge.pointer("/node/media/code").and_then(|v| v.as_str()); + if media_code == Some(shortcode) { + let play_count = edge + .pointer("/node/media/play_count") + .and_then(|v| v.as_u64()); + return Ok(play_count); + } + } + } + + Ok(None) + } + + /// Resolve a short share URL (`/share/p/...`) to its canonical form. + pub async fn resolve_share(&self, url: &str) -> Result { + if !url.contains("/share/") { + return Ok(url.to_string()); + } + let resp = self + .http_client + .get(url) + .header(USER_AGENT, DEFAULT_USER_AGENT) + .send() + .await?; + + // reqwest follows redirects by default; the final URL is the resolved one. + Ok(resp.url().to_string()) + } + + /// Convenience: fetch media from a raw Instagram URL (handles share + /// resolution, shortcode extraction, and fetching in one call). + pub async fn fetch_from_url(&self, url: &str) -> Result { + let resolved = self.resolve_share(url).await?; + let shortcode = extract_shortcode(&resolved); + self.fetch_media_item(&shortcode).await + } +} From 9198f9860734864950704c78ecff4e70d54d8a68 Mon Sep 17 00:00:00 2001 From: CoreByte <62032154+CoreBytee@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:13:05 +0200 Subject: [PATCH 03/11] Update mod.rs --- packages/embdr/src/sources/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/embdr/src/sources/mod.rs b/packages/embdr/src/sources/mod.rs index 9dd1921..dd85882 100644 --- a/packages/embdr/src/sources/mod.rs +++ b/packages/embdr/src/sources/mod.rs @@ -4,6 +4,7 @@ use crate::sources::instagram::InstagramSource; mod instagram; +#[allow(dead_code)] #[async_trait::async_trait] pub trait Source { /// Returns the name of the source. From 981fcd8f247f6dda36c57f48d6d1f1dad23fbc02 Mon Sep 17 00:00:00 2001 From: CoreByte <62032154+CoreBytee@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:15:10 +0200 Subject: [PATCH 04/11] add workflows --- .github/workflows/build-binary.yml | 28 +++++++++++++++++++ .github/workflows/publish-release.yml | 39 +++++++++++++++++++++++++++ .github/workflows/run-checks.yml | 35 ++++++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 .github/workflows/build-binary.yml create mode 100644 .github/workflows/publish-release.yml create mode 100644 .github/workflows/run-checks.yml diff --git a/.github/workflows/build-binary.yml b/.github/workflows/build-binary.yml new file mode 100644 index 0000000..b52192e --- /dev/null +++ b/.github/workflows/build-binary.yml @@ -0,0 +1,28 @@ +name: Build binary +on: + workflow_dispatch: + push: + branches: + - rust +jobs: + build-binary: + name: Build binary + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v6 + + # Setup + - uses: oven-sh/setup-bun@v2 + # - uses: Swatinem/rust-cache@v2 + - run: bun install + + - name: Build binary + run: cargo build --verbose + + - run: cp target/debug/embdr ./embdr-arm64 + + - uses: actions/upload-artifact@v7 + with: + name: embdr-arm64 + path: ./embdr-arm64 + archive: false diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 0000000..31c90c0 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,39 @@ +name: Publish release + +permissions: + contents: write + +on: + push: + tags: + - "v*" +jobs: + publish-release: + name: Publish release + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v6 + + # Setup + - uses: oven-sh/setup-bun@v2 + # - uses: Swatinem/rust-cache@v2 + - run: bun install + + - name: Build binary + run: cargo build --release --verbose + + - run: cp target/release/embdr ./embdr-arm64 + + - name: Publish release + uses: softprops/action-gh-release@v3 + with: + files: ./embdr-arm64 + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/upload-artifact@v7 + with: + name: embdr-arm64 + path: ./embdr-arm64 + archive: false diff --git a/.github/workflows/run-checks.yml b/.github/workflows/run-checks.yml new file mode 100644 index 0000000..cb38005 --- /dev/null +++ b/.github/workflows/run-checks.yml @@ -0,0 +1,35 @@ +name: Run checks +on: + workflow_dispatch: + push: + branches: + - rust + pull_request: + branches: + - rust +jobs: + cargo-check: + name: Run cargo checks + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Install bun + uses: oven-sh/setup-bun@v2 + - name: Install bun dependencies + run: bun install + - name: Run cargo checks + run: cargo check + + cargo-test: + name: Run cargo tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Install bun + uses: oven-sh/setup-bun@v2 + - name: Install bun dependencies + run: bun install + - name: Run cargo tests + run: cargo test From 060df76bdf97846664b8e0dea838c163c643030d Mon Sep 17 00:00:00 2001 From: CoreByte <62032154+CoreBytee@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:24:46 +0200 Subject: [PATCH 05/11] Add proxy support for Instagram API requests --- Cargo.lock | 1 + README.md | 60 +++++++++++++++++-------- packages/embdr/Cargo.toml | 3 ++ packages/embdr/src/sources/instagram.rs | 15 ++++++- 4 files changed, 60 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9d816a6..3777a5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,6 +196,7 @@ dependencies = [ "async-trait", "dotenvy", "instagram-client", + "reqwest", "rustls", "tokio", "tracing", diff --git a/README.md b/README.md index d47ab01..b5a8d8f 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,23 @@ # EMBDR -EMBDR is a Discord bot and webserver that provides better embeds for Instagram links. When a user posts an Instagram link in a Discord server, EMBDR detects it and replies with a custom embed that displays a playable video and improved metadata. +EMBDR is a Discord bot written in Rust that provides better embeds for Instagram links. When a user posts an Instagram link in a Discord server, EMBDR detects it and replies with a custom embed that displays a playable video and improved metadata. ## Features - Detects Instagram links in Discord messages -- Replies with a custom embed containing a playable video and post information -- Provides a web endpoint for rich embeds and video redirection +- Replies with a custom embed containing a playable video, author information, and post statistics +- Extensible source system for adding support for additional platforms + +## Project Structure + +EMBDR is organised as a Cargo workspace with two packages: + +- **`packages/embdr`** — The main Discord bot. Connects to the Discord gateway, listens for messages, and replies with rich embeds. +- **`packages/instagram-client`** — A standalone Rust library for interacting with Instagram's GraphQL API. Handles CSRF token management, shortcode extraction, and media fetching. + +## Prerequisites + +- [Rust](https://www.rust-lang.org/tools/install) (edition 2024) ## Installation @@ -16,29 +27,31 @@ EMBDR is a Discord bot and webserver that provides better embeds for Instagram l cd EMBDR ``` -2. **Install dependencies:** +2. **Build the project:** ```sh - bun install + cargo build ``` -3. **Configure environment variables:** - - Copy `.env.example` to `.env` (if available) or create a `.env` file with the following variables: - ``` - DISCORD_TOKEN=your_discord_bot_token - WEBSERVER_PORT=3000 - WEBSERVER_URL=https://your.domain.com - ``` - - Replace the values as needed. +## Configuration + +Create a `.env` file in the project root with the following variables: + +``` +DISCORD_TOKEN=your_discord_bot_token +PROXY_URL=your_proxy_url +``` + +The `PROXY_URL` variable is optional. When set, all Instagram API requests will be routed through the specified proxy. -## Running the Bot +## Running -Start the bot and webserver with: +Start the bot with: ```sh -bun run start +cargo run ``` -The bot will log in to Discord and start listening for messages. The webserver will run on the port specified in your `.env`. +The bot will log in to Discord and start listening for messages. ## Contributing @@ -53,4 +66,15 @@ Please follow the existing code style and include clear commit messages. ## License -This project is licensed under the MIT License. See [LICENSE](LICENSE) for details. \ No newline at end of file +This project is licensed under the MIT License. See [LICENSE](LICENSE) for details. + +## Publishing a New Release + +There is a workflow in this repository to publish a new release. To activate it, create a new tag and push it to GitHub: + +```sh +git tag v1.0.0-beta.5 +git push origin --tags +``` + +This will trigger the publish release workflow, which builds the binary and creates a GitHub release with the compiled artifact. diff --git a/packages/embdr/Cargo.toml b/packages/embdr/Cargo.toml index 0f5c1e3..b24c85f 100644 --- a/packages/embdr/Cargo.toml +++ b/packages/embdr/Cargo.toml @@ -25,6 +25,9 @@ twilight-util = { version = "0.17.0", features = ["builder"] } # TLS crypto provider for rustls (used by twilight) rustls = { version = "0.23", default-features = false, features = ["ring", "logging", "std", "tls12"] } +# HTTP (for proxy support) +reqwest = { version = "0.12", features = ["cookies"] } + # Other async-trait = "0.1.92" url = "2.5.8" diff --git a/packages/embdr/src/sources/instagram.rs b/packages/embdr/src/sources/instagram.rs index be95a79..422c396 100644 --- a/packages/embdr/src/sources/instagram.rs +++ b/packages/embdr/src/sources/instagram.rs @@ -1,3 +1,4 @@ +use reqwest::Client; use url::Url; use instagram_client::InstagramClient; @@ -10,7 +11,19 @@ pub struct InstagramSource { impl InstagramSource { pub fn new() -> Self { - let instagram_client = InstagramClient::new().expect("Failed to create instagram client"); + let instagram_client = match std::env::var("PROXY_URL") { + Ok(proxy_url) => { + let proxy = reqwest::Proxy::all(&proxy_url) + .expect("Failed to parse PROXY_URL"); + let http_client = Client::builder() + .cookie_store(true) + .proxy(proxy) + .build() + .expect("Failed to build HTTP client with proxy"); + InstagramClient::new_with_http(http_client) + } + Err(_) => InstagramClient::new().expect("Failed to create instagram client"), + }; Self { instagram_client } } } From 3334b743cc80f800628977bc4d0ce1617a448a4c Mon Sep 17 00:00:00 2001 From: CoreByte <62032154+CoreBytee@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:24:49 +0200 Subject: [PATCH 06/11] Update README.md --- README.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index b5a8d8f..27da210 100644 --- a/README.md +++ b/README.md @@ -22,15 +22,16 @@ EMBDR is organised as a Cargo workspace with two packages: ## Installation 1. **Clone the repository:** - ```sh - git clone https://github.com/CoreBytee/EMBDR.git - cd EMBDR - ``` + + ```sh + git clone https://github.com/CoreBytee/EMBDR.git + cd EMBDR + ``` 2. **Build the project:** - ```sh - cargo build - ``` + ```sh + cargo build + ``` ## Configuration @@ -64,10 +65,6 @@ Contributions are welcome! To contribute: Please follow the existing code style and include clear commit messages. -## License - -This project is licensed under the MIT License. See [LICENSE](LICENSE) for details. - ## Publishing a New Release There is a workflow in this repository to publish a new release. To activate it, create a new tag and push it to GitHub: @@ -78,3 +75,7 @@ git push origin --tags ``` This will trigger the publish release workflow, which builds the binary and creates a GitHub release with the compiled artifact. + +## License + +This project is licensed under the MIT License. See [LICENSE](LICENSE) for details. From ea40886d81344635b26fdc4c7b12d42fcc0f4caa Mon Sep 17 00:00:00 2001 From: CoreByte <62032154+CoreBytee@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:28:35 +0200 Subject: [PATCH 07/11] remove bun from actions --- .github/workflows/build-binary.yml | 5 ----- .github/workflows/publish-release.yml | 5 ----- .github/workflows/run-checks.yml | 4 ---- 3 files changed, 14 deletions(-) diff --git a/.github/workflows/build-binary.yml b/.github/workflows/build-binary.yml index b52192e..8576c00 100644 --- a/.github/workflows/build-binary.yml +++ b/.github/workflows/build-binary.yml @@ -11,11 +11,6 @@ jobs: steps: - uses: actions/checkout@v6 - # Setup - - uses: oven-sh/setup-bun@v2 - # - uses: Swatinem/rust-cache@v2 - - run: bun install - - name: Build binary run: cargo build --verbose diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 31c90c0..6f7e173 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -14,11 +14,6 @@ jobs: steps: - uses: actions/checkout@v6 - # Setup - - uses: oven-sh/setup-bun@v2 - # - uses: Swatinem/rust-cache@v2 - - run: bun install - - name: Build binary run: cargo build --release --verbose diff --git a/.github/workflows/run-checks.yml b/.github/workflows/run-checks.yml index cb38005..e05dfc6 100644 --- a/.github/workflows/run-checks.yml +++ b/.github/workflows/run-checks.yml @@ -14,8 +14,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 - - name: Install bun - uses: oven-sh/setup-bun@v2 - name: Install bun dependencies run: bun install - name: Run cargo checks @@ -27,8 +25,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 - - name: Install bun - uses: oven-sh/setup-bun@v2 - name: Install bun dependencies run: bun install - name: Run cargo tests From 803100b7b7022aa58eb1515c5b646779fce4adf3 Mon Sep 17 00:00:00 2001 From: CoreByte <62032154+CoreBytee@users.noreply.github.com> Date: Wed, 2 Sep 2026 22:22:14 +0200 Subject: [PATCH 08/11] Add TikTok embed support --- Cargo.lock | 323 ++++++++++++++++++++++++ Cargo.toml | 2 + packages/embdr/Cargo.toml | 1 + packages/embdr/src/main.rs | 5 + packages/embdr/src/sources/instagram.rs | 8 +- packages/embdr/src/sources/mod.rs | 4 +- packages/embdr/src/sources/tiktok.rs | 89 +++++++ packages/instagram-client/src/lib.rs | 8 +- packages/tiktok-client/Cargo.toml | 23 ++ packages/tiktok-client/src/html.rs | 59 +++++ packages/tiktok-client/src/lib.rs | 128 ++++++++++ 11 files changed, 639 insertions(+), 11 deletions(-) create mode 100644 packages/embdr/src/sources/tiktok.rs create mode 100644 packages/tiktok-client/Cargo.toml create mode 100644 packages/tiktok-client/src/html.rs create mode 100644 packages/tiktok-client/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 3777a5c..d497a75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,12 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.12.1" @@ -157,12 +163,46 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cssparser" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c66d1cd8ed61bf80b38432613a7a2f09401ab8d0501110655f8b341484a3e3" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.119", +] + [[package]] name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "displaydoc" version = "0.2.7" @@ -189,6 +229,27 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "ego-tree" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2972feb8dffe7bc8c5463b1dacda1b0dfbed3710e50f977d965429692d74cd8" + [[package]] name = "embdr" version = "0.1.0" @@ -198,6 +259,7 @@ dependencies = [ "instagram-client", "reqwest", "rustls", + "tiktok-client", "tokio", "tracing", "tracing-subscriber", @@ -275,6 +337,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures-channel" version = "0.3.34" @@ -314,6 +386,24 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "getopts" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" +dependencies = [ + "unicode-width", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -367,6 +457,18 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +[[package]] +name = "html5ever" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +dependencies = [ + "log", + "mac", + "markup5ever", + "match_token", +] + [[package]] name = "http" version = "1.5.0" @@ -739,6 +841,37 @@ version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "markup5ever" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +dependencies = [ + "log", + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "memchr" version = "2.8.3" @@ -779,6 +912,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -890,6 +1029,58 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -917,6 +1108,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "proc-macro2" version = "1.0.107" @@ -957,6 +1154,21 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "rand" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "redox_syscall" version = "0.5.18" @@ -1154,6 +1366,21 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scraper" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc3d051b884f40e309de6c149734eab57aa8cc1347992710dc80bcc1c2194c15" +dependencies = [ + "cssparser", + "ego-tree", + "getopts", + "html5ever", + "precomputed-hash", + "selectors", + "tendril", +] + [[package]] name = "security-framework" version = "3.7.0" @@ -1177,6 +1404,25 @@ dependencies = [ "libc", ] +[[package]] +name = "selectors" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd568a4c9bb598e291a08244a5c1f5a8a6650bee243b5b0f8dbb3d9cc1d87fe8" +dependencies = [ + "bitflags", + "cssparser", + "derive_more", + "fxhash", + "log", + "new_debug_unreachable", + "phf", + "phf_codegen", + "precomputed-hash", + "servo_arc", + "smallvec", +] + [[package]] name = "semver" version = "1.0.28" @@ -1259,6 +1505,15 @@ dependencies = [ "serde", ] +[[package]] +name = "servo_arc" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "sha1_smol" version = "1.0.1" @@ -1306,6 +1561,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + [[package]] name = "slab" version = "0.4.12" @@ -1334,6 +1595,31 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + [[package]] name = "subtle" version = "2.6.1" @@ -1416,6 +1702,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "thiserror" version = "2.0.20" @@ -1445,6 +1742,20 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tiktok-client" +version = "0.1.0" +dependencies = [ + "reqwest", + "scraper", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "url", +] + [[package]] name = "time" version = "0.3.55" @@ -1783,6 +2094,12 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "untrusted" version = "0.9.0" @@ -1807,6 +2124,12 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" diff --git a/Cargo.toml b/Cargo.toml index a8cda64..9b464d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,10 @@ resolver = "3" members = [ "packages/embdr", "packages/instagram-client", + "packages/tiktok-client", ] default-members = ["packages/embdr"] [workspace.dependencies] instagram-client = { path = "packages/instagram-client" } +tiktok-client = { path = "packages/tiktok-client" } diff --git a/packages/embdr/Cargo.toml b/packages/embdr/Cargo.toml index b24c85f..94a74eb 100644 --- a/packages/embdr/Cargo.toml +++ b/packages/embdr/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] instagram-client.workspace = true +tiktok-client.workspace = true # Environment dotenvy = "0.15.7" diff --git a/packages/embdr/src/main.rs b/packages/embdr/src/main.rs index f292343..7b558a1 100644 --- a/packages/embdr/src/main.rs +++ b/packages/embdr/src/main.rs @@ -152,6 +152,11 @@ impl EMBDR { ); } + http.update_message(message.channel_id, message.id) + .flags(MessageFlags::SUPPRESS_EMBEDS) + .await + .ok(); + let result = http .create_message(message.channel_id) .reply(message.id) diff --git a/packages/embdr/src/sources/instagram.rs b/packages/embdr/src/sources/instagram.rs index 422c396..bf3b9b1 100644 --- a/packages/embdr/src/sources/instagram.rs +++ b/packages/embdr/src/sources/instagram.rs @@ -12,9 +12,9 @@ pub struct InstagramSource { impl InstagramSource { pub fn new() -> Self { let instagram_client = match std::env::var("PROXY_URL") { - Ok(proxy_url) => { - let proxy = reqwest::Proxy::all(&proxy_url) - .expect("Failed to parse PROXY_URL"); + Ok(proxy_url) if !proxy_url.is_empty() => { + println!("Using proxy: {}", proxy_url); + let proxy = reqwest::Proxy::all(&proxy_url).expect("Failed to parse PROXY_URL"); let http_client = Client::builder() .cookie_store(true) .proxy(proxy) @@ -22,7 +22,7 @@ impl InstagramSource { .expect("Failed to build HTTP client with proxy"); InstagramClient::new_with_http(http_client) } - Err(_) => InstagramClient::new().expect("Failed to create instagram client"), + _ => InstagramClient::new().expect("Failed to create instagram client"), }; Self { instagram_client } } diff --git a/packages/embdr/src/sources/mod.rs b/packages/embdr/src/sources/mod.rs index dd85882..52ec974 100644 --- a/packages/embdr/src/sources/mod.rs +++ b/packages/embdr/src/sources/mod.rs @@ -1,8 +1,10 @@ use url::Url; use crate::sources::instagram::InstagramSource; +use crate::sources::tiktok::TiktokSource; mod instagram; +mod tiktok; #[allow(dead_code)] #[async_trait::async_trait] @@ -30,7 +32,7 @@ pub trait Source { } pub fn get_sources() -> Sources { - vec![Box::new(InstagramSource::new())] + vec![Box::new(InstagramSource::new()), Box::new(TiktokSource::new())] } pub type Sources = Vec>; diff --git a/packages/embdr/src/sources/tiktok.rs b/packages/embdr/src/sources/tiktok.rs new file mode 100644 index 0000000..f0b2761 --- /dev/null +++ b/packages/embdr/src/sources/tiktok.rs @@ -0,0 +1,89 @@ +use reqwest::Client; +use url::Url; + +use tiktok_client::TiktokClient; + +use crate::sources::{MediaAuthor, MediaData, MediaItem, Source}; + +pub struct TiktokSource { + tiktok_client: TiktokClient, +} + +impl TiktokSource { + pub fn new() -> Self { + let tiktok_client = match std::env::var("PROXY_URL") { + Ok(proxy_url) if !proxy_url.is_empty() => { + let proxy = reqwest::Proxy::all(&proxy_url).expect("Failed to parse PROXY_URL"); + let http_client = Client::builder() + .cookie_store(true) + .proxy(proxy) + .build() + .expect("Failed to build HTTP client with proxy"); + TiktokClient::new_with_http_client(http_client) + } + _ => TiktokClient::new().expect("Failed to create tiktok client"), + }; + Self { tiktok_client } + } +} + +#[async_trait::async_trait] +impl Source for TiktokSource { + fn name(&self) -> String { + "TikTok".to_string() + } + + fn id(&self) -> String { + "tiktok".to_string() + } + + fn short_id(&self) -> String { + "tt".to_string() + } + + fn color(&self) -> u32 { + 0xff004f + } + + fn predicate(&self, url: &Url) -> bool { + let Some(hostname) = url.host_str() else { + return false; + }; + + let hostname_matches = + hostname == "www.tiktok.com" || hostname == "tiktok.com" || hostname == "vm.tiktok.com"; + + return hostname_matches; + } + + async fn extract_media( + &self, + url: &Url, + ) -> Result> { + let tiktok_post = self.tiktok_client.fetch_from_url(url.as_str()).await?; + println!("TikTok post: {:#?}", tiktok_post); + + let description = if tiktok_post.content.is_empty() { + None + } else { + Some(tiktok_post.content.clone()) + }; + + Ok(MediaData { + id: tiktok_post.id.clone(), + author: MediaAuthor { + name: tiktok_post.account.display_name.clone(), + url: format!("https://www.tiktok.com/@{}", tiktok_post.account.username), + }, + description, + items: tiktok_post + .media_attachments + .into_iter() + .map(|item| MediaItem { + url: item.url.clone(), + }) + .collect(), + properties: vec![], + }) + } +} diff --git a/packages/instagram-client/src/lib.rs b/packages/instagram-client/src/lib.rs index 99a209d..336bf05 100644 --- a/packages/instagram-client/src/lib.rs +++ b/packages/instagram-client/src/lib.rs @@ -235,7 +235,7 @@ const DOC_ID_FETCH_PLAY_COUNT: &str = "27234427476213202"; const BASE_URL: &str = "https://www.instagram.com"; const GRAPHQL_URL: &str = "https://www.instagram.com/graphql/query"; -const DEFAULT_USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36"; +const DEFAULT_USER_AGENT: &str = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.3"; const MAX_RETRIES: u32 = 3; @@ -557,11 +557,7 @@ impl InstagramClient { /// /// `user_id` can be a numeric PK (as `&str`) or username — the endpoint /// expects the numeric user PK though. - pub async fn fetch_play_count( - &self, - user_id: &str, - shortcode: &str, - ) -> Result> { + pub async fn fetch_play_count(&self, user_id: &str, shortcode: &str) -> Result> { let csrf = self.get_csrf_token().await?; let variables = json!({ diff --git a/packages/tiktok-client/Cargo.toml b/packages/tiktok-client/Cargo.toml new file mode 100644 index 0000000..1b83036 --- /dev/null +++ b/packages/tiktok-client/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "tiktok-client" +version = "0.1.0" +edition = "2024" + +[dependencies] +# Async +tokio = { version = "1.53.1", features = ["full"] } + +# Logging +tracing = "0.1" + +# HTTP +reqwest = { version = "0.12", features = ["cookies", "json"] } + +# Other +thiserror = "2" +url = "2.5.8" +scraper = "0.22" + +# JSON +serde = { version = "1", features = ["derive"] } +serde_json = "1" diff --git a/packages/tiktok-client/src/html.rs b/packages/tiktok-client/src/html.rs new file mode 100644 index 0000000..d3aa460 --- /dev/null +++ b/packages/tiktok-client/src/html.rs @@ -0,0 +1,59 @@ +use scraper::Html; + +pub fn html_to_markdown(html: &str) -> String { + let document = Html::parse_document(html); + let mut result = String::new(); + let root = document.root_element(); + render_node(&root, &mut result); + result +} + +fn render_node(node: &scraper::ElementRef, output: &mut String) { + for child in node.children() { + match child.value() { + scraper::Node::Text(text) => { + output.push_str(text); + } + scraper::Node::Element(element) => { + match element.name() { + "b" => { + output.push_str("**"); + if let Some(element_ref) = scraper::ElementRef::wrap(child) { + render_node(&element_ref, output); + } + output.push_str("**"); + } + "br" => { + output.push('\n'); + } + "a" => { + if let Some(element_ref) = scraper::ElementRef::wrap(child) { + render_node(&element_ref, output); + } + } + _ => { + if let Some(element_ref) = scraper::ElementRef::wrap(child) { + render_node(&element_ref, output); + } + } + } + } + _ => {} + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn html_to_markdown_conversion() { + let html = "Better than Thanos??
Marvel somehow never miss when it comes to mainstream villains #ultron #villianspeach

❤️ 537 💬 3 🔁 13"; + let markdown = html_to_markdown(html); + assert_eq!( + markdown, + "**Better than Thanos??**\nMarvel somehow never miss when it comes to mainstream villains #ultron #villianspeach\n\n**❤️ 537 💬 3 🔁 13**" + ); + } +} diff --git a/packages/tiktok-client/src/lib.rs b/packages/tiktok-client/src/lib.rs new file mode 100644 index 0000000..585b4ad --- /dev/null +++ b/packages/tiktok-client/src/lib.rs @@ -0,0 +1,128 @@ +use serde::Deserialize; +use thiserror::Error; +use url::Url; + +mod html; + +const DEFAULT_USER_AGENT: &str = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.3"; + +#[derive(Debug, Error)] +pub enum TiktokError { + #[error("HTTP request failed: {0}")] + Reqwest(#[from] reqwest::Error), + + #[error("JSON parse error: {0}")] + Json(#[from] serde_json::Error), + + #[error("URL parse error: {0}")] + ShareResolveError(String), +} + +pub type Result = std::result::Result; + +#[derive(Debug, Clone, Deserialize)] +pub struct TiktokPost { + pub id: String, + pub url: String, + pub created_at: String, + pub content: String, + pub account: TiktokUser, + pub media_attachments: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct TiktokUser { + pub id: String, + pub display_name: String, + pub username: String, + pub avatar: String, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct TiktokMedia { + pub id: String, + #[serde(rename = "type")] + pub media_type: String, + pub url: String, + pub preview_url: String, +} + +pub struct TiktokClient { + http_client: reqwest::Client, +} + +impl TiktokClient { + pub fn new() -> Result { + let http_client = reqwest::Client::builder() + .user_agent(DEFAULT_USER_AGENT) + .build()?; + return Ok(TiktokClient::new_with_http_client(http_client)); + } + + pub fn new_with_http_client(http_client: reqwest::Client) -> Self { + TiktokClient { http_client } + } + + /// Resolves a TikTok share URL to the original video URL. + pub async fn resolve_share(&self, url: &str) -> Result { + let url = Url::parse(url) + .map_err(|_| TiktokError::ShareResolveError("Failed to parse url".into()))?; + + let slash_count = url.path().chars().filter(|&c| c == '/').count(); + let max_slashes = if url.path().ends_with('/') { 2 } else { 1 }; + + if url.domain() != Some("vm.tiktok.com") || slash_count > max_slashes { + return Ok(url.to_string()); + } + + let response = self.http_client.get(url).send().await?; + let mut final_url = response.url().clone(); + final_url.set_query(None); + return Ok(final_url.to_string()); + } + + pub async fn fetch_from_url(&self, url: &str) -> Result { + let parsed_url = Url::parse(url) + .map_err(|_| TiktokError::ShareResolveError("Failed to parse url".into()))?; + + let post_id = parsed_url + .path_segments() + .and_then(|segments| segments.last()) + .ok_or_else(|| TiktokError::ShareResolveError("Could not extract post ID from URL".into()))?; + + let api_url = format!( + "https://offload.tnktok.com/users/username/statuses/{}", + post_id + ); + + let response = self.http_client.get(&api_url).send().await?; + let mut post: TiktokPost = response.json().await?; + post.content = html::html_to_markdown(&post.content); + Ok(post) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn resolve_share() { + let client = TiktokClient::new().unwrap(); + let url = "https://vm.tiktok.com/ZGdQJxgjN/"; + let resolved_url = client.resolve_share(url).await.unwrap(); + assert_eq!( + resolved_url, + "https://www.tiktok.com/@rijschoolehvlegend/photo/7680933567869816097" + ); + } + + #[tokio::test] + async fn fetch_from_url() { + let client = TiktokClient::new().unwrap(); + let url = "https://tiktok.com/@kieth_saint/video/7680002452866747680"; + let post = client.fetch_from_url(url).await.unwrap(); + assert_eq!(post.id, "7680002452866747680"); + assert_eq!(post.account.username, "kieth_saint"); + } +} From c1a3dc57c234ba85ebb9ba16533ad8f15f495756 Mon Sep 17 00:00:00 2001 From: CoreByte <62032154+CoreBytee@users.noreply.github.com> Date: Wed, 2 Sep 2026 22:24:04 +0200 Subject: [PATCH 09/11] Update instagram.rs --- packages/embdr/src/sources/instagram.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/embdr/src/sources/instagram.rs b/packages/embdr/src/sources/instagram.rs index bf3b9b1..5ddd35c 100644 --- a/packages/embdr/src/sources/instagram.rs +++ b/packages/embdr/src/sources/instagram.rs @@ -13,7 +13,6 @@ impl InstagramSource { pub fn new() -> Self { let instagram_client = match std::env::var("PROXY_URL") { Ok(proxy_url) if !proxy_url.is_empty() => { - println!("Using proxy: {}", proxy_url); let proxy = reqwest::Proxy::all(&proxy_url).expect("Failed to parse PROXY_URL"); let http_client = Client::builder() .cookie_store(true) From d81ada0e5017680d2a7d0b4a20c3b21a957c209f Mon Sep 17 00:00:00 2001 From: CoreByte <62032154+CoreBytee@users.noreply.github.com> Date: Wed, 2 Sep 2026 22:30:03 +0200 Subject: [PATCH 10/11] Resolve share URL before extracting post ID --- packages/tiktok-client/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/tiktok-client/src/lib.rs b/packages/tiktok-client/src/lib.rs index 585b4ad..ee6b8b2 100644 --- a/packages/tiktok-client/src/lib.rs +++ b/packages/tiktok-client/src/lib.rs @@ -82,13 +82,16 @@ impl TiktokClient { } pub async fn fetch_from_url(&self, url: &str) -> Result { - let parsed_url = Url::parse(url) + let resolved_url = self.resolve_share(url).await?; + let parsed_url = Url::parse(resolved_url.as_str()) .map_err(|_| TiktokError::ShareResolveError("Failed to parse url".into()))?; let post_id = parsed_url .path_segments() .and_then(|segments| segments.last()) - .ok_or_else(|| TiktokError::ShareResolveError("Could not extract post ID from URL".into()))?; + .ok_or_else(|| { + TiktokError::ShareResolveError("Could not extract post ID from URL".into()) + })?; let api_url = format!( "https://offload.tnktok.com/users/username/statuses/{}", From 5bc6a3f71392f7cc7f31ffbf3b14b3203b99f1a1 Mon Sep 17 00:00:00 2001 From: CoreByte <62032154+CoreBytee@users.noreply.github.com> Date: Tue, 8 Sep 2026 13:35:49 +0200 Subject: [PATCH 11/11] Remove branch filter from GitHub workflows --- .github/workflows/build-binary.yml | 2 -- .github/workflows/run-checks.yml | 4 ---- 2 files changed, 6 deletions(-) diff --git a/.github/workflows/build-binary.yml b/.github/workflows/build-binary.yml index 8576c00..c7718f2 100644 --- a/.github/workflows/build-binary.yml +++ b/.github/workflows/build-binary.yml @@ -2,8 +2,6 @@ name: Build binary on: workflow_dispatch: push: - branches: - - rust jobs: build-binary: name: Build binary diff --git a/.github/workflows/run-checks.yml b/.github/workflows/run-checks.yml index e05dfc6..d7175ea 100644 --- a/.github/workflows/run-checks.yml +++ b/.github/workflows/run-checks.yml @@ -2,11 +2,7 @@ name: Run checks on: workflow_dispatch: push: - branches: - - rust pull_request: - branches: - - rust jobs: cargo-check: name: Run cargo checks