From 4753a8a8bcefca92852bccc7f333ee3aacca8d4a Mon Sep 17 00:00:00 2001 From: kvba Date: Tue, 6 Aug 2024 11:44:53 +0200 Subject: [PATCH] Huge update! - Typescript rewrite - Types added - Code optimization --- package.json | 16 ++- pnpm-lock.yaml | 303 ++++++++++++++++++++++++++++++++++++++- src/class/GifClient.ts | 107 ++++++++++++++ src/class/ReadlineEx.ts | 28 ++++ src/classes/GifClient.js | 85 ----------- src/classes/Helper.js | 35 ----- src/classes/RL.js | 36 ----- src/index.js | 21 --- src/index.ts | 27 ++++ src/type/GifClient.ts | 19 +++ 10 files changed, 492 insertions(+), 185 deletions(-) create mode 100644 src/class/GifClient.ts create mode 100644 src/class/ReadlineEx.ts delete mode 100644 src/classes/GifClient.js delete mode 100644 src/classes/Helper.js delete mode 100644 src/classes/RL.js delete mode 100644 src/index.js create mode 100644 src/index.ts create mode 100644 src/type/GifClient.ts diff --git a/package.json b/package.json index af7e472..c063e41 100644 --- a/package.json +++ b/package.json @@ -1,15 +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", + "version": "1.1.0", "private": true, - "main": "src/index.js", - "type": "module", + "main": "src/index.ts", "scripts": { - "start": "node ." + "start": "tsx ." }, "dependencies": { - "axios": "^1.6.7", - "discord-protos": "^1.0.5" + "axios": "^1.7.3", + "discord-protos": "1.0.5", + "tsx": "4.16.5", + "typescript": "5.5.4" + }, + "devDependencies": { + "@types/node": "22.1.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e83d00a..806bbb7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,17 +9,168 @@ importers: .: dependencies: axios: - specifier: ^1.6.7 + specifier: ^1.7.3 version: 1.7.3 discord-protos: - specifier: ^1.0.5 + specifier: 1.0.5 version: 1.0.5 + tsx: + specifier: 4.16.5 + version: 4.16.5 + typescript: + specifier: 5.5.4 + version: 5.5.4 + devDependencies: + '@types/node': + specifier: 22.1.0 + version: 22.1.0 packages: + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@protobuf-ts/runtime@2.9.4': resolution: {integrity: sha512-vHRFWtJJB/SiogWDF0ypoKfRIZ41Kq+G9cEFj6Qm1eQaAhJ1LDFvgZ7Ja4tb3iLOQhz0PaoPnnOijF1qmEqTxg==} + '@types/node@22.1.0': + resolution: {integrity: sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -37,6 +188,11 @@ packages: discord-protos@1.0.5: resolution: {integrity: sha512-924CFthTtgkwsjVp9tC6Pvj/C5b5HWQ68A6suiEz1Dkg+LqvVsvQAGgMhvYc/nXNcosqQIeBruSQBxv4wIxVvw==} + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + follow-redirects@1.15.6: resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} engines: {node: '>=4.0'} @@ -50,6 +206,14 @@ packages: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-tsconfig@4.7.6: + resolution: {integrity: sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -61,10 +225,99 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + tsx@4.16.5: + resolution: {integrity: sha512-ArsiAQHEW2iGaqZ8fTA1nX0a+lN5mNTyuGRRO6OW3H/Yno1y9/t1f9YOI1Cfoqz63VAthn++ZYcbDP7jPflc+A==} + engines: {node: '>=18.0.0'} + hasBin: true + + typescript@5.5.4: + resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.13.0: + resolution: {integrity: sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==} + snapshots: + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + '@protobuf-ts/runtime@2.9.4': {} + '@types/node@22.1.0': + dependencies: + undici-types: 6.13.0 + asynckit@0.4.0: {} axios@1.7.3: @@ -85,6 +338,32 @@ snapshots: dependencies: '@protobuf-ts/runtime': 2.9.4 + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + follow-redirects@1.15.6: {} form-data@4.0.0: @@ -93,6 +372,13 @@ snapshots: combined-stream: 1.0.8 mime-types: 2.1.35 + fsevents@2.3.3: + optional: true + + get-tsconfig@4.7.6: + dependencies: + resolve-pkg-maps: 1.0.0 + mime-db@1.52.0: {} mime-types@2.1.35: @@ -100,3 +386,16 @@ snapshots: mime-db: 1.52.0 proxy-from-env@1.1.0: {} + + resolve-pkg-maps@1.0.0: {} + + tsx@4.16.5: + dependencies: + esbuild: 0.21.5 + get-tsconfig: 4.7.6 + optionalDependencies: + fsevents: 2.3.3 + + typescript@5.5.4: {} + + undici-types@6.13.0: {} diff --git a/src/class/GifClient.ts b/src/class/GifClient.ts new file mode 100644 index 0000000..0b39e8d --- /dev/null +++ b/src/class/GifClient.ts @@ -0,0 +1,107 @@ +import type { AxiosResponse } from "axios" +import type { GifsList, ProtoResponse } from "../type/GifClient" + +import { ProtoTypes } from "../type/GifClient" + +import { FrecencyUserSettings } from "discord-protos" +import axiosd, { Axios } from "axios" + + +const PROTO_URL = (proto_type: keyof typeof ProtoTypes) => "https://discord.com/api/v9/users/@me/settings-proto/".concat(String(ProtoTypes[proto_type])) + +/** Main client class for this project */ +export class GifClient { + req: Axios + + constructor(token: string) { + + this.req = 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: axiosd.defaults.transformResponse, + transformRequest: axiosd.defaults.transformRequest + }) + } + + private handleError(res: AxiosResponse) { + if(res.status >= 400) { + const { code, message } = res.data + let msg = (code && message) ? `${code}: ${message}` : JSON.stringify(res.data); + switch(res.status) { + case 401: msg = "Invalid token!"; break + + } + throw new Error(msg) + } + + return + } + + /** + * Grabs list of gifs and returns them + * @returns Gifs object + */ + async getGifs(): Promise { + const resp = await this.req.get(PROTO_URL("FRECENCY_AND_FAVORITES_SETTINGS")) + this.handleError(resp) + + const { data } = resp + + const {settings: encodedSettings} = data + const decodedSettings = FrecencyUserSettings.fromBase64(encodedSettings!) + + const gifs = decodedSettings?.["favoriteGifs"]?.["gifs"] + if(!gifs) throw new Error("Invalid settings! App may have been updated.") + + return gifs + } + + /** + * Validates gifs and removes unavailable ones. + * @param gifs Gifs + * @returns Valid Gifs + */ + async validateGifs(gifs: GifsList): Promise { + let newGifs = Object.assign({}, gifs) + + for (let key of Object.keys(gifs)) { + const resp = await this.req.head(gifs[key].src) + const isOK = resp.status < 400 + + console.log(`${isOK ? "✅" : "❌"} ${key}`) + if(!isOK) delete newGifs[key] + } + + return newGifs + } + + /** + * Saves gifs and returns if it succedded + * @param gifs Gifs to save + * @return Was save successful? + */ + async saveGifs(gifs) { + const decodedSettings: FrecencyUserSettings = { + favoriteGifs: { + hideTooltip: false, + gifs: gifs + } + } + + const encodedSettings: string = FrecencyUserSettings.toBase64(decodedSettings); + + const resp = await this.req.patch(PROTO_URL("FRECENCY_AND_FAVORITES_SETTINGS"), {settings: encodedSettings}) + this.handleError(resp) + + return resp.status < 400 + } +} \ No newline at end of file diff --git a/src/class/ReadlineEx.ts b/src/class/ReadlineEx.ts new file mode 100644 index 0000000..50a63c2 --- /dev/null +++ b/src/class/ReadlineEx.ts @@ -0,0 +1,28 @@ +import { Interface } from "readline" +import { stdin, stdout } from "process" + +/** Class for prompting user in terminal */ +export default class ReadlineEx extends Interface { + constructor() { + super({ + input: stdin, + output: stdout + }) + } + + /** + * Prompts user + * @param question Question for the readline function + * @param require Is method accepting empty strings? + * @returns Answer + */ + async ask(question: string = "", require: boolean = false): Promise { + let answer: string|null = null + while( + !answer || + (require && answer?.trim?.().length === 0) + ) answer = await new Promise(resolve => this.question(question, resolve)) + + return answer + } +} \ No newline at end of file diff --git a/src/classes/GifClient.js b/src/classes/GifClient.js deleted file mode 100644 index 9eced76..0000000 --- a/src/classes/GifClient.js +++ /dev/null @@ -1,85 +0,0 @@ -import { Helper } from "./Helper.js" -import { FrecencyUserSettings } from "discord-protos" -import { Axios } from "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/src/classes/Helper.js b/src/classes/Helper.js deleted file mode 100644 index 708fa45..0000000 --- a/src/classes/Helper.js +++ /dev/null @@ -1,35 +0,0 @@ -/** 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/src/classes/RL.js b/src/classes/RL.js deleted file mode 100644 index 350d005..0000000 --- a/src/classes/RL.js +++ /dev/null @@ -1,36 +0,0 @@ -import { createInterface } from "readline" -import { stdin, stdout } from "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/src/index.js b/src/index.js deleted file mode 100644 index be76829..0000000 --- a/src/index.js +++ /dev/null @@ -1,21 +0,0 @@ -import { GifClient } from "./classes/GifClient.js"; -import { RL } from "./classes/RL.js"; - - -const token = await RL.readLine("Provide your token here: ", true) - -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/src/index.ts b/src/index.ts new file mode 100644 index 0000000..247782f --- /dev/null +++ b/src/index.ts @@ -0,0 +1,27 @@ +import { GifClient } from "./class/GifClient"; +import InterfaceEx from "./class/ReadlineEx"; + +;(async()=>{ + + const token = await new InterfaceEx().ask("Provide your token here: ", true) + const client = new GifClient(token) + + console.log("Grabbing gifs...") + const gifs = await client.getGifs() + const gifsSize = Object.keys(gifs).length + + console.log(`Validating ${gifsSize} gif${gifsSize > 1 ? "s" : ""}...`) + const validGifs = await client.validateGifs(gifs) + const validGifsSize = Object.keys(validGifs).length + + console.log(`Got ${validGifsSize} valid gifs... saving them.`) + const didSave = await client.saveGifs(validGifs); + + console.log( + didSave ? `Successfully validated and saved ${validGifsSize} gif${validGifsSize > 1 ? "s" : ""}!` + : "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/src/type/GifClient.ts b/src/type/GifClient.ts new file mode 100644 index 0000000..2f1b9bc --- /dev/null +++ b/src/type/GifClient.ts @@ -0,0 +1,19 @@ +import type { FrecencyUserSettings_FavoriteGIF } from "discord-protos"; + +export type ErrorResponse = { + code: number, + message: string +} + +export type ProtoResponse = { + settings?: string, +} & Partial + + +export type GifsList = { [key: string]: FrecencyUserSettings_FavoriteGIF; } + +export const ProtoTypes = { + PRELOADED_USER_SETTINGS: 1, + FRECENCY_AND_FAVORITES_SETTINGS: 2, + TEST_SETTINGS: 3 +} \ No newline at end of file