Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
kvba5 committed Mar 2, 2024
0 parents commit d8e8e38
Show file tree
Hide file tree
Showing 12 changed files with 926 additions and 0 deletions.
134 changes: 134 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -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
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<center><img src="preview.gif" height="300" alt="Gif Validator preview" /></center>


# 💀 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

<b>❗ OFFICIAL SUPPORT IS AVAILABLE ONLY FOR WINDOWS</b>
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.
20 changes: 20 additions & 0 deletions build.bat
Original file line number Diff line number Diff line change
@@ -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 )
)
85 changes: 85 additions & 0 deletions classes/GifClient.js
Original file line number Diff line number Diff line change
@@ -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<boolean>} 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)
}
}
35 changes: 35 additions & 0 deletions classes/Helper.js
Original file line number Diff line number Diff line change
@@ -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)
}
}
36 changes: 36 additions & 0 deletions classes/RL.js
Original file line number Diff line number Diff line change
@@ -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<string>} 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<string>} Answer
*/
static async readLine(question = "", require = false) {
const instance = new RL()
return instance.readLine(question, require)
}
}
5 changes: 5 additions & 0 deletions clear.bat
Original file line number Diff line number Diff line change
@@ -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 )
22 changes: 22 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -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
})
Loading

0 comments on commit d8e8e38

Please sign in to comment.