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/README.md b/README.md index 50c90eb..302c217 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,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/assets/conflictSolver.ts b/assets/conflictSolver.ts new file mode 100644 index 0000000..3aa5cce --- /dev/null +++ b/assets/conflictSolver.ts @@ -0,0 +1,200 @@ +import * as utils from "./utils" +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 } = { + 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 < 6; index++) { + const randomProps = utils.pickRandomProperties(targetModule, targetAmountOfPropertiesToPick > 1 ? (targetAmountOfPropertiesToPick / 2) : targetAmountOfPropertiesToPick); + 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, + 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) { + /* + 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); + */ + 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) { + 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); + } + 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; +} diff --git a/assets/fetcher.ts b/assets/fetcher.ts index c76e1e2..321496c 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}`, { @@ -109,7 +111,7 @@ const lock = { }, } -export async function fetchFullDiscordCSSDefinitions() { +export async function fetchFullDiscordCSSDefinitions(reverseMode = false, enableRegexp = false) { await lock.promise.promise; if (cachedScripts.length == 0 && lock.locked == false) { lock.locked = true; @@ -194,58 +196,91 @@ export async function fetchFullDiscordCSSDefinitions() { } */ const cache: { [key: string]: any } = {}; + const cache2: { [key: string]: { propName: string, value: boolean } } = {}; const result = { evaluatedScripts, - find(predicate: (element: any) => boolean) { + 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); - 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(MODULE_MATCHER_REGEX(prop), "g"); + 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; } - return false; + if (!enableRegexp) { + cache2[this.module] = { propName: prop, value: false }; + return cache2[this.module].value; + } + else return false; } }; 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 } }; + if (builder == undefined || typeof builder != "function") { + console.log(foundOrNot[index2], builder, predicate.toString()); + } + builder(fakeWebpack, undefined, fakeRequire); + cache[foundModule] = fakeWebpack.exports; + } + if (first) + return cache[foundModule]; + evenMoreFinalResult.push(cache[foundModule]); } - return cache[foundModule]; + // return evenMoreFinalResult; + continue; } } - return undefined; + return first ? undefined : evenMoreFinalResult; }, + 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 8ac33dc..75993e0 100644 --- a/assets/utils.ts +++ b/assets/utils.ts @@ -18,8 +18,47 @@ 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) { + n--; + } + + while (randomKeys.length < n) { + const randomKey = keys[Math.floor(Math.random() * keys.length)]; + if (!randomKeys.includes(randomKey)) { + randomKeys.push(randomKey); + } + } + 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) => { + promises.push(asyncFn(full, ...args)); + return full; + }); + const data = await Promise.all(promises); + return str.replace(regex, () => data.shift()); +} export function getDeferred() { let resolve: undefined | ((arg: any) => void) = undefined; let reject: undefined | ((e?: Error) => void) = undefined; @@ -31,3 +70,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 e187c13..b28cda5 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 { escapeRegExp, pickRandomProperties, replaceAsync } from './assets/utils'; const writeFileAsync = promisify(fs.writeFile); const readdir = promisify(fs.readdir); @@ -18,16 +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-Z0-9]+\_\_?[a-zA-Z0-9_.-]+)/g; function replaceClassNamesByRegex(cssString: string, jsonFile: { [key: string]: string }[]): string { - return cssString.replace(REPLACEMENT_REGEX, (match, group1: string, group2) => { - const modifiedGroup = group1.replace(/'/g, "\""); + 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(/\s/g,'.'); @@ -36,6 +50,73 @@ function replaceClassNamesByRegex(cssString: string, jsonFile: { [key: string]: }); } +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; + // console.log(matcherRegex, realClassName); + const targetModule = cssDefs.find(x => { + if ( + x.hasOwnProperty(realClassName) || + // @ts-expect-error + x.hasOwnProperty(realClassName, matcherRegex) + ) { + targetModuleId = x.module; + return true; + } + return false; + }); + 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, 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 }[]) { + 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(/\./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 || await output.recipe == null) { + return match; + } + return cook(output); + } + 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 && await outputLocal.recipe != null) { + result += await cook(outputLocal); + } + else { + result += `.${element}`; + } + } + return result; + }); +} + async function startConverting(inputFilePath: string, optionalFilePath: string): Promise { const outputFolder = 'build'; const fileName = path.basename(inputFilePath, path.extname(inputFilePath)); @@ -66,8 +147,39 @@ async function startConverting(inputFilePath: string, optionalFilePath: string): } } +async function startReverseConverting(inputFilePath: string, basePath: string): Promise { + const fileName = path.basename(inputFilePath, path.extname(inputFilePath)) + ".raw"; + 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, ''); + } + + const cssString = await fs.promises.readFile(inputFilePath, 'utf8'); + const cssDefs = await fetcher.fetchFullDiscordCSSDefinitions(true, 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); +console.log(args) +const mode = args.includes("--reverse") ? 1 : 0; +if (mode != 0) + 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); @@ -85,6 +197,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 +211,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, resolvedPath); } })(); } - else + else if (mode == 0) startConverting(resolvedPath, optionalFilePath); + else if (mode == 1) + startReverseConverting(resolvedPath, path.resolve("./")); } 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