Yeah, here's the app

This commit is contained in:
2025-12-22 14:13:02 -05:00
commit 21f990f4d1
6 changed files with 434 additions and 0 deletions

137
.gitignore vendored Normal file
View File

@@ -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.*

10
README.md Normal file
View File

@@ -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`)

3
Start.bat Normal file
View File

@@ -0,0 +1,3 @@
@ECHO OFF
node app/index.js
pause

105
app/index.js Normal file
View File

@@ -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);
});

25
package.json Normal file
View File

@@ -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"
]
}
}

154
pnpm-lock.yaml generated Normal file
View File

@@ -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: {}