commit 21f990f4d147169a0f1a6487bde0c255ff1f7c01 Author: Winter Date: Mon Dec 22 14:13:02 2025 -0500 Yeah, here's the app diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d9be330 --- /dev/null +++ b/.gitignore @@ -0,0 +1,137 @@ +# ---> Node +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# vitepress build output +**/.vitepress/dist + +# vitepress cache directory +**/.vitepress/cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* diff --git a/README.md b/README.md new file mode 100644 index 0000000..326702c --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# How to use + +### Install +2. Run: `npm i -g pnpm` (If you do not have pnpm. Requires NodeJS) +3. Run: `pnpm i` - Installs packages + +### Running +You have two options: +1. Using `start.bat` (Windows users) +2. Run: `pnpm run start` (Alternatively: `npm run start`) \ No newline at end of file diff --git a/Start.bat b/Start.bat new file mode 100644 index 0000000..831178e --- /dev/null +++ b/Start.bat @@ -0,0 +1,3 @@ +@ECHO OFF +node app/index.js +pause \ No newline at end of file diff --git a/app/index.js b/app/index.js new file mode 100644 index 0000000..f32fd4e --- /dev/null +++ b/app/index.js @@ -0,0 +1,105 @@ +require("dotenv").config(); + +const fs = require("fs").promises; +const fss = require("fs"); // sync fs api +const path = require("path"); +const mm = require("music-metadata"); +const os = require("os"); + +/** + * Recursively list all files inside `rootDir`. + * - returns an array of absolute file paths + * - skips unreadable directories but logs a warning + * - follows symlinks that point to files, but skips symlinked directories + */ +async function listAllFiles(rootDir) { + const files = []; + const directories = []; + + async function scanDir(dir) { + let entries; + try { + entries = await fs.readdir(dir, { withFileTypes: true }); + } catch (err) { + console.warn(`Warning: cannot read directory "${dir}": ${err.message}`); + return; + } + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + if (fullPath.includes("SpotiDownloader.com - ")) { + directories.push(fullPath); + fs.rename(fullPath, fullPath.replace("SpotiDownloader.com - ", "")); + } + + // recurse into subdirectory + await scanDir(fullPath); + continue; + } + + if (entry.isFile()) { + if (fullPath.includes("SpotiDownloader.com - ")) { + files.push(fullPath); + fs.rename(fullPath, fullPath.replace("SpotiDownloader.com - ", "")); + } + + continue; + } + + if (entry.isSymbolicLink()) { + // try to follow symlink if it points to a file; skip symlinked dirs + try { + const stat = await fs.stat(fullPath); + if (stat.isFile()) files.push(fullPath); + else if (stat.isDirectory()) { + console.warn(`Skipping symlinked directory: ${fullPath}`); + } + } catch (e) { + console.warn( + `Broken or inaccessible symlink "${fullPath}": ${e.message}` + ); + } + continue; + } + + // other types (FIFO, socket, etc.) are ignored + } + } + + await scanDir(rootDir); + return { + files, + directories, + }; +} + +async function main() { + const homeDir = os.homedir(); + const downloadsDir = path.join(homeDir, "Downloads"); + + console.log(`Scanning: ${downloadsDir}\n`); + const allFiles = await listAllFiles(downloadsDir); + + if (allFiles.files.length === 0) { + console.log("No files found."); + return; + } else { + console.log(`Found and cleaned ${allFiles.files.length} file(s)`); + } + + if (allFiles.directories.length === 0) { + console.log("No directories found."); + return; + } else { + console.log( + `Found and cleaned ${allFiles.directories.length} directories.` + ); + } +} + +main().catch((err) => { + console.error("Fatal error:", err); + process.exit(1); +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..594c919 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "spoti-cleaner", + "version": "1.0.0", + "description": "", + "main": "app/index.js", + "bin": "app/index.js", + "scripts": { + "start": "node app/index.js", + "build": "pkg --targets node18-win-x64 --debug ." + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.19.0", + "dependencies": { + "dotenv": "^17.2.3", + "music-metadata": "^11.9.0", + "os": "^0.1.2" + }, + "pkg": { + "targets": [ + "node8" + ] + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..bccf11b --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,154 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + dotenv: + specifier: ^17.2.3 + version: 17.2.3 + music-metadata: + specifier: ^11.9.0 + version: 11.9.0 + os: {specifier: ^0.1.2, version: 0.1.2} + +packages: + + '@borewit/text-codec@0.1.1': + resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==} + + '@borewit/text-codec@0.2.0': + resolution: {integrity: sha512-X999CKBxGwX8wW+4gFibsbiNdwqmdQEXmUejIWaIqdrHBgS5ARIOOeyiQbHjP9G58xVEPcuvP6VwwH3A0OFTOA==} + + '@tokenizer/inflate@0.2.7': + resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + dotenv@17.2.3: + resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + engines: {node: '>=12'} + + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + + file-type@21.0.0: + resolution: {integrity: sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==} + engines: {node: '>=20'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + music-metadata@11.9.0: + resolution: {integrity: sha512-J7VqD8FY6KRcm75Fzj86FPsckiD/EdvO5OS3P+JiMf/2krP3TcAseZYfkic6eFeJ0iBhhzcdxgfu8hLW95aXXw==} + engines: {node: '>=18'} + + os@0.1.2: + resolution: {integrity: sha512-ZoXJkvAnljwvc56MbvhtKVWmSkzV712k42Is2mA0+0KTSRakq5XXuXpjZjgAt9ctzl51ojhQWakQQpmOvXWfjQ==} + + strtok3@10.3.4: + resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} + engines: {node: '>=18'} + + token-types@6.1.1: + resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==} + engines: {node: '>=14.16'} + + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} + engines: {node: '>=18'} + +snapshots: + + '@borewit/text-codec@0.1.1': {} + + '@borewit/text-codec@0.2.0': {} + + '@tokenizer/inflate@0.2.7': + dependencies: + debug: 4.4.3 + fflate: 0.8.2 + token-types: 6.1.1 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + + content-type@1.0.5: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + dotenv@17.2.3: {} + + fflate@0.8.2: {} + + file-type@21.0.0: + dependencies: + '@tokenizer/inflate': 0.2.7 + strtok3: 10.3.4 + token-types: 6.1.1 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + + ieee754@1.2.1: {} + + media-typer@1.1.0: {} + + ms@2.1.3: {} + + music-metadata@11.9.0: + dependencies: + '@borewit/text-codec': 0.2.0 + '@tokenizer/token': 0.3.0 + content-type: 1.0.5 + debug: 4.4.3 + file-type: 21.0.0 + media-typer: 1.1.0 + strtok3: 10.3.4 + token-types: 6.1.1 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + + os@0.1.2: {} + + strtok3@10.3.4: + dependencies: + '@tokenizer/token': 0.3.0 + + token-types@6.1.1: + dependencies: + '@borewit/text-codec': 0.1.1 + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + + uint8array-extras@1.5.0: {}