diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..436c448
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,134 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+# Clear from any builds
+**/*.exe
+out.js
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bbfe773
--- /dev/null
+++ b/README.md
@@ -0,0 +1,38 @@
+
+
+
+# 💀 GIF Validator
+GIF Validator is a utility tool that checks your favorite gifs on Discord and identifies any that are no longer available. This ensures your gif collection remains up-to-date and clean from broken links.
+
+## ❗ Requirements
+If you wish to use original JS files (for example if you don't trust the EXE file) you will need:
+- [Node.js](https://nodejs.org/en/) [v21.6.1 or higher]
+- [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) (or any other package manager like yarn/pnpm)
+
+## ⚙ Installation
+
+To install Gif Validator, follow these steps:
+
+1. Clone the repository to your local machine.
+2. Navigate to the cloned directory.
+3. Run `npm install` to install all the necessary dependencies.
+
+## ⌨ Usage
+
+To start using Gif Validator:
+
+1. Ensure you have set up your Discord API token in the configuration file.
+2. Run `npm start` to execute the script.
+
+The script will validate your gifs and remove any that are no longer available leaving you with a clean collection!
+
+## 📖 How to Build
+
+❗ OFFICIAL SUPPORT IS AVAILABLE ONLY FOR WINDOWS
+If you have any major Linux Distro (Like Ubuntu, Debian etc.), please make a pull request with needed dependencies, scripts and configuration files for build to work on them!
+
+Building is fairly simple, just use the following command:
+```bash
+npm run build-win
+```
+...or open the `build.bat` file in the root of the repository.
\ No newline at end of file
diff --git a/build.bat b/build.bat
new file mode 100644
index 0000000..87e9492
--- /dev/null
+++ b/build.bat
@@ -0,0 +1,20 @@
+@echo off
+WHERE node >nul 2>nul
+if %ERRORLEVEL% NEQ 0 (
+ echo You must install node.js to build this app!
+) else (
+ echo It is recommended to install signtool to remove corrupted signature warning!
+ echo https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/
+
+ .\node_modules\.bin\esbuild index.js --bundle --platform=node --outfile=out.js
+
+ node --experimental-sea-config sea-config.json
+ node -e "require('fs').copyFileSync(process.execPath, 'gif-validator.exe')"
+
+ if NOT exist "C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\signtool.exe" ( echo Signtool not found. Ignoring... ) else ( "C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\signtool.exe" remove /s gif-validator.exe )
+
+ npx postject gif-validator.exe NODE_SEA_BLOB sea.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
+
+ if exist sea.blob ( del sea.blob )
+ if exist out.js ( out.js )
+)
\ No newline at end of file
diff --git a/classes/GifClient.js b/classes/GifClient.js
new file mode 100644
index 0000000..ddf0f9f
--- /dev/null
+++ b/classes/GifClient.js
@@ -0,0 +1,85 @@
+const { Helper } = require("./Helper.js")
+const { FrecencyUserSettings } = require("discord-protos")
+const { Axios } = require("axios")
+
+/** Main client class for this project */
+export class GifClient {
+ constructor(token) {
+ if(!token) throw new Error("No token provided")
+
+ this.axios = new Axios({
+ headers: {
+ "Authorization": token,
+ "accept": "*/*",
+ "Content-Type": "application/json",
+ "x-discord-locale": "en-US",
+ "accept-language": "en-US;q=0.9",
+ "Referer": "https://discord.com/channels/@me",
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) discord/1.0.9034 Chrome/108.0.5359.215 Electron/22.3.26 Safari/537.36",
+ "x-discord-timezone": Intl.DateTimeFormat().resolvedOptions().timeZone,
+ "x-super-properties": "eyJvcyI6IldpbmRvd3MiLCJicm93c2VyIjoiRGlzY29yZCBDbGllbnQiLCJyZWxlYXNlX2NoYW5uZWwiOiJzdGFibGUiLCJjbGllbnRfdmVyc2lvbiI6IjEuMC45MDM0Iiwib3NfdmVyc2lvbiI6IjEwLjAuMTkwNDUiLCJvc19hcmNoIjoieDY0IiwiYXBwX2FyY2giOiJpYTMyIiwic3lzdGVtX2xvY2FsZSI6ImVuLVVTIiwiYnJvd3Nlcl91c2VyX2FnZW50IjoiTW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV09XNjQpIEFwcGxlV2ViS2l0LzUzNy4zNiAoS0hUTUwsIGxpa2UgR2Vja28pIGRpc2NvcmQvMS4wLjkwMzQgQ2hyb21lLzEwOC4wLjUzNTkuMjE1IEVsZWN0cm9uLzIyLjMuMjYgU2FmYXJpLzUzNy4zNiIsImJyb3dzZXJfdmVyc2lvbiI6IjIyLjMuMjYiLCJjbGllbnRfYnVpbGRfbnVtYmVyIjoyNzEyMTYsIm5hdGl2ZV9idWlsZF9udW1iZXIiOjQ0MTQyLCJjbGllbnRfZXZlbnRfc291cmNlIjpudWxsfQ=="
+ },
+ transformResponse: (data) => Helper.tryParseJSON(data, true),
+ transformRequest: (data) => JSON.stringify(data)
+ })
+ }
+
+ _handleError(response) {
+ const json = Helper.tryParseJSON(response);
+ if(json) {
+ if(json["message"]) {
+ if(json["message"] === "401: Unauthorized" && json["code"] === 0) throw new Error("Invalid token!")
+ if(!Helper.statusesOK.some(s => json["message"].startsWith(s))) throw new Error(json)
+ }
+ return true
+ }
+ return true
+ }
+
+ /**
+ * Grabs list of gifs and returns them
+ * @returns {Promise<{[key: string]: import("discord-protos").FrecencyUserSettings_FavoriteGIF}>} Gifs
+ */
+ async getGifs() {
+ const {data} = await this.axios.get(Helper.PROTO_URL(2))
+ this._handleError(data)
+
+ const {settings: encodedSettings} = data
+ const decodedSettings = FrecencyUserSettings.fromBase64(encodedSettings)
+
+ return decodedSettings["favoriteGifs"]["gifs"]
+ }
+
+ /**
+ * Validates gifs and removes unavailable ones.
+ * @param {{[key: string]: import("discord-protos").FrecencyUserSettings_FavoriteGIF}} gifs Gifs
+ * @returns {Promise<{[key: string]: import("discord-protos").FrecencyUserSettings_FavoriteGIF}>} Valid Gifs
+ */
+ async validateGifs(gifs) {
+ let newGifs = {...gifs}
+ for (let key of Object.keys(gifs)) {
+ const resp = await this.axios.head(gifs[key].src)
+ if(!Helper.isOK(resp.status)) delete newGifs[key]
+ }
+ return newGifs
+ }
+
+ /**
+ * Saves gifs and returns if it succedded
+ * @param {{[key: string]: import("discord-protos").FrecencyUserSettings_FavoriteGIF}} gifs Gifs to save
+ * @return {Promise} Was save successful?
+ */
+ async saveGifs(gifs) {
+ const decodedSettings = {
+ favoriteGifs: {
+ gifs: gifs
+ }
+ }
+ const encodedSettings = FrecencyUserSettings.toBase64(decodedSettings);
+
+ const {status, data} = await this.axios.patch(Helper.PROTO_URL(2), {settings: encodedSettings})
+ this._handleError(data)
+
+ return Helper.isOK(status)
+ }
+}
\ No newline at end of file
diff --git a/classes/Helper.js b/classes/Helper.js
new file mode 100644
index 0000000..708fa45
--- /dev/null
+++ b/classes/Helper.js
@@ -0,0 +1,35 @@
+/** Helper methods for project */
+export class Helper {
+ /** Status codes which are taken as OK */
+ static statusesOK = [
+ ...new Array(8).fill(0).map((_, i) => 200 + i),
+ 226
+ ]
+
+ /**
+ * Generates url for proto manipulation
+ * @param {1 | 2 | 3} proto_type Type of proto
+ * @returns {string} URL
+ */
+ static PROTO_URL = (proto_type) => "https://discord.com/api/v9/users/@me/settings-proto/".concat(proto_type)
+
+ /**
+ * Tries to parse a JSON string to object
+ * @param {string} maybeJson String that might be a JSON
+ * @param {boolean} shouldReturnBack Should function return string on error?
+ * @returns {object|null} Parsed object if valid JSON | null if invalid
+ */
+ static tryParseJSON(maybeJson = "", shouldReturnBack = false){
+ try {return JSON.parse(maybeJson)}
+ catch {return shouldReturnBack ? maybeJson : null}
+ }
+
+ /**
+ * Checks if status code is an OK response (200-208 & 226)
+ * @param {number} status Status code
+ * @returns {boolean} Is OK?
+ */
+ static isOK(status) {
+ return Helper.statusesOK.includes(status)
+ }
+}
\ No newline at end of file
diff --git a/classes/RL.js b/classes/RL.js
new file mode 100644
index 0000000..b4e4f98
--- /dev/null
+++ b/classes/RL.js
@@ -0,0 +1,36 @@
+const { createInterface } = require("readline")
+const { stdin, stdout } = require("process")
+
+/** Class for prompting user in terminal */
+export class RL {
+ constructor() {
+ this.interface = createInterface(stdin, stdout)
+ }
+
+ /**
+ * Prompts user
+ * @param {string} question Question for the readline function
+ * @param {boolean} require Is method accepting empty strings?
+ * @returns {Promise} Answer
+ */
+ async readLine(question = "", require = false) {
+ const q = (q) => new Promise(resolve => this.interface.question(q, resolve))
+ let a = ""
+
+ if(require) while(a.trim().length === 0) a = await q(question)
+ else a = await q(question)
+
+ return a
+ }
+
+ /**
+ * Prompts user staticaally without need of new classes
+ * @param {string} question Question for the readline function
+ * @param {boolean} require Is method accepting empty strings?
+ * @returns {Promise} Answer
+ */
+ static async readLine(question = "", require = false) {
+ const instance = new RL()
+ return instance.readLine(question, require)
+ }
+}
\ No newline at end of file
diff --git a/clear.bat b/clear.bat
new file mode 100644
index 0000000..911e4b0
--- /dev/null
+++ b/clear.bat
@@ -0,0 +1,5 @@
+@echo off
+
+if exist sea.blob ( del sea.blob )
+if exist out.js ( del out.js )
+if exist gif-validator.exe ( del gif-validator.exe )
\ No newline at end of file
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..2885c76
--- /dev/null
+++ b/index.js
@@ -0,0 +1,22 @@
+const { GifClient } = require("./classes/GifClient.js");
+const { RL } = require("./classes/RL.js");
+
+process.removeAllListeners('warning')
+
+RL.readLine("Provide your token here: ", true).then(async token => {
+ const client = new GifClient(token)
+
+ console.log("Grabbing gifs...")
+ const gifs = await client.getGifs()
+
+ console.log(`Validating ${Object.keys(gifs).length} gifs...`)
+ const validatedGifs = await client.validateGifs(gifs)
+
+ console.log(`Got ${Object.keys(validatedGifs).length} valid gifs... saving them.`)
+ const result = await client.saveGifs(validatedGifs);
+
+ if(result) console.log(`Successfully validated and saved ${Object.keys(validatedGifs).length} gifs!`)
+ else console.log("Something went wrong... maybe discord updated their servers. If so, this app WON'T work anymore.")
+
+ setTimeout(process.exit, 5_000) // Delay before closing
+})
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..7deac4f
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,528 @@
+{
+ "name": "gif-validator",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "gif-validator",
+ "version": "1.0.0",
+ "dependencies": {
+ "axios": "^1.6.7",
+ "discord-protos": "^1.0.5"
+ },
+ "devDependencies": {
+ "esbuild": "0.20.1"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz",
+ "integrity": "sha512-m55cpeupQ2DbuRGQMMZDzbv9J9PgVelPjlcmM5kxHnrBdBx6REaEd7LamYV7Dm8N7rCyR/XwU6rVP8ploKtIkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.1.tgz",
+ "integrity": "sha512-4j0+G27/2ZXGWR5okcJi7pQYhmkVgb4D7UKwxcqrjhvp5TKWx3cUjgB1CGj1mfdmJBQ9VnUGgUhign+FPF2Zgw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.1.tgz",
+ "integrity": "sha512-hCnXNF0HM6AjowP+Zou0ZJMWWa1VkD77BXe959zERgGJBBxB+sV+J9f/rcjeg2c5bsukD/n17RKWXGFCO5dD5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.1.tgz",
+ "integrity": "sha512-MSfZMBoAsnhpS+2yMFYIQUPs8Z19ajwfuaSZx+tSl09xrHZCjbeXXMsUF/0oq7ojxYEpsSo4c0SfjxOYXRbpaA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.1.tgz",
+ "integrity": "sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.1.tgz",
+ "integrity": "sha512-pFIfj7U2w5sMp52wTY1XVOdoxw+GDwy9FsK3OFz4BpMAjvZVs0dT1VXs8aQm22nhwoIWUmIRaE+4xow8xfIDZA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.1.tgz",
+ "integrity": "sha512-UyW1WZvHDuM4xDz0jWun4qtQFauNdXjXOtIy7SYdf7pbxSWWVlqhnR/T2TpX6LX5NI62spt0a3ldIIEkPM6RHw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.1.tgz",
+ "integrity": "sha512-itPwCw5C+Jh/c624vcDd9kRCCZVpzpQn8dtwoYIt2TJF3S9xJLiRohnnNrKwREvcZYx0n8sCSbvGH349XkcQeg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.1.tgz",
+ "integrity": "sha512-LojC28v3+IhIbfQ+Vu4Ut5n3wKcgTu6POKIHN9Wpt0HnfgUGlBuyDDQR4jWZUZFyYLiz4RBBBmfU6sNfn6RhLw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.1.tgz",
+ "integrity": "sha512-cX8WdlF6Cnvw/DO9/X7XLH2J6CkBnz7Twjpk56cshk9sjYVcuh4sXQBy5bmTwzBjNVZze2yaV1vtcJS04LbN8w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.1.tgz",
+ "integrity": "sha512-4H/sQCy1mnnGkUt/xszaLlYJVTz3W9ep52xEefGtd6yXDQbz/5fZE5dFLUgsPdbUOQANcVUa5iO6g3nyy5BJiw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.1.tgz",
+ "integrity": "sha512-c0jgtB+sRHCciVXlyjDcWb2FUuzlGVRwGXgI+3WqKOIuoo8AmZAddzeOHeYLtD+dmtHw3B4Xo9wAUdjlfW5yYA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.1.tgz",
+ "integrity": "sha512-TgFyCfIxSujyuqdZKDZ3yTwWiGv+KnlOeXXitCQ+trDODJ+ZtGOzLkSWngynP0HZnTsDyBbPy7GWVXWaEl6lhA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.1.tgz",
+ "integrity": "sha512-b+yuD1IUeL+Y93PmFZDZFIElwbmFfIKLKlYI8M6tRyzE6u7oEP7onGk0vZRh8wfVGC2dZoy0EqX1V8qok4qHaw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.1.tgz",
+ "integrity": "sha512-wpDlpE0oRKZwX+GfomcALcouqjjV8MIX8DyTrxfyCfXxoKQSDm45CZr9fanJ4F6ckD4yDEPT98SrjvLwIqUCgg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.1.tgz",
+ "integrity": "sha512-5BepC2Au80EohQ2dBpyTquqGCES7++p7G+7lXe1bAIvMdXm4YYcEfZtQrP4gaoZ96Wv1Ute61CEHFU7h4FMueQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.1.tgz",
+ "integrity": "sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.1.tgz",
+ "integrity": "sha512-4fL68JdrLV2nVW2AaWZBv3XEm3Ae3NZn/7qy2KGAt3dexAgSVT+Hc97JKSZnqezgMlv9x6KV0ZkZY7UO5cNLCg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.1.tgz",
+ "integrity": "sha512-GhRuXlvRE+twf2ES+8REbeCb/zeikNqwD3+6S5y5/x+DYbAQUNl0HNBs4RQJqrechS4v4MruEr8ZtAin/hK5iw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.1.tgz",
+ "integrity": "sha512-ZnWEyCM0G1Ex6JtsygvC3KUUrlDXqOihw8RicRuQAzw+c4f1D66YlPNNV3rkjVW90zXVsHwZYWbJh3v+oQFM9Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.1.tgz",
+ "integrity": "sha512-QZ6gXue0vVQY2Oon9WyLFCdSuYbXSoxaZrPuJ4c20j6ICedfsDilNPYfHLlMH7vGfU5DQR0czHLmJvH4Nzis/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.1.tgz",
+ "integrity": "sha512-HzcJa1NcSWTAU0MJIxOho8JftNp9YALui3o+Ny7hCh0v5f90nprly1U3Sj1Ldj/CvKKdvvFsCRvDkpsEMp4DNw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.1.tgz",
+ "integrity": "sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@protobuf-ts/runtime": {
+ "version": "2.9.3",
+ "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime/-/runtime-2.9.3.tgz",
+ "integrity": "sha512-nivzCpg/qYD0RX2OmHOahJALb8ndjGmUhNBcTJ0BbXoqKwCSM6vYA+vegzS3rhuaPgbyC7Ec8idlnizzUfIRuw=="
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "node_modules/axios": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
+ "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
+ "dependencies": {
+ "follow-redirects": "^1.15.4",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/discord-protos": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/discord-protos/-/discord-protos-1.0.5.tgz",
+ "integrity": "sha512-924CFthTtgkwsjVp9tC6Pvj/C5b5HWQ68A6suiEz1Dkg+LqvVsvQAGgMhvYc/nXNcosqQIeBruSQBxv4wIxVvw==",
+ "dependencies": {
+ "@protobuf-ts/runtime": "^2.8.2"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.1.tgz",
+ "integrity": "sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.20.1",
+ "@esbuild/android-arm": "0.20.1",
+ "@esbuild/android-arm64": "0.20.1",
+ "@esbuild/android-x64": "0.20.1",
+ "@esbuild/darwin-arm64": "0.20.1",
+ "@esbuild/darwin-x64": "0.20.1",
+ "@esbuild/freebsd-arm64": "0.20.1",
+ "@esbuild/freebsd-x64": "0.20.1",
+ "@esbuild/linux-arm": "0.20.1",
+ "@esbuild/linux-arm64": "0.20.1",
+ "@esbuild/linux-ia32": "0.20.1",
+ "@esbuild/linux-loong64": "0.20.1",
+ "@esbuild/linux-mips64el": "0.20.1",
+ "@esbuild/linux-ppc64": "0.20.1",
+ "@esbuild/linux-riscv64": "0.20.1",
+ "@esbuild/linux-s390x": "0.20.1",
+ "@esbuild/linux-x64": "0.20.1",
+ "@esbuild/netbsd-x64": "0.20.1",
+ "@esbuild/openbsd-x64": "0.20.1",
+ "@esbuild/sunos-x64": "0.20.1",
+ "@esbuild/win32-arm64": "0.20.1",
+ "@esbuild/win32-ia32": "0.20.1",
+ "@esbuild/win32-x64": "0.20.1"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.5",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
+ "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..8ab4103
--- /dev/null
+++ b/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "gif-validator",
+ "description": "gif-validator is a simple script allowing you to clear favorite gifs on Discord from ones that are not available.",
+ "version": "1.0.0",
+ "private": true,
+ "main": "index.js",
+ "scripts": {
+ "start": "node .",
+ "build-win": "build.bat",
+ "clear-win": "clear.bat"
+ },
+ "dependencies": {
+ "axios": "^1.6.7",
+ "discord-protos": "^1.0.5"
+ },
+ "devDependencies": {
+ "esbuild": "0.20.1"
+ }
+}
diff --git a/preview.gif b/preview.gif
new file mode 100644
index 0000000..99442c4
Binary files /dev/null and b/preview.gif differ
diff --git a/sea-config.json b/sea-config.json
new file mode 100644
index 0000000..d38a9d6
--- /dev/null
+++ b/sea-config.json
@@ -0,0 +1,4 @@
+{
+ "main": "out.js",
+ "output": "sea.blob"
+}
\ No newline at end of file