From 9503f9248fcf3057acb5b058b2f52a0ba2767469 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 14 Apr 2024 23:45:28 +0200 Subject: [PATCH 01/20] Add reverse lookup of class names and auto convert Adds auto conversion of working theme files to our format --- .gitignore | 3 +- assets/conflictSolver.ts | 56 ++++++++++++++++++++++++++++++++ assets/fetcher.ts | 68 +++++++++++++++++++++++--------------- assets/utils.ts | 26 ++++++++++++++- index.ts | 70 +++++++++++++++++++++++++++++++++++++--- 5 files changed, 190 insertions(+), 33 deletions(-) create mode 100644 assets/conflictSolver.ts diff --git a/.gitignore b/.gitignore index 97086a7..c9eaedf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules .idea -build \ No newline at end of file +build +*.raw.css diff --git a/assets/conflictSolver.ts b/assets/conflictSolver.ts new file mode 100644 index 0000000..ff992bc --- /dev/null +++ b/assets/conflictSolver.ts @@ -0,0 +1,56 @@ +import * as utils from "./utils" +import * as fetcher from "./fetcher" + +type SolveResult = { + isUnique: boolean, + recipe: string[], +}; + +const stages: { [key: number]: (targetModule: { [key: string]: any }, targetProp: string, targetModuleId: string) => Promise } = { + 0: async (targetModule, targetProp) => { + const proxy = await fetcher.fetchFullDiscordCSSDefinitions(); + return { + isUnique: proxy.filter(x => { + return x.hasOwnProperty(targetProp); + }).length == 1, + recipe: [targetProp], + }; + }, + 1: async (targetModule, targetProp) => { + const proxy = await fetcher.fetchFullDiscordCSSDefinitions(); + const targetModuleKeys = Object.keys(targetModule); + const targetAmountOfPropertiesToPick = ((targetModuleKeys.length % 2 == 0) ? targetModuleKeys.length : targetModuleKeys.length - 1); + for (let index = 0; index < 3; index++) { + const randomProps = utils.pickRandomProperties(targetModule, targetAmountOfPropertiesToPick > 1 ? (targetAmountOfPropertiesToPick / 2) : targetAmountOfPropertiesToPick); + const targetProps = [...randomProps, targetProp]; + const constructed = { + isUnique: proxy.filter(x => + targetProps.every(key => x && x.hasOwnProperty(key))).length == 1, + recipe: targetProps + }; + if (constructed.isUnique) + return constructed; + } + return { + isUnique: false, + recipe: [], + }; + }, +}; + +export default async function solver(targetModule: { [key: string]: any }, targetProp: string, targetModuleId: string) { + /* + const targetModuleKeys = Object.keys(targetModule); + const targetAmountOfPropertiesToPick = ((targetModuleKeys.length % 2 == 0) ? targetModuleKeys.length : targetModuleKeys.length - 1); + const randomProps = utils.pickRandomProperties(targetModule, targetAmountOfPropertiesToPick > 1 ? (targetAmountOfPropertiesToPick / 2) : targetAmountOfPropertiesToPick); + */ + for (let index = 0; index < Object.keys(stages).length; index++) { + const currentStage = stages[index]; + const result = await currentStage(targetModule, targetProp, targetModuleId); + if (result.isUnique == true) { + return result.recipe; + } + console.log("Next stage.", targetModule, targetProp); + } + return null; +} diff --git a/assets/fetcher.ts b/assets/fetcher.ts index 3374e85..6b781cd 100644 --- a/assets/fetcher.ts +++ b/assets/fetcher.ts @@ -1,4 +1,4 @@ -import {EvaluatedScript, FetchedScript, PreparedScript} from "./types"; +import { EvaluatedScript, FetchedScript, PreparedScript } from "./types"; const DISCORD_DOMAIN = "discord.com"; const DISCORD_APP_PATH = "/app"; @@ -17,10 +17,12 @@ const TEXT_CONSTANTS = { scriptEnd: "", }; -const MODULE_MATCHER_REGEX = (propName: string) => `({|,)(${propName}):`; +const MODULE_VALUE_MATCHER_REGEX = (propName: string) => `({|,)(${propName}):`; +const MODULE_PROP_MATCHER_REGEX = (propName: string) => `({|,)(\\w+):"(${propName})"`; const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.3"; import * as utils from "./utils"; +import solver from "./conflictSolver"; function fetchDiscordPage() { return fetch(`https://${DISCORD_DOMAIN}${DISCORD_APP_PATH}`, { @@ -99,7 +101,7 @@ function evaluateScripts(convertedScripts: PreparedScript[]) { const cachedScripts: EvaluatedScript[] = []; -export async function fetchFullDiscordCSSDefinitions() { +export async function fetchFullDiscordCSSDefinitions(reverseMode = false) { if (cachedScripts.length == 0) { const response = await fetchDiscordPage(); const responseAsText = await response.text(); @@ -177,7 +179,7 @@ export async function fetchFullDiscordCSSDefinitions() { const cache: { [key: string]: any } = {}; const result = { evaluatedScripts, - find(predicate: (element: any) => boolean) { + filter(predicate: (element: any) => boolean, thisArg?: any, first: boolean = false) { for (let index = 0; index < evaluatedScripts.length; index++) { const evaluatedScript = evaluatedScripts[index]; const modules = Object.keys(evaluatedScript.value); @@ -186,7 +188,7 @@ export async function fetchFullDiscordCSSDefinitions() { const fakeObject = { module: "", hasOwnProperty(prop: string) { // and this is, kids, why you don't do something.hasOwnProperty() but Object.prototype.hasOwnProperty.call(something) - const regex = new RegExp(MODULE_MATCHER_REGEX(prop), "g"); + const regex = new RegExp(reverseMode ? MODULE_PROP_MATCHER_REGEX(prop) : MODULE_VALUE_MATCHER_REGEX(prop), "g"); if (regex.test(evaluatedScript.value[x].toString())) { this.module = x; return true; @@ -197,36 +199,50 @@ export async function fetchFullDiscordCSSDefinitions() { fakeArray.push(fakeObject); }); // const foundOrNot = modules.filter(x=>evaluatedScript.value[x].toString().includes()) - const foundOrNot = fakeArray.find(predicate); + const foundOrNot = fakeArray.filter(predicate); fakeArray.length = 0; - if (foundOrNot) { - const fakeRequire = (id: string) => { - const fakeWebpack2 = { exports: undefined as unknown as { [key: string]: string } }; - // console.log(id); - if (cache[id] == undefined) { - if (evaluatedScript.value[id] == undefined) { - evaluatedScripts.find(x => x.value[id])?.value[id](fakeWebpack2, undefined, fakeRequire); + if (foundOrNot.length > 0) { + const evenMoreFinalResult = []; + for (let index2 = 0; index2 < foundOrNot.length; index2++) { + const fakeRequire = (id: string) => { + const fakeWebpack2 = { exports: undefined as unknown as { [key: string]: string } }; + // console.log(id); + if (cache[id] == undefined) { + if (evaluatedScript.value[id] == undefined) { + evaluatedScripts.find(x => x.value[id])?.value[id](fakeWebpack2, undefined, fakeRequire); + cache[id] = fakeWebpack2.exports; + return cache[id]; + } + evaluatedScript.value[id](fakeWebpack2, undefined, fakeRequire); cache[id] = fakeWebpack2.exports; - return cache[id]; } - evaluatedScript.value[id](fakeWebpack2, undefined, fakeRequire); - cache[id] = fakeWebpack2.exports; - } - return cache[id] ?? fakeWebpack2.exports; - }; + return cache[id] ?? fakeWebpack2.exports; + }; - const foundModule = foundOrNot.module; - if (cache[foundModule] == undefined) { - const builder = evaluatedScript.value[foundModule]; - const fakeWebpack = { exports: undefined as unknown as { [key: string]: string } }; - builder(fakeWebpack, undefined, fakeRequire); - cache[foundModule] = fakeWebpack.exports; + const foundModule = foundOrNot[index2].module; + if (cache[foundModule] == undefined) { + const builder = evaluatedScript.value[foundModule]; + const fakeWebpack = { exports: undefined as unknown as { [key: string]: string } }; + builder(fakeWebpack, undefined, fakeRequire); + cache[foundModule] = fakeWebpack.exports; + } + if (first) + return cache[foundModule]; + evenMoreFinalResult.push(cache[foundModule]); } - return cache[foundModule]; + return evenMoreFinalResult; } } return undefined; }, + get find() { + return (...args: any[]) => this.filter(args[0], args[1], true); + } }; return result as unknown as { [key: string]: any }[]; } + +export function conflictSolver(targetModule: { [key: string]: any }, targetProp: string, targetModuleId: string) { + console.log("Running conflict solver. This task will take a while."); + return solver(targetModule, targetProp, targetModuleId); +} diff --git a/assets/utils.ts b/assets/utils.ts index 98664ac..bf398b8 100644 --- a/assets/utils.ts +++ b/assets/utils.ts @@ -18,5 +18,29 @@ export function getTextBetween(text: string, start: string, end: string) { return results; } export const delay = (milliseconds: number) => new Promise((resolve, reject) => { - setTimeout(_ => resolve(), milliseconds); + setTimeout((_: any) => resolve(), milliseconds); }); +export function pickRandomProperties(obj: any, n: number) { + const keys = Object.keys(obj); + const randomKeys: string[] = []; + if (n >= keys.length) { + return keys.map(key => obj[key]); + } + + while (randomKeys.length < n) { + const randomKey = keys[Math.floor(Math.random() * keys.length)]; + if (!randomKeys.includes(randomKey)) { + randomKeys.push(randomKey); + } + } + return randomKeys; +} +export async function replaceAsync(str: string, regex: any, asyncFn: (...args: any[]) => any) { + const promises: any[] = []; + str.replace(regex, (full, ...args) => { + promises.push(asyncFn(full, ...args)); + return full; + }); + const data = await Promise.all(promises); + return str.replace(regex, () => data.shift()); +} diff --git a/index.ts b/index.ts index 078deeb..9f48004 100644 --- a/index.ts +++ b/index.ts @@ -2,7 +2,8 @@ import * as fs from 'fs'; import * as path from 'path'; import { promisify } from 'util'; -import {EvaluatedScript} from "./assets/types"; +import { EvaluatedScript } from "./assets/types"; +import { pickRandomProperties, replaceAsync } from './assets/utils'; const writeFileAsync = promisify(fs.writeFile); const readdir = promisify(fs.readdir); @@ -19,10 +20,11 @@ const readdir = promisify(fs.readdir); // thankies shady. regex go brerrr const REPLACEMENT_REGEX = /(\[["']\w+.+?]).(\w+)/g; +const REPLACEMENT_REGEX2 = /\.([a-zA-Z]+\_\_?[a-zA-Z0-9_.-]+)\s\{/g; function replaceClassNamesByRegex(cssString: string, jsonFile: { [key: string]: string }[]): string { return cssString.replace(REPLACEMENT_REGEX, (match, group1: string, group2) => { - const modifiedGroup = group1.replace(/'/g, "\""); + const modifiedGroup = group1.replace(/'/g, "\""); // JSON.parse can't parse single quotes....? const rawProps: string[] = JSON.parse(modifiedGroup); // too lazy const toExcludeProps: string[] = []; @@ -30,12 +32,37 @@ function replaceClassNamesByRegex(cssString: string, jsonFile: { [key: string]: console.log(match, targetProps); const targetClassName = jsonFile.find(x => targetProps.every(key => x && x.hasOwnProperty(key)) && !toExcludeProps.some(key => x && x.hasOwnProperty(key.slice(1)))); if (targetClassName) { - return targetClassName[group2].replace(' ','.'); + return targetClassName[group2].replace(' ', '.'); } return match; }); } +function reverseLookup(realClassName: string, cssDefs: { [key: string]: string }[]) { + let targetModuleId: string | null = null; + const targetModule = cssDefs.find(x => { + if (x.hasOwnProperty(realClassName)) { + targetModuleId = x.module; + return true; + } + return false; + }); + if (!targetModule) + throw new Error(`Reverse lookup for ${realClassName} failed.`); + const targetProp = Object.keys(targetModule).find(x => targetModule[x] == realClassName); + return { recipe: fetcher.conflictSolver(targetModule, targetProp!, targetModuleId!), targetProp }; +} + +async function replaceClassNamesByRegexInReverse(cssString: string, cssDefs: { [key: string]: string }[]) { + return await replaceAsync(cssString, REPLACEMENT_REGEX2, async (match, group1: string) => { + const modifiedGroup = group1.replace('.', ' '); + const output = reverseLookup(modifiedGroup, cssDefs); + if (output == null) + return match; + return `.${JSON.stringify(await output.recipe)}.${output.targetProp} {`; + }); +} + async function startConverting(inputFilePath: string, optionalFilePath: string): Promise { const outputFolder = 'build'; const fileName = path.basename(inputFilePath, path.extname(inputFilePath)); @@ -66,8 +93,33 @@ async function startConverting(inputFilePath: string, optionalFilePath: string): } } +async function startReverseConverting(inputFilePath: string): Promise { + const fileName = path.basename(inputFilePath, path.extname(inputFilePath)) + ".raw"; + const outputPath = path.join("./", fileName); + + try { + if (!fs.existsSync(inputFilePath)) { + fs.writeFileSync(inputFilePath, ''); + } + + const cssString = await fs.promises.readFile(inputFilePath, 'utf8'); + const cssDefs = await fetcher.fetchFullDiscordCSSDefinitions(true); + const updatedCSS = await replaceClassNamesByRegexInReverse(cssString, cssDefs); + const fileExtension = ".css"; + + await writeFileAsync(outputPath + fileExtension, updatedCSS); + + console.log(`Updated CSS has been written to ${outputPath}`); + } catch (err) { + console.error('Error:', err); + } +} + const args: string[] = process.argv.slice(2); +const mode = args.includes("--reverse") ? 1 : 0; +if (mode != 0) + args.shift(); if (args.includes("--help") || args.length == 0) { console.error('Usage:\n\tnpx ts-node index.ts '); process.exit(1); @@ -85,6 +137,9 @@ async function getFiles(dir: string): Promise { let inputPath: string = args[0]; console.log(args) if (inputPath) { + /* reverseLookup("emojiContainer__07d67").then(out => console.log(out)); + // @ts-ignore + return; */ let resolvedPath: string = path.resolve(inputPath); let optionalFilePath = args[1]; if (!fs.existsSync(resolvedPath)) { @@ -96,10 +151,15 @@ if (inputPath) { const paths = await getFiles(resolvedPath); for (let index = 0; index < paths.length; index++) { const element = paths[index]; - startConverting(element, optionalFilePath); + if (mode == 0) + startConverting(element, optionalFilePath); + else if (mode == 1) + startReverseConverting(element); } })(); } - else + else if (mode == 0) startConverting(resolvedPath, optionalFilePath); + else if (mode == 1) + startReverseConverting(resolvedPath); } From b8ba9b1423095f20d71fc2390d2086b3975b8356 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 15 Apr 2024 02:37:21 +0200 Subject: [PATCH 02/20] Make conflict solver prefer less props when possible --- assets/conflictSolver.ts | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/assets/conflictSolver.ts b/assets/conflictSolver.ts index ff992bc..024a29a 100644 --- a/assets/conflictSolver.ts +++ b/assets/conflictSolver.ts @@ -20,16 +20,42 @@ const stages: { [key: number]: (targetModule: { [key: string]: any }, targetProp const proxy = await fetcher.fetchFullDiscordCSSDefinitions(); const targetModuleKeys = Object.keys(targetModule); const targetAmountOfPropertiesToPick = ((targetModuleKeys.length % 2 == 0) ? targetModuleKeys.length : targetModuleKeys.length - 1); - for (let index = 0; index < 3; index++) { + for (let index = 0; index < 6; index++) { const randomProps = utils.pickRandomProperties(targetModule, targetAmountOfPropertiesToPick > 1 ? (targetAmountOfPropertiesToPick / 2) : targetAmountOfPropertiesToPick); - const targetProps = [...randomProps, targetProp]; + const targetProps = randomProps.includes(targetProp) ? randomProps : [...randomProps, targetProp]; const constructed = { isUnique: proxy.filter(x => targetProps.every(key => x && x.hasOwnProperty(key))).length == 1, recipe: targetProps }; if (constructed.isUnique) + // return constructed; + { + if (constructed.recipe.length > 4) { + let currentMaxPropCount = constructed.recipe.length - 2; + let last = { isUnique: false, recipe: [] } as SolveResult; + // for (let index2 = 0; index2 < 32; index2++) { + for (let index2 = 0; index2 < targetModuleKeys.length; index2++) { + const randomProps2 = utils.pickRandomProperties(targetModule, currentMaxPropCount); + const targetProps2 = randomProps2.includes(targetProp) ? randomProps2 : [...randomProps2, targetProp]; + // if (randomProps2.length == 0) + // continue; + const constructed2 = { + isUnique: proxy.filter(x => + targetProps2.every(key => x && x.hasOwnProperty(key))).length == 1, + recipe: targetProps2 + }; + currentMaxPropCount--; + if (constructed2.isUnique) { + last = constructed2; + } + else { + return last; + } + } + } return constructed; + } } return { isUnique: false, From 37616c1bf1dad98c1cdbef48a70199eb70dd2fc1 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 15 Apr 2024 03:20:49 +0200 Subject: [PATCH 03/20] Update index.ts --- index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/index.ts b/index.ts index 9f48004..592b3f8 100644 --- a/index.ts +++ b/index.ts @@ -20,7 +20,7 @@ const readdir = promisify(fs.readdir); // thankies shady. regex go brerrr const REPLACEMENT_REGEX = /(\[["']\w+.+?]).(\w+)/g; -const REPLACEMENT_REGEX2 = /\.([a-zA-Z]+\_\_?[a-zA-Z0-9_.-]+)\s\{/g; +const REPLACEMENT_REGEX2 = /\.([a-zA-Z]+\_\_?[a-zA-Z0-9_.-]+)\s?\{/g; function replaceClassNamesByRegex(cssString: string, jsonFile: { [key: string]: string }[]): string { return cssString.replace(REPLACEMENT_REGEX, (match, group1: string, group2) => { @@ -96,7 +96,6 @@ async function startConverting(inputFilePath: string, optionalFilePath: string): async function startReverseConverting(inputFilePath: string): Promise { const fileName = path.basename(inputFilePath, path.extname(inputFilePath)) + ".raw"; const outputPath = path.join("./", fileName); - try { if (!fs.existsSync(inputFilePath)) { fs.writeFileSync(inputFilePath, ''); From 67e7c50bc7973bb533f9f47e0c7eab06ca401fba Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 15 Apr 2024 19:30:08 +0200 Subject: [PATCH 04/20] Fix return when module is missing when using `first` --- assets/fetcher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/fetcher.ts b/assets/fetcher.ts index 0f76279..0eda4ca 100644 --- a/assets/fetcher.ts +++ b/assets/fetcher.ts @@ -252,7 +252,7 @@ export async function fetchFullDiscordCSSDefinitions(reverseMode = false) { return evenMoreFinalResult; } } - return undefined; + return first ? undefined : []; }, get find() { return (...args: any[]) => this.filter(args[0], args[1], true); From d1b91a843140aabb73c84bd2ca107a1e6b80a721 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 15 Apr 2024 21:50:43 +0200 Subject: [PATCH 05/20] Make directory structure for reverse conversion --- index.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/index.ts b/index.ts index 592b3f8..f562df9 100644 --- a/index.ts +++ b/index.ts @@ -93,9 +93,15 @@ async function startConverting(inputFilePath: string, optionalFilePath: string): } } -async function startReverseConverting(inputFilePath: string): Promise { +async function startReverseConverting(inputFilePath: string, basePath: string): Promise { const fileName = path.basename(inputFilePath, path.extname(inputFilePath)) + ".raw"; - const outputPath = path.join("./", fileName); + const inputParsed = path.relative(basePath, path.parse(inputFilePath).dir); + console.log(inputParsed); + const outputFolder = 'reverse-build/' + inputParsed; + if (!fs.existsSync(outputFolder)) { + fs.mkdirSync(outputFolder, { recursive: true }); + } + const outputPath = path.join(outputFolder, fileName); try { if (!fs.existsSync(inputFilePath)) { fs.writeFileSync(inputFilePath, ''); @@ -153,12 +159,12 @@ if (inputPath) { if (mode == 0) startConverting(element, optionalFilePath); else if (mode == 1) - startReverseConverting(element); + startReverseConverting(element, resolvedPath); } })(); } else if (mode == 0) startConverting(resolvedPath, optionalFilePath); else if (mode == 1) - startReverseConverting(resolvedPath); + startReverseConverting(resolvedPath, path.resolve("./")); } From c0fa2866b2fa0c3592656f43be8b0f860c4bbdaa Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 16 Apr 2024 02:23:23 +0200 Subject: [PATCH 06/20] Update index.ts --- index.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/index.ts b/index.ts index f562df9..4f4a0a1 100644 --- a/index.ts +++ b/index.ts @@ -20,7 +20,7 @@ const readdir = promisify(fs.readdir); // thankies shady. regex go brerrr const REPLACEMENT_REGEX = /(\[["']\w+.+?]).(\w+)/g; -const REPLACEMENT_REGEX2 = /\.([a-zA-Z]+\_\_?[a-zA-Z0-9_.-]+)\s?\{/g; +const REPLACEMENT_REGEX2 = /\.([a-zA-Z]+\_\_?[a-zA-Z0-9_.-]+)/g; function replaceClassNamesByRegex(cssString: string, jsonFile: { [key: string]: string }[]): string { return cssString.replace(REPLACEMENT_REGEX, (match, group1: string, group2) => { @@ -48,7 +48,8 @@ function reverseLookup(realClassName: string, cssDefs: { [key: string]: string } return false; }); if (!targetModule) - throw new Error(`Reverse lookup for ${realClassName} failed.`); + // throw new Error(`Reverse lookup for ${realClassName} failed.`); + return null; const targetProp = Object.keys(targetModule).find(x => targetModule[x] == realClassName); return { recipe: fetcher.conflictSolver(targetModule, targetProp!, targetModuleId!), targetProp }; } @@ -59,7 +60,7 @@ async function replaceClassNamesByRegexInReverse(cssString: string, cssDefs: { [ const output = reverseLookup(modifiedGroup, cssDefs); if (output == null) return match; - return `.${JSON.stringify(await output.recipe)}.${output.targetProp} {`; + return `.${JSON.stringify(await output.recipe)}.${output.targetProp}`; }); } From 3d1e747575364b4d609233c1817fbe6ec6d5d8a6 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 17 Apr 2024 18:30:06 +0200 Subject: [PATCH 07/20] Support classes that when reversed, have spaces in them --- assets/fetcher.ts | 8 ++++---- assets/utils.ts | 3 +++ index.ts | 43 +++++++++++++++++++++++++++++++++++-------- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/assets/fetcher.ts b/assets/fetcher.ts index 0eda4ca..b418a1d 100644 --- a/assets/fetcher.ts +++ b/assets/fetcher.ts @@ -111,7 +111,7 @@ const lock = { }, } -export async function fetchFullDiscordCSSDefinitions(reverseMode = false) { +export async function fetchFullDiscordCSSDefinitions(reverseMode = false, enableRegexp = false) { await lock.promise.promise; if (cachedScripts.length == 0 && lock.locked == false) { lock.locked = true; @@ -202,12 +202,12 @@ export async function fetchFullDiscordCSSDefinitions(reverseMode = false) { for (let index = 0; index < evaluatedScripts.length; index++) { const evaluatedScript = evaluatedScripts[index]; const modules = Object.keys(evaluatedScript.value); - const fakeArray: { module: string; hasOwnProperty(prop: string): boolean; }[] = []; + const fakeArray: { module: string; hasOwnProperty(prop: string, customRegex: RegExp): boolean; }[] = []; modules.forEach((x) => { const fakeObject = { module: "", - hasOwnProperty(prop: string) { // and this is, kids, why you don't do something.hasOwnProperty() but Object.prototype.hasOwnProperty.call(something) - const regex = new RegExp(reverseMode ? MODULE_PROP_MATCHER_REGEX(prop) : MODULE_VALUE_MATCHER_REGEX(prop), "g"); + hasOwnProperty(prop: string, customRegex?: RegExp) { // and this is, kids, why you don't do something.hasOwnProperty() but Object.prototype.hasOwnProperty.call(something) + const regex = enableRegexp && customRegex != undefined ? customRegex : new RegExp(reverseMode ? MODULE_PROP_MATCHER_REGEX(prop) : MODULE_VALUE_MATCHER_REGEX(prop), "g"); if (regex.test(evaluatedScript.value[x].toString())) { this.module = x; return true; diff --git a/assets/utils.ts b/assets/utils.ts index 7a51c30..c67fedd 100644 --- a/assets/utils.ts +++ b/assets/utils.ts @@ -55,3 +55,6 @@ export function getDeferred() { return { resolve, reject, promise }; } +export function escapeRegExp(string: string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} diff --git a/index.ts b/index.ts index 4f4a0a1..4607c9d 100644 --- a/index.ts +++ b/index.ts @@ -3,7 +3,7 @@ import * as fs from 'fs'; import * as path from 'path'; import { promisify } from 'util'; import { EvaluatedScript } from "./assets/types"; -import { pickRandomProperties, replaceAsync } from './assets/utils'; +import { escapeRegExp, pickRandomProperties, replaceAsync } from './assets/utils'; const writeFileAsync = promisify(fs.writeFile); const readdir = promisify(fs.readdir); @@ -38,10 +38,16 @@ function replaceClassNamesByRegex(cssString: string, jsonFile: { [key: string]: }); } -function reverseLookup(realClassName: string, cssDefs: { [key: string]: string }[]) { +const prepareMatcherRegexForReverseLookup = (realClassName: string) => `(${escapeRegExp(realClassName)}(\\s?\\w+)\\4?)`; + +function reverseLookup(realClassName: string, cssDefs: { [key: string]: string }[], matcherRegex = new RegExp(`({|,)(\\w+):"${prepareMatcherRegexForReverseLookup(realClassName)}"`, "g")) { let targetModuleId: string | null = null; const targetModule = cssDefs.find(x => { - if (x.hasOwnProperty(realClassName)) { + if ( + x.hasOwnProperty(realClassName) || + // @ts-expect-error + x.hasOwnProperty(realClassName, matcherRegex) + ) { targetModuleId = x.module; return true; } @@ -50,17 +56,38 @@ function reverseLookup(realClassName: string, cssDefs: { [key: string]: string } if (!targetModule) // throw new Error(`Reverse lookup for ${realClassName} failed.`); return null; - const targetProp = Object.keys(targetModule).find(x => targetModule[x] == realClassName); + const targetProp = Object.keys(targetModule).find(x => targetModule[x] == realClassName || new RegExp(prepareMatcherRegexForReverseLookup(realClassName)).test(targetModule[x])); return { recipe: fetcher.conflictSolver(targetModule, targetProp!, targetModuleId!), targetProp }; } async function replaceClassNamesByRegexInReverse(cssString: string, cssDefs: { [key: string]: string }[]) { return await replaceAsync(cssString, REPLACEMENT_REGEX2, async (match, group1: string) => { + // const modifiedGroup = group1.replace('.', ' '); + // const output = reverseLookup(modifiedGroup, cssDefs); + // if (output == null) + // return match; + // return `.${JSON.stringify(await output.recipe)}.${output.targetProp}`; const modifiedGroup = group1.replace('.', ' '); + const splitBySpaces = modifiedGroup.split(" "); const output = reverseLookup(modifiedGroup, cssDefs); - if (output == null) - return match; - return `.${JSON.stringify(await output.recipe)}.${output.targetProp}`; + if (splitBySpaces.length < 2) { + if (output == null) { + return match; + } + return `.${JSON.stringify(await output.recipe)}.${output.targetProp}`; + } + if (output != null) + return `.${JSON.stringify(await output.recipe)}.${output.targetProp}`; + let result = ""; + for (let index = 0; index < splitBySpaces.length; index++) { + const element = splitBySpaces[index]; + console.log(element); + const outputLocal = reverseLookup(element, cssDefs); + if (outputLocal != null) { + result += `.${JSON.stringify(await outputLocal.recipe)}.${outputLocal.targetProp}` + } + } + return result; }); } @@ -109,7 +136,7 @@ async function startReverseConverting(inputFilePath: string, basePath: string): } const cssString = await fs.promises.readFile(inputFilePath, 'utf8'); - const cssDefs = await fetcher.fetchFullDiscordCSSDefinitions(true); + const cssDefs = await fetcher.fetchFullDiscordCSSDefinitions(true, true); const updatedCSS = await replaceClassNamesByRegexInReverse(cssString, cssDefs); const fileExtension = ".css"; From 057e008dd54d84c5891b546e7d2fb4d1aa055eef Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 17 Apr 2024 18:46:30 +0200 Subject: [PATCH 08/20] Update index.ts --- index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/index.ts b/index.ts index 4607c9d..de2aa3b 100644 --- a/index.ts +++ b/index.ts @@ -38,9 +38,9 @@ function replaceClassNamesByRegex(cssString: string, jsonFile: { [key: string]: }); } -const prepareMatcherRegexForReverseLookup = (realClassName: string) => `(${escapeRegExp(realClassName)}(\\s?\\w+)\\4?)`; +const prepareMatcherRegexForReverseLookup = (realClassName: string, calculatedGroup: number) => `(${escapeRegExp(realClassName)}(\\s?\\w+)\\${calculatedGroup}?)`; -function reverseLookup(realClassName: string, cssDefs: { [key: string]: string }[], matcherRegex = new RegExp(`({|,)(\\w+):"${prepareMatcherRegexForReverseLookup(realClassName)}"`, "g")) { +function reverseLookup(realClassName: string, cssDefs: { [key: string]: string }[], matcherRegex = new RegExp(`({|,)(\\w+):"${prepareMatcherRegexForReverseLookup(realClassName, 4)}"`, "g")) { let targetModuleId: string | null = null; const targetModule = cssDefs.find(x => { if ( @@ -53,10 +53,12 @@ function reverseLookup(realClassName: string, cssDefs: { [key: string]: string } } return false; }); - if (!targetModule) + if (!targetModule) { + console.log(`Reverse lookup for ${realClassName} failed.`); // throw new Error(`Reverse lookup for ${realClassName} failed.`); return null; - const targetProp = Object.keys(targetModule).find(x => targetModule[x] == realClassName || new RegExp(prepareMatcherRegexForReverseLookup(realClassName)).test(targetModule[x])); + } + const targetProp = Object.keys(targetModule).find(x => targetModule[x] == realClassName || new RegExp(prepareMatcherRegexForReverseLookup(realClassName, 2), "g").test(targetModule[x])); return { recipe: fetcher.conflictSolver(targetModule, targetProp!, targetModuleId!), targetProp }; } From 949ce328623b5ed08af246a75acc9ca7ccc38164 Mon Sep 17 00:00:00 2001 From: hsiF <90235641+zrodevkaan@users.noreply.github.com> Date: Thu, 18 Apr 2024 17:50:47 -0500 Subject: [PATCH 09/20] README enhancement, make args order not matter for --reverse (#21) * uwu read me + --reverse fix * k * Make the args removal correct --------- Co-authored-by: Daniel --- README.md | 8 ++++++++ index.ts | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a627bb8..c08c4a7 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,18 @@ pnpm i // Example: npx ts-node index.ts test (ext) // Example: npx ts-node index.ts test.css (ext) + // Example Reverse: npx ts-node index.ts --reverse test.css (ext) + // Reverse is the action of taking a theme and turning it into our format with correct props + // Our system will automatically apply .css // The file needs to be in the same dir of the project ``` +# Why use this? +This project was born out of a determination to find a smoother path for theme developers. Instead of constantly chasing Discord's updates and renaming classes, I set out to discover the most efficient solution. + +Enter the .['sizeLarge'] format: a simple yet powerful method that slashes the time and effort needed to locate specific class names. It's the result of dedicated tinkering and a commitment to making life easier for developers. + # Template This is how the file should be structured for full functionality diff --git a/index.ts b/index.ts index de2aa3b..a786b80 100644 --- a/index.ts +++ b/index.ts @@ -152,9 +152,10 @@ async function startReverseConverting(inputFilePath: string, basePath: string): const args: string[] = process.argv.slice(2); +console.log(args) const mode = args.includes("--reverse") ? 1 : 0; if (mode != 0) - args.shift(); + args.splice(args.indexOf("--reverse"), 1); if (args.includes("--help") || args.length == 0) { console.error('Usage:\n\tnpx ts-node index.ts '); process.exit(1); From f7bb4e416ea12e052e3d68f4d423a5d7687cef20 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 1 May 2024 00:28:49 +0200 Subject: [PATCH 10/20] Add index system to conversion For situations where there are multiple matches but it's impossible to narrow the search --- index.ts | 16 ++++++++++++++-- test.css | 2 ++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/index.ts b/index.ts index a786b80..941de3a 100644 --- a/index.ts +++ b/index.ts @@ -19,17 +19,29 @@ const readdir = promisify(fs.readdir); }*/ // thankies shady. regex go brerrr -const REPLACEMENT_REGEX = /(\[["']\w+.+?]).(\w+)/g; +const REPLACEMENT_REGEX = /(\[["']\w+.+?])(\[\d\])?.(\w+)/g; const REPLACEMENT_REGEX2 = /\.([a-zA-Z]+\_\_?[a-zA-Z0-9_.-]+)/g; function replaceClassNamesByRegex(cssString: string, jsonFile: { [key: string]: string }[]): string { - return cssString.replace(REPLACEMENT_REGEX, (match, group1: string, group2) => { + return cssString.replace(REPLACEMENT_REGEX, (match, group1: string, index: string, group2) => { const modifiedGroup = group1.replace(/'/g, "\""); // JSON.parse can't parse single quotes....? const rawProps: string[] = JSON.parse(modifiedGroup); // too lazy const toExcludeProps: string[] = []; const targetProps = rawProps.filter(x => x.startsWith("!") ? toExcludeProps.push(x) && false : true); console.log(match, targetProps); + if (index != undefined) { + const availableClassNames = jsonFile.filter(x => targetProps.every(key => x && x.hasOwnProperty(key)) && !toExcludeProps.some(key => x && x.hasOwnProperty(key.slice(1)))); + availableClassNames.sort((a, b) => { + const aValues = Object.values(a); + const bValues = Object.values(b); + return aValues.join().localeCompare(bValues.join()); + }); + if (availableClassNames.length > 0) { + return availableClassNames[JSON.parse(index)[0]][group2].replace(' ', '.'); + } + return match; + } const targetClassName = jsonFile.find(x => targetProps.every(key => x && x.hasOwnProperty(key)) && !toExcludeProps.some(key => x && x.hasOwnProperty(key.slice(1)))); if (targetClassName) { return targetClassName[group2].replace(' ', '.'); diff --git a/test.css b/test.css index ff4d232..e5cb198 100644 --- a/test.css +++ b/test.css @@ -8,3 +8,5 @@ .["emojiContainer","emojiContainerClickable"].emojiContainer { color: #000; } .["emojiContainer","!footer", "!emojiContainerClickable"].emojiContainer { color: #000; } + +.["scrollerBase"][0].scrollerBase From a17dbd3174e75d1afdeb2efb95c70bfb0748bc2c Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 1 May 2024 16:03:08 +0200 Subject: [PATCH 11/20] Fix for more than one digit --- index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.ts b/index.ts index 941de3a..af0be04 100644 --- a/index.ts +++ b/index.ts @@ -19,8 +19,8 @@ const readdir = promisify(fs.readdir); }*/ // thankies shady. regex go brerrr -const REPLACEMENT_REGEX = /(\[["']\w+.+?])(\[\d\])?.(\w+)/g; const REPLACEMENT_REGEX2 = /\.([a-zA-Z]+\_\_?[a-zA-Z0-9_.-]+)/g; +const REPLACEMENT_REGEX = /(\[["']\w+.+?])(\[\d+\])?.(\w+)/g; function replaceClassNamesByRegex(cssString: string, jsonFile: { [key: string]: string }[]): string { return cssString.replace(REPLACEMENT_REGEX, (match, group1: string, index: string, group2) => { From d7ae5797ab2c173afa8c483e6ba54578253aa0d9 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 1 May 2024 16:04:15 +0200 Subject: [PATCH 12/20] Support for digits in reversing class name --- index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.ts b/index.ts index af0be04..1eb8493 100644 --- a/index.ts +++ b/index.ts @@ -19,8 +19,8 @@ const readdir = promisify(fs.readdir); }*/ // thankies shady. regex go brerrr -const REPLACEMENT_REGEX2 = /\.([a-zA-Z]+\_\_?[a-zA-Z0-9_.-]+)/g; const REPLACEMENT_REGEX = /(\[["']\w+.+?])(\[\d+\])?.(\w+)/g; +const REPLACEMENT_REGEX2 = /\.([a-zA-Z0-9]+\_\_?[a-zA-Z0-9_.-]+)/g; function replaceClassNamesByRegex(cssString: string, jsonFile: { [key: string]: string }[]): string { return cssString.replace(REPLACEMENT_REGEX, (match, group1: string, index: string, group2) => { From d4946034f0aa733806e75024504535c41ac444c3 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 1 May 2024 20:35:08 +0200 Subject: [PATCH 13/20] Fix dots not converted to spaces --- index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.ts b/index.ts index f950191..b5d79a8 100644 --- a/index.ts +++ b/index.ts @@ -81,7 +81,7 @@ async function replaceClassNamesByRegexInReverse(cssString: string, cssDefs: { [ // if (output == null) // return match; // return `.${JSON.stringify(await output.recipe)}.${output.targetProp}`; - const modifiedGroup = group1.replace('.', ' '); + const modifiedGroup = group1.replace(/\./g, ' '); const splitBySpaces = modifiedGroup.split(" "); const output = reverseLookup(modifiedGroup, cssDefs); if (splitBySpaces.length < 2) { From 9b522e43db3a7d756f8ce7b1d6336074bf063e46 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 1 May 2024 20:59:08 +0200 Subject: [PATCH 14/20] Update conflictSolver.ts --- assets/conflictSolver.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/assets/conflictSolver.ts b/assets/conflictSolver.ts index 024a29a..5d2231b 100644 --- a/assets/conflictSolver.ts +++ b/assets/conflictSolver.ts @@ -70,13 +70,23 @@ export default async function solver(targetModule: { [key: string]: any }, targe const targetAmountOfPropertiesToPick = ((targetModuleKeys.length % 2 == 0) ? targetModuleKeys.length : targetModuleKeys.length - 1); const randomProps = utils.pickRandomProperties(targetModule, targetAmountOfPropertiesToPick > 1 ? (targetAmountOfPropertiesToPick / 2) : targetAmountOfPropertiesToPick); */ - for (let index = 0; index < Object.keys(stages).length; index++) { + const stageCount = Object.keys(stages).length; + // let lastResult: SolveResult | null = null; + for (let index = 0; index < stageCount; index++) { const currentStage = stages[index]; + console.log("Now running stage", index + 1); const result = await currentStage(targetModule, targetProp, targetModuleId); if (result.isUnique == true) { return result.recipe; } - console.log("Next stage.", targetModule, targetProp); + // lastResult = result; + console.log("Next stage.", `Now ${index + 1}/${stageCount}`, targetModule, targetProp); } + console.log(targetModule, targetProp, targetModuleId); + /* const proxy = await fetcher.fetchFullDiscordCSSDefinitions(); + const res = proxy.filter(x => lastResult!.recipe.filter(x => !x.startsWith("!")).every(key => x && x.hasOwnProperty(key)) && !lastResult!.recipe.filter(x => x.startsWith("!")).some(key => x && x.hasOwnProperty(key.slice(1)))) + console.log(res); + process.exit(1); */ + // return null; return null; } From 6e744680ef3bccad181ac58327f94fc93c02cacb Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 1 May 2024 21:01:20 +0200 Subject: [PATCH 15/20] Fix a bug where filter returns only modules from first script --- assets/fetcher.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/assets/fetcher.ts b/assets/fetcher.ts index b418a1d..d300a65 100644 --- a/assets/fetcher.ts +++ b/assets/fetcher.ts @@ -199,6 +199,7 @@ export async function fetchFullDiscordCSSDefinitions(reverseMode = false, enable const result = { evaluatedScripts, filter(predicate: (element: any) => boolean, thisArg?: any, first: boolean = false) { + const evenMoreFinalResult = []; for (let index = 0; index < evaluatedScripts.length; index++) { const evaluatedScript = evaluatedScripts[index]; const modules = Object.keys(evaluatedScript.value); @@ -221,7 +222,7 @@ export async function fetchFullDiscordCSSDefinitions(reverseMode = false, enable const foundOrNot = fakeArray.filter(predicate); fakeArray.length = 0; if (foundOrNot.length > 0) { - const evenMoreFinalResult = []; + // const evenMoreFinalResult = []; for (let index2 = 0; index2 < foundOrNot.length; index2++) { const fakeRequire = (id: string) => { const fakeWebpack2 = { exports: undefined as unknown as { [key: string]: string } }; @@ -249,10 +250,11 @@ export async function fetchFullDiscordCSSDefinitions(reverseMode = false, enable return cache[foundModule]; evenMoreFinalResult.push(cache[foundModule]); } - return evenMoreFinalResult; + // return evenMoreFinalResult; + continue; } } - return first ? undefined : []; + return first ? undefined : evenMoreFinalResult; }, get find() { return (...args: any[]) => this.filter(args[0], args[1], true); From 0d42f9bafe1f87d4a8d4a6f85dfb5bfbdc91dc7b Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 1 May 2024 21:01:36 +0200 Subject: [PATCH 16/20] Update fetcher.ts --- assets/fetcher.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/assets/fetcher.ts b/assets/fetcher.ts index d300a65..31bf1cc 100644 --- a/assets/fetcher.ts +++ b/assets/fetcher.ts @@ -243,6 +243,9 @@ export async function fetchFullDiscordCSSDefinitions(reverseMode = false, enable if (cache[foundModule] == undefined) { const builder = evaluatedScript.value[foundModule]; const fakeWebpack = { exports: undefined as unknown as { [key: string]: string } }; + if (builder == undefined || typeof builder != "function") { + console.log(foundOrNot[index2], builder, predicate.toString()); + } builder(fakeWebpack, undefined, fakeRequire); cache[foundModule] = fakeWebpack.exports; } From ed70191f0b8bca312168dbc82d799b7aa204fc40 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 1 May 2024 21:02:45 +0200 Subject: [PATCH 17/20] Implement hasOwnProperty return value cache --- assets/fetcher.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/assets/fetcher.ts b/assets/fetcher.ts index 31bf1cc..321496c 100644 --- a/assets/fetcher.ts +++ b/assets/fetcher.ts @@ -196,6 +196,7 @@ export async function fetchFullDiscordCSSDefinitions(reverseMode = false, enable } */ const cache: { [key: string]: any } = {}; + const cache2: { [key: string]: { propName: string, value: boolean } } = {}; const result = { evaluatedScripts, filter(predicate: (element: any) => boolean, thisArg?: any, first: boolean = false) { @@ -206,14 +207,27 @@ export async function fetchFullDiscordCSSDefinitions(reverseMode = false, enable const fakeArray: { module: string; hasOwnProperty(prop: string, customRegex: RegExp): boolean; }[] = []; modules.forEach((x) => { const fakeObject = { - module: "", + module: x, hasOwnProperty(prop: string, customRegex?: RegExp) { // and this is, kids, why you don't do something.hasOwnProperty() but Object.prototype.hasOwnProperty.call(something) + if (cache2[this.module] && cache2[this.module].propName == prop) + return cache2[this.module].value; + // console.log(prop, new Error()); const regex = enableRegexp && customRegex != undefined ? customRegex : new RegExp(reverseMode ? MODULE_PROP_MATCHER_REGEX(prop) : MODULE_VALUE_MATCHER_REGEX(prop), "g"); if (regex.test(evaluatedScript.value[x].toString())) { - this.module = x; - return true; + // console.log(regex); + // console.log(evaluatedScript.value[x].toString()); + // console.log(evaluatedScript.value[x].toString().match(regex)); + if (!enableRegexp) { + cache2[this.module] = { propName: prop, value: true }; + return cache2[this.module].value; + } + else return true; + } + if (!enableRegexp) { + cache2[this.module] = { propName: prop, value: false }; + return cache2[this.module].value; } - return false; + else return false; } }; fakeArray.push(fakeObject); From 2bc75e5ccdda0755b272915a6c6986c17d4ef4d4 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 1 May 2024 21:03:47 +0200 Subject: [PATCH 18/20] Uhhhh fix --- index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.ts b/index.ts index b5d79a8..e403de5 100644 --- a/index.ts +++ b/index.ts @@ -50,7 +50,7 @@ function replaceClassNamesByRegex(cssString: string, jsonFile: { [key: string]: }); } -const prepareMatcherRegexForReverseLookup = (realClassName: string, calculatedGroup: number) => `(${escapeRegExp(realClassName)}(\\s?\\w+)\\${calculatedGroup}?)`; +const prepareMatcherRegexForReverseLookup = (realClassName: string, _calculatedGroup: number) => `(${escapeRegExp(realClassName)}(\\s?\\w+)*)`; function reverseLookup(realClassName: string, cssDefs: { [key: string]: string }[], matcherRegex = new RegExp(`({|,)(\\w+):"${prepareMatcherRegexForReverseLookup(realClassName, 4)}"`, "g")) { let targetModuleId: string | null = null; From 8011ab5c8e1f5546c55bcdf22eb65dccdfaa72f5 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 1 May 2024 21:05:00 +0200 Subject: [PATCH 19/20] Make reverse search match only key start with the real one --- index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/index.ts b/index.ts index e403de5..a34c1e2 100644 --- a/index.ts +++ b/index.ts @@ -54,6 +54,7 @@ const prepareMatcherRegexForReverseLookup = (realClassName: string, _calculatedG function reverseLookup(realClassName: string, cssDefs: { [key: string]: string }[], matcherRegex = new RegExp(`({|,)(\\w+):"${prepareMatcherRegexForReverseLookup(realClassName, 4)}"`, "g")) { let targetModuleId: string | null = null; + // console.log(matcherRegex, realClassName); const targetModule = cssDefs.find(x => { if ( x.hasOwnProperty(realClassName) || @@ -70,8 +71,10 @@ function reverseLookup(realClassName: string, cssDefs: { [key: string]: string } // throw new Error(`Reverse lookup for ${realClassName} failed.`); return null; } - const targetProp = Object.keys(targetModule).find(x => targetModule[x] == realClassName || new RegExp(prepareMatcherRegexForReverseLookup(realClassName, 2), "g").test(targetModule[x])); + const targetProp = Object.keys(targetModule).find(x => targetModule[x] == realClassName || new RegExp("^" + prepareMatcherRegexForReverseLookup(realClassName, 2), "g").test(targetModule[x])); + console.log(targetModule); return { recipe: fetcher.conflictSolver(targetModule, targetProp!, targetModuleId!), targetProp }; + // return { recipe: Promise.resolve([Object.keys(targetModule), undefined] as [string[], number | undefined]), targetProp }; } async function replaceClassNamesByRegexInReverse(cssString: string, cssDefs: { [key: string]: string }[]) { From a0468433fac60a5e7ea1b0f35cfcf378e9d807d8 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 1 May 2024 21:06:57 +0200 Subject: [PATCH 20/20] Make use of index system and add exclusion layer --- assets/conflictSolver.ts | 110 ++++++++++++++++++++++++++++++++++++++- assets/utils.ts | 17 +++++- index.ts | 21 +++++--- 3 files changed, 140 insertions(+), 8 deletions(-) diff --git a/assets/conflictSolver.ts b/assets/conflictSolver.ts index 5d2231b..3aa5cce 100644 --- a/assets/conflictSolver.ts +++ b/assets/conflictSolver.ts @@ -4,6 +4,7 @@ import * as fetcher from "./fetcher" type SolveResult = { isUnique: boolean, recipe: string[], + index?: number, }; const stages: { [key: number]: (targetModule: { [key: string]: any }, targetProp: string, targetModuleId: string) => Promise } = { @@ -62,6 +63,112 @@ const stages: { [key: number]: (targetModule: { [key: string]: any }, targetProp recipe: [], }; }, + 2: async (targetModule, targetProp) => { + const proxy = await fetcher.fetchFullDiscordCSSDefinitions(); + const found = proxy.filter(x => utils.haveSameKeys(x, targetModule)); + console.log("found", found, "targetModule", targetModule); + if (!found.every(x => Object.keys(x).length == Object.keys(targetModule).length)) + return { isUnique: false, recipe: [...Object.keys(targetModule)], index: found.findIndex(x => x[targetProp] == targetModule[targetProp]) }; + // if (found.length > 0) { + // console.log(found, targetModule, targetProp); + // } + found.sort((a, b) => { + const aValues = Object.values(a); + const bValues = Object.values(b); + return aValues.join().localeCompare(bValues.join()); + }); + return { + isUnique: found.length > 0 && found.findIndex(x => x[targetProp] == targetModule[targetProp]) != -1, + recipe: [...Object.keys(targetModule)], + index: found.findIndex(x => x[targetProp] == targetModule[targetProp]), + }; + }, + 3: async (targetModule, targetProp) => { + const proxy = await fetcher.fetchFullDiscordCSSDefinitions(); + // since we are at this stage, we need to assume there are a lot of results + const allThatContainTheTargetProp = proxy.filter(x => { + return x.hasOwnProperty(targetProp); + }); + const targetModuleKeys = Object.keys(targetModule); + const notInTargetFinal: string[] = []; + // console.log(allThatContainTheTargetProp); + for (let index = 0; index < allThatContainTheTargetProp.length; index++) { + const current = allThatContainTheTargetProp[index]; + const currentModuleKeys = Object.keys(current); + const notInTarget = currentModuleKeys.filter(key => !targetModuleKeys.includes(key)); + notInTargetFinal.push(...notInTarget); + } + if (notInTargetFinal.length > 2) { + let currentMaxPropCount = notInTargetFinal.length - 2; + const targetAmountOfPropertiesToPick = ((targetModuleKeys.length % 2 == 0) ? targetModuleKeys.length : targetModuleKeys.length - 1); + let last = { + isUnique: proxy.filter(x => + x && x.hasOwnProperty(targetProp) && !notInTargetFinal.some(key => x && x.hasOwnProperty(key))).length == 1, + recipe: [targetProp, ...notInTargetFinal.map(x => "!" + x)], + } as SolveResult; + console.log(last.isUnique, last.recipe, proxy.filter(x => + x && x.hasOwnProperty(targetProp) && !notInTargetFinal.some(key => x && x.hasOwnProperty(key)))); + // process.exit(1); + // return { isUnique: false, recipe: [] }; + for (let index = 0; index < notInTargetFinal.length; index++) { + const randomIndices = utils.pickRandomIndicesFromArr(notInTargetFinal, Math.max(0, Math.min(currentMaxPropCount, notInTargetFinal.length))); + const randomProps = utils.pickRandomProperties(targetModule, targetAmountOfPropertiesToPick > 1 ? (targetAmountOfPropertiesToPick / 2) : targetAmountOfPropertiesToPick); + const targetProps = randomProps.includes(targetProp) ? randomProps : [...randomProps, targetProp]; + + currentMaxPropCount++; + console.log("predicted", targetProps, proxy.filter(x => targetProps.every(key => x && x.hasOwnProperty(key)) && + !randomIndices.some(randomIndex => x && x.hasOwnProperty(notInTargetFinal[randomIndex])))); + const constructed2 = { + isUnique: proxy.filter(x => targetProps.every(key => x && x.hasOwnProperty(key)) && + !randomIndices.some(randomIndex => x && x.hasOwnProperty(notInTargetFinal[randomIndex]))).length == 1, + recipe: [targetProp, ...randomIndices.map(x => notInTargetFinal[x]).map(x => "!" + x)], + }; + const otherMatches = proxy.filter(x => targetProps.every(key => x && x.hasOwnProperty(key)) && + !randomIndices.some(randomIndex => x && x.hasOwnProperty(notInTargetFinal[randomIndex]))); + let allKeysSame = true; + for (let index2 = 0; index2 < otherMatches.length; index2++) { + const element = otherMatches[index2]; + if (!utils.haveSameKeys(element, targetModule) || Object.keys(otherMatches[index2]).length != targetModuleKeys.length) + allKeysSame = false; + } + if (allKeysSame && otherMatches.length > 1) { + otherMatches.sort((a, b) => { + const aValues = Object.values(a); + const bValues = Object.values(b); + return aValues.join().localeCompare(bValues.join()); + }); + // console.log(otherMatches, otherMatches.filter(x => x[targetProp]), targetModule[targetProp]); + // console.log("test", proxy.filter(x => x.hasOwnProperty("container")).filter(x=>x[targetProp] == targetModule[targetProp])); + return { + isUnique: true, + index: otherMatches.findIndex(x => x[targetProp] == targetModule[targetProp]), + recipe: [...targetProps, ...notInTargetFinal.map(x => "!" + x)], + }; + } + console.log("Constructed2:", constructed2, "Not in target:", notInTargetFinal); + if (constructed2.isUnique) { + last = constructed2; + break; + } + // else { + // console.log("Returning last", last); + // return last; + // } + } + console.log("Returning last 2", last); + return last; + } + const calculatedIsUnique = proxy.filter(x => + x && x.hasOwnProperty(targetProp) && !notInTargetFinal.some(key => x && x.hasOwnProperty(key))).length == 1; + console.log(notInTargetFinal, { + isUnique: calculatedIsUnique, + recipe: [targetProp, ...notInTargetFinal.map(x => "!" + x)], + }); + return { + isUnique: calculatedIsUnique, + recipe: [targetProp, ...notInTargetFinal.map(x => "!" + x)], + } + }, }; export default async function solver(targetModule: { [key: string]: any }, targetProp: string, targetModuleId: string) { @@ -77,7 +184,8 @@ export default async function solver(targetModule: { [key: string]: any }, targe console.log("Now running stage", index + 1); const result = await currentStage(targetModule, targetProp, targetModuleId); if (result.isUnique == true) { - return result.recipe; + console.log("Found unique recipe."); + return [result.recipe, result.index] as [string[], number | undefined]; } // lastResult = result; console.log("Next stage.", `Now ${index + 1}/${stageCount}`, targetModule, targetProp); diff --git a/assets/utils.ts b/assets/utils.ts index c67fedd..75993e0 100644 --- a/assets/utils.ts +++ b/assets/utils.ts @@ -24,7 +24,7 @@ export function pickRandomProperties(obj: any, n: number) { const keys = Object.keys(obj); const randomKeys: string[] = []; if (n >= keys.length) { - return keys.map(key => obj[key]); + n--; } while (randomKeys.length < n) { @@ -35,6 +35,21 @@ export function pickRandomProperties(obj: any, n: number) { } return randomKeys; } +export function pickRandomIndicesFromArr(arr: T[], n: number) { + const randomKeys: number[] = []; + while (randomKeys.length < n) { + const randomKey = Math.floor(Math.random() * arr.length); + if (!randomKeys.includes(randomKey)) { + randomKeys.push(randomKey); + } + } + return randomKeys; +} +export function haveSameKeys(obj1: {}, obj2: {}) { + return Object.keys(obj2).every(function (prop) { + return obj1.hasOwnProperty(prop); + }); +} export async function replaceAsync(str: string, regex: any, asyncFn: (...args: any[]) => any) { const promises: any[] = []; str.replace(regex, (full, ...args) => { diff --git a/index.ts b/index.ts index a34c1e2..b28cda5 100644 --- a/index.ts +++ b/index.ts @@ -87,21 +87,30 @@ async function replaceClassNamesByRegexInReverse(cssString: string, cssDefs: { [ const modifiedGroup = group1.replace(/\./g, ' '); const splitBySpaces = modifiedGroup.split(" "); const output = reverseLookup(modifiedGroup, cssDefs); + const weUseIndexOrNo = + (indexOrNo: number | undefined) => + (indexOrNo == undefined ? "" : `[${indexOrNo}]`); + const cook = + async (out: { recipe: Promise<[string[], number | undefined] | null>; targetProp: string | undefined; }) => + `.${JSON.stringify((await out.recipe)![0])}${weUseIndexOrNo((await out.recipe)![1])}.${out.targetProp}`; if (splitBySpaces.length < 2) { - if (output == null) { + if (output == null || await output.recipe == null) { return match; } - return `.${JSON.stringify(await output.recipe)}.${output.targetProp}`; + return cook(output); } - if (output != null) - return `.${JSON.stringify(await output.recipe)}.${output.targetProp}`; + if (output != null && await output.recipe != null) + return cook(output); let result = ""; for (let index = 0; index < splitBySpaces.length; index++) { const element = splitBySpaces[index]; console.log(element); const outputLocal = reverseLookup(element, cssDefs); - if (outputLocal != null) { - result += `.${JSON.stringify(await outputLocal.recipe)}.${outputLocal.targetProp}` + if (outputLocal != null && await outputLocal.recipe != null) { + result += await cook(outputLocal); + } + else { + result += `.${element}`; } } return result;