diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a3c9d04 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +config.json +package-lock.json +.vs/ +node_modules/ +dist/*.exe +dist/*.json \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d2f5657 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Discord Icon Changer +### Change your Desktop Discord Icon like on mobile! \ No newline at end of file diff --git a/dist/README.md b/dist/README.md new file mode 100644 index 0000000..feba1ba --- /dev/null +++ b/dist/README.md @@ -0,0 +1 @@ +# This folder is storing compilated files \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..6d79f68 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "DiscordIconChanger", + "version": "1.0.0", + "description": "Change your Discord taskbar icon from one of the new Custom Icons for Mobile", + "main": "src/index.js", + "output":"iconchanger.blob", + "scripts": { + "dev": "npm install && node .", + "start": "node .", + "build":"nexe . -r src/**/**/* -o \"dist/DiscordIconChanger.exe\" --build" + }, + "author": "ezzud", + "license": "MIT", + "dependencies": { + "edit-json-file": "^1.7.0", + "fs": "^0.0.1-security" + } +} diff --git a/src/assets/icons/Ceramic.ico b/src/assets/icons/Ceramic.ico new file mode 100644 index 0000000..efe2069 Binary files /dev/null and b/src/assets/icons/Ceramic.ico differ diff --git a/src/assets/icons/Charcoal.ico b/src/assets/icons/Charcoal.ico new file mode 100644 index 0000000..9f374e7 Binary files /dev/null and b/src/assets/icons/Charcoal.ico differ diff --git a/src/assets/icons/D64.ico b/src/assets/icons/D64.ico new file mode 100644 index 0000000..bb3a13b Binary files /dev/null and b/src/assets/icons/D64.ico differ diff --git a/src/assets/icons/Default.ico b/src/assets/icons/Default.ico new file mode 100644 index 0000000..bb7bfd3 Binary files /dev/null and b/src/assets/icons/Default.ico differ diff --git a/src/assets/icons/Fuming.ico b/src/assets/icons/Fuming.ico new file mode 100644 index 0000000..ba2ebf4 Binary files /dev/null and b/src/assets/icons/Fuming.ico differ diff --git a/src/assets/icons/Galactic Chrome.ico b/src/assets/icons/Galactic Chrome.ico new file mode 100644 index 0000000..26c0cfd Binary files /dev/null and b/src/assets/icons/Galactic Chrome.ico differ diff --git a/src/assets/icons/Gaming.ico b/src/assets/icons/Gaming.ico new file mode 100644 index 0000000..85d94bc Binary files /dev/null and b/src/assets/icons/Gaming.ico differ diff --git a/src/assets/icons/Goth.ico b/src/assets/icons/Goth.ico new file mode 100644 index 0000000..5cec36d Binary files /dev/null and b/src/assets/icons/Goth.ico differ diff --git a/src/assets/icons/Holo.ico b/src/assets/icons/Holo.ico new file mode 100644 index 0000000..f31d8a5 Binary files /dev/null and b/src/assets/icons/Holo.ico differ diff --git a/src/assets/icons/Mainframe.ico b/src/assets/icons/Mainframe.ico new file mode 100644 index 0000000..8c267fe Binary files /dev/null and b/src/assets/icons/Mainframe.ico differ diff --git a/src/assets/icons/Pastel.ico b/src/assets/icons/Pastel.ico new file mode 100644 index 0000000..c36e17b Binary files /dev/null and b/src/assets/icons/Pastel.ico differ diff --git a/src/assets/icons/Pirate.ico b/src/assets/icons/Pirate.ico new file mode 100644 index 0000000..652bb73 Binary files /dev/null and b/src/assets/icons/Pirate.ico differ diff --git a/src/assets/icons/Prismatic Waves.ico b/src/assets/icons/Prismatic Waves.ico new file mode 100644 index 0000000..da7d7e8 Binary files /dev/null and b/src/assets/icons/Prismatic Waves.ico differ diff --git a/src/assets/icons/Sakura.ico b/src/assets/icons/Sakura.ico new file mode 100644 index 0000000..63a303a Binary files /dev/null and b/src/assets/icons/Sakura.ico differ diff --git a/src/assets/icons/Sherbet Dreamsicle.ico b/src/assets/icons/Sherbet Dreamsicle.ico new file mode 100644 index 0000000..ebe6cc2 Binary files /dev/null and b/src/assets/icons/Sherbet Dreamsicle.ico differ diff --git a/src/assets/icons/Sunset Ave.ico b/src/assets/icons/Sunset Ave.ico new file mode 100644 index 0000000..e172f43 Binary files /dev/null and b/src/assets/icons/Sunset Ave.ico differ diff --git a/src/assets/icons/Tactical.ico b/src/assets/icons/Tactical.ico new file mode 100644 index 0000000..c5dfeb2 Binary files /dev/null and b/src/assets/icons/Tactical.ico differ diff --git a/src/assets/icons/UwU.ico b/src/assets/icons/UwU.ico new file mode 100644 index 0000000..aa18489 Binary files /dev/null and b/src/assets/icons/UwU.ico differ diff --git a/src/assets/icons/WHAM.ico b/src/assets/icons/WHAM.ico new file mode 100644 index 0000000..b824797 Binary files /dev/null and b/src/assets/icons/WHAM.ico differ diff --git a/src/assets/utils/ConfigManager.js b/src/assets/utils/ConfigManager.js new file mode 100644 index 0000000..f1561cf --- /dev/null +++ b/src/assets/utils/ConfigManager.js @@ -0,0 +1,21 @@ +const editJsonFile = require("edit-json-file"); +const config = editJsonFile("./config.json", {autosave:true}); + + +async function getConfig() { + return config; +} + +async function set(name, value) { + await config.set(name, value); +} + +async function get(name) { + return (await config.get(name)); +} + +async function remove(name) { + await config.unset(name); +} + +module.exports = { getConfig, set, get, remove }; \ No newline at end of file diff --git a/src/assets/utils/DiscordInstance.js b/src/assets/utils/DiscordInstance.js new file mode 100644 index 0000000..4214962 --- /dev/null +++ b/src/assets/utils/DiscordInstance.js @@ -0,0 +1,236 @@ +const fs = require("fs"); +const util = require("util") +const local_appdata = process.env.LOCALAPPDATA; +const readdir = util.promisify(fs.readdir); +const Logger = require("./Logger"); +const Config = require("./ConfigManager"); +const readline = require('readline').createInterface({ + input: process.stdin, + output: process.stdout +}); +const instanceNames = [ + "Discord", + "DiscordPTB", + "DiscordCanary", + "DiscordDevelopment" +]; +const linuxDiscordDirs = [ + "/usr/share", + "/usr/lib64", + "/opt", + "$USER$/.local/share", + "$USER$/.dvm", + "/var/lib/flatpak/app", + "$USER$//.local/share/flatpak/app" +]; + +async function getNumberArray(min, max) { + var list = []; + for (var i = min; i <= max; i++) { + list.push(i); + } + return list; +} + +async function clearConsole() { + console.clear(); + console.log(`____________________________________________________________________________\n\n ██╗\n ██║\n ██║\n ██║\n ██║con Changer for Discord\n ╚═╝\n`) + Logger.info(`Author: \x1b[36mhttps://github.com/Ezzud\x1b[0m`); + Logger.info(`Support: \x1b[36mhttps://discord.ezzud.fr\x1b[0m\n____________________________________________________________________________\n`); +} + +async function getInput(allowed_inputs, hideInputs = false, caseSensitive = false) { + var inputList = ""; + if(allowed_inputs && !hideInputs) { + inputList += " ("; + for(let i = 0; i < allowed_inputs.length; i++) { + if(allowed_inputs.length === 1) + inputList += allowed_inputs[i]; + else + inputList += allowed_inputs[i]; + if((allowed_inputs.length - 1) !== i) { + inputList+=", "; + } + + } + inputList += ")"; + } + let correctAnswer; + while(!correctAnswer) { + var answer = await new Promise(resolve => { + readline.question(`${inputList} Select your answer > `, resolve) + }) + if(caseSensitive) { + answer = answer.replaceAll(" ", "") + if(answer.split("")[0] === " ") answer = answer.substring(1); + } else { + if(answer.split("")[0] === " ") answer = answer.substring(1); + answer = answer.toLowerCase(); + } + if(answer === "cancel") correctAnswer = "cancel"; + else if(allowed_inputs) { + if(allowed_inputs.find(x => x.toString().toLowerCase() === answer)) { + correctAnswer = answer; + } else { + Logger.error("Invalid Value, please retry.") + } + } else { + correctAnswer = answer; + } + } + return correctAnswer; +} + +async function listDir(path) { + try { + return await readdir(path) + } catch (err) { + Logger.error(err) + } +} + +async function isDirDiscordDir(path) { + if(await isDirStillExists(path)) { + var files = await listDir(path); + if(files.find(x => x.startsWith("app-"))) { + return true; + } + } + return false; +} + +async function getDiscordDirInfo(path, discordType = "Discord") { + var files = await listDir(path); + let fixedPath = path; + if(path.endsWith("\\")) fixedPath = fixedPath.slice(0, -1); + return { "InstanceName":discordType, "DiscordRoot":fixedPath, "AppRoot":fixedPath + "\\" + files.find(x => x.startsWith("app-")) }; +} + +async function isDirStillExists(path) { + return fs.existsSync(path); +} + +async function selectCustomInstance() { + let path = await getInput(undefined, false, true); + if(await isDirStillExists(path)) { + if(await isDirDiscordDir(path)) { + await clearConsole(); + let inf = await getDiscordDirInfo(path); + await Config.set("workingApp", inf); + return inf; + } else { + await clearConsole(); + Logger.error("Discord Folder is not recognized"); + return undefined; + } + } else { + await clearConsole(); + Logger.error("Path not found"); + return undefined; + } + let inf = await getDiscordDirInfo(path); + await Config.set("workingApp", inf); + console.log("a") + return inf; +} + +async function startDiscordInstanceSelect() { + var installedInstances; + + var platform = process.platform; + if(process.platform === "win32" || process.platform.startsWith("win")) { + installedInstances = await findInstalledWindowsDiscordInstances(); + } else { + await clearConsole(); + if(platform === "linux") + Logger.critical("The Linux compatibility is still in development..."); + else + Logger.critical("This program is not supporting your Operating System. You can \x1b[31mcontribute to/fork\x1b[0m the project to make it compatible"); + return "error"; + } + + if(installedInstances.length < 1) { + Logger.warning("No instance of Discord has been found on the default path, please give the path where your Discord is installed\n Type \"\x1b[33mcancel\x1b[0m\" to cancel"); + Logger.info("Write the path of your Discord below (Example: C:\\Users\\ezzud\\AppData\\Local\\Discord)") + return(await selectCustomInstance()); + } + let displayText = `Please select one of the path to customise (1 - ${installedInstances.length})\n`; + for(let i = 0; i < installedInstances.length; i++) { + displayText+=` ${(i === (installedInstances.length - 1)) ? `┗` : `┣`} [\x1b[33m${i+1}\x1b[0m] ${installedInstances[i].InstanceName} - ${installedInstances[i].DiscordRoot}` + } + displayText+=`\n\n- Type \x1b[33ma number\x1b[0m to select a detected Discord Installation Folder\n- Type \"\x1b[33mcustom\x1b[0m\" to select a custom path \n- Type \"\x1b[33mcancel\x1b[0m\" to cancel\n` + Logger.info(displayText); + + let r = await getChoice(installedInstances); + if(r) { + await Config.set("workingApp", r); + return(r); + } + return undefined; + +} + +async function findInstalledLinuxDiscordInstances() { + const installedInstances = []; + + for(let i = 0; i < linuxDiscordDirs.length; i++) { + let dir = linuxDiscordDirs[i]; + if(await isDirStillExists(dir)) { + // STILL WIP + } else {continue;} + } +} + +async function findInstalledWindowsDiscordInstances() { + const installedInstances = []; + let noFolderCheck = false; + + if(!local_appdata) { + Logger.error("%LOCALAPPDATA% not found"); + noFolderCheck = true; + } + + if(!noFolderCheck) { + if (!fs.existsSync(local_appdata)) { + Logger.error("Your Local Appdata folder does not exists"); + noFolderCheck = true; + } + if(!noFolderCheck) { + for(let i = 0; i < instanceNames.length; i++) { + let completePath = local_appdata + "\\" + instanceNames[i]; + if (fs.existsSync(completePath)) { + var files = await listDir(completePath); + if(files.find(x => x.startsWith("app-"))) { + installedInstances.push(await getDiscordDirInfo(completePath, instanceNames[i])) + } + } + } + } + } + return installedInstances; +} + +async function getChoice(installedInstances) { + // Building choices + let arr = await getNumberArray(1, installedInstances.length) + arr.push("cancel"); + arr.push("custom"); + + // Get Result + var num = await getInput(arr); + if(num === "custom") { + Logger.info("Write the path of your Discord below (Example: C:\\Users\\ezzud\\AppData\\Local\\Discord)") + let userPath = await selectCustomInstance(); + return(userPath); + } else { + if(num === "cancel") { + await clearConsole(); + Logger.warning("Selection cancelled"); + return undefined; + } + await clearConsole(); + return(installedInstances[parseInt(num) - 1]); + } +} + +module.exports = { startDiscordInstanceSelect, listDir, getInput, isDirStillExists, getNumberArray }; \ No newline at end of file diff --git a/src/assets/utils/Logger.js b/src/assets/utils/Logger.js new file mode 100644 index 0000000..132710c --- /dev/null +++ b/src/assets/utils/Logger.js @@ -0,0 +1,28 @@ +const WHITE = "\x1b[0m"; +const RED = "\x1b[31m"; +const RED_2 = "\x1b[37m\x1b[41m"; +const GREEN = "\x1b[32m"; +const YELLOW = "\x1b[33m"; +const AQUA = "\x1b[36m"; + +function info(text) { + console.log(`${WHITE}[${AQUA}!${WHITE}] ${text}`); +} + +function warning(text) { + console.log(`${WHITE}[${YELLOW}!${WHITE}] ${text}`); +} + +function error(text) { + console.log(`${WHITE}[${RED}!${WHITE}] ${text}`); +} + +function success(text) { + console.log(`${WHITE}[${GREEN}!${WHITE}] ${text}`); +} + +function critical(text) { + console.log(`${RED_2}[!]${WHITE} ${text}`); +} + +module.exports = { info, warning, error, success, critical }; \ No newline at end of file diff --git a/src/assets/utils/MenuManager.js b/src/assets/utils/MenuManager.js new file mode 100644 index 0000000..0627df0 --- /dev/null +++ b/src/assets/utils/MenuManager.js @@ -0,0 +1,136 @@ +const { startDiscordInstanceSelect, listDir, isDirStillExists, getNumberArray, getInput } = require("./DiscordInstance"); +const { copyFile } = require("fs"); +const Logger = require("./Logger"); +const Config = require("./ConfigManager"); +const assetsFolder = "./src/assets/icons/"; +const { exec } = require("child_process"); +var icons; + +function resetTaskBar() { + exec("ie4uinit.exe -show", (error, stdout, stderr) => {}) + exec("taskkill /f /im explorer.exe && start explorer.exe", (error, stdout, stderr) => {}); +} + +async function displayIcons() { + icons = await listDir(assetsFolder); + + let finalText = ""; + let counter = 0; + for(let i = 0; i < icons.length; i++) { + if(counter === 4) { + finalText += "\n"; + counter = 0; + } + let selectedIcon = await Config.get("selected"); + let fileName = icons[i].split(".")[0]; + let fileLine = `[\x1b[33m${i+1}\x1b[0m] ${fileName}`; + let spaceCount = 35 - fileLine.length; + + if(selectedIcon === fileName) fileLine = `[\x1b[33m${i+1}\x1b[0m] \x1b[32m${fileName}\x1b[0m`; + if(fileName === "Default") fileLine = `[\x1b[33m${i+1}\x1b[0m] \x1b[36m${fileName}\x1b[0m`; + + finalText += fileLine; + for(let j = 0; j < spaceCount; j++) { + finalText += " "; + } + counter++; + } + finalText += `\n[\x1b[31m?\x1b[0m] Change Discord Path [\x1b[31mX\x1b[0m] Exit\n` + console.log(finalText) +} + +async function applyIcon(iconSource, iconDestination) { + await copyFile( iconSource, iconDestination, (err) => { + if (err) { + Logger.error("Error during applying icon:", err); + } + }); +} + +async function clearConsole() { + console.clear(); + console.log(`____________________________________________________________________________\n\n ██╗\n ██║\n ██║\n ██║\n ██║con Changer for Discord\n ╚═╝\n`) + Logger.info(`Author: \x1b[36mhttps://github.com/Ezzud\x1b[0m`); + Logger.info(`Support: \x1b[36mhttps://discord.ezzud.fr\x1b[0m\n____________________________________________________________________________\n`); +} + +async function selectIcon(workingApp) { + let array = await getNumberArray(1, icons.length); + array.push("?") + array.push("X") + + let choice = await getInput(array, true); + if(choice === "x") process.exit(0); + if(choice === "?") { + await Config.remove("workingApp"); + await Config.remove("selected"); + clearConsole(); + workingApp = await startDiscordInstanceSelect(); + if(!workingApp) { + initMenu(); + return; + } + if(workingApp === "error") return; + Logger.success("Defined new Discord Path!") + initMenu(); + return; + } + + let icon = icons[parseInt(choice) - 1]; + if(!icon) { + Logger.error("Icon index not found"); + process.exit(0); + } + Logger.info(`Applying Icon \x1b[36m${icon.split(".")[0]}\x1b[0m...`); + + clearConsole(); + await applyIcon(__dirname.replace("utils", "icons") + `\\${icon}`, workingApp.DiscordRoot + `\\app.ico`); + await applyIcon(__dirname.replace("utils", "icons") + `\\${icon}`, workingApp.AppRoot + `\\app.ico`); + Logger.success(`Icon \x1b[36m${icon.split(".")[0]}\x1b[0m Successfully applied! Restart your computer for the icon cache to reload`); + Logger.warning(`Do you want to restart your Taskbar to not have to restart your computer? (y/n)`); + + let all = ["y", "n", "yes", "no"]; + let res = await getInput(all, true); + clearConsole(); + if(res === "y" || res === "yes") { + await resetTaskBar(); + Logger.success(`Icon \x1b[36m${icon.split(".")[0]}\x1b[0m Successfully applied!`); + } else { + Logger.success(`Icon \x1b[36m${icon.split(".")[0]}\x1b[0m Successfully applied! Restart your computer for the icon cache to reload`); + } + await Config.set("selected", icon.split(".")[0]); + initMenu(); +} + +async function initMenu() { + var workingApp; + if(await Config.get("workingApp")) { + let c = await Config.get("workingApp"); + if(!await isDirStillExists(c.DiscordRoot) || !await isDirStillExists(c.AppRoot)) { + Logger.error("Saved Discord Path not found, please select a new one"); + await Config.remove("selected"); + workingApp = await startDiscordInstanceSelect(); + if(!workingApp) { + initMenu(); + return; + } + if(workingApp === "error") return; + } else { + workingApp = c; + } + } else { + await Config.remove("selected"); + workingApp = await startDiscordInstanceSelect(); + if(!workingApp) { + initMenu(); + return; + } + if(workingApp === "error") return; + } + Logger.info(`Working App: ${workingApp.InstanceName} (${workingApp.DiscordRoot})\n`) + await displayIcons(); + console.log(`- Type \x1b[33ma number\x1b[0m to select your icon\n- Type \x1b[33m?\x1b[0m to change your working Discord installation\n- Type \x1b[33mX\x1b[0m to exit\n`) + await selectIcon(workingApp); +} + +module.exports = { initMenu, clearConsole }; \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..87c79e2 --- /dev/null +++ b/src/index.js @@ -0,0 +1,7 @@ +const { clearConsole, initMenu } = require("./assets/utils/MenuManager") + +async function launch() { + clearConsole(); + initMenu(); +} +launch()