`nhentai --id ${id}`: Download a doujin.
-`node symlinks.js`: Updates symlinks. Must be run whenever new doujins have been added, otherwise they will not be seen by the other scripts.
-
-`node select.js`: Open fzf/überzug menu to select a doujin. Shows a list of tags first. When a tag is selected, shows all doujins with that tag and lets the user select one, displaying the thumbnails of the doujins at the side. The "\*" tag can be used to search/select from all doujins.
+`node select.js`: Open fzf/überzug menu to select a doujin.
`node multiple-tags.js <tag1> <tag2> ...`: Open fzf/überzug menu with doujins that match all given tags (tags separated by whitespace).
`node stats-doujins.js`: Displays number of downloaded doujins, total size and average size per doujin
-`node stats-tags.js`: Displays tags sorted by how many doujins are available for each tag. May produce long output, you might want to pipe it into `head`, `grep` or `less`.
+`node stats.js <criterium>`: Displays tags, artists, characters, parodies or groups sorted by how many doujins are available for each. May produce long output, you might want to pipe it into `head`, `grep` or `less`.
`node scrape-wholesome.js`: Scrapes [wholesome hentais](https://wholesomelist.com/list). This takes a long time since it's about 2700 hentais in total (consumes about 32GB of disk space), but you can abort it any time (and resume it later).
-`node scrape.js <criterium> <value>`: Scrapes doujins by criterium, where criterium can be tag, artist, character, parody or group and value is the value. E.g. `node scrape.js tag full-color` scrapes fully colored doujins.
+`node scrape-links.js <criterium> <value>`: Scrapes doujins by criterium, where criterium can be tag, artist, character, parody or group and value is the value. E.g. `node scrape-links.js tag full-color` scrapes fully colored doujins.
Note: you might want to create a subdirectory and put the doujins into there, they will all be put into the current working directory. (Run the scripts from a different directory to prevent spamming this directory)
-Of course, you can also run these scripts in a directory where you already downloaded doujins using the `nhentai` tool, but make sure to run the symlinks script to "register" them all in the system.
-
+Of course, you can also run these scripts in a directory where you already downloaded doujins using the `nhentai` tool.
# Notes
`./fzf-previews` taken from [fzf-ueberzogen](https://github.com/seebye/fzf-ueberzogen) with slight modifications.
-Happy scraping and jerking!
+Happy scraping!
module.exports.doujin = doujins => module.exports.fzf(doujins.sort(), __dirname + "/fzf-previews")
.then(doujin => child.spawn("firefox", [`file://${process.cwd()}/${doujin}/index.html`]))
- .catch(_ => {})
const fs = require("fs").promises
+const criteriaKeys = ["tag", "group", "artist", "character", "parody"]
module.exports.doujins = shortNames => fs.readdir(".", {encoding: "utf8", withFileTypes: true})
.then(doujins => doujins
.map(dirent => fs.readFile(`${dirent.name}/metadata.json`)
.then(data => JSON.parse(data.toString()))
+ .then(data => {data.title = dirent.name; return data})
.then(data => [dirent.name, data])
.catch(_ => [])))
.then(promises => Promise.all(promises))
.then(doujins => doujins
- .filter(([title, data]) => title && data)
- .filter(([title, data]) => (title == data.title) == !shortNames))
+ .filter(([title, data]) => title && data))
+ //.filter(([title, data]) => (title == data.title) == !shortNames))
.then(doujins => Object.fromEntries(doujins))
-module.exports.tags = _ => {
- const tags = {"*": []}
+module.exports.criteria = _ => {
+ const criteria = Object.fromEntries(criteriaKeys.map(key => [key, {["*"]: []}]))
return module.exports.doujins()
.then(doujins => Object.values(doujins))
.then(doujins => doujins
- .forEach(doujin => {
- tags["*"].push(doujin.title)
+ .forEach(doujin =>
+ criteriaKeys.forEach(crit => {
+ criteria[crit]["*"].push(doujin.title)
- doujin.tag && doujin.tag.forEach(tag => {
- tags[tag] = tags[tag] || []
- tags[tag].push(doujin.title)
+ doujin[crit] && doujin[crit].forEach(val => {
+ criteria[crit][val] = criteria[crit][val] || []
+ criteria[crit][val].push(doujin.title)
+ })
})
- }))
- .then(_ => tags)
+ ))
+ .then(_ => criteria)
}
const info = require("./info")
;(async _ => {
- const tags = await info.tags()
+ const criteria = await info.criteria()
+ let stage = 0
+ let crt, val, dou
- do {
+ while (true) {
try {
- tag = await fzf.fzf(Object.keys(tags).sort())
+ switch (stage) {
+ case 0:
+ crt = await fzf.fzf(Object.keys(criteria).sort()); break
+ case 1:
+ val = await fzf.fzf(Object.keys(criteria[crt]).sort()); break
+ case 2:
+ dou = await fzf.doujin(Object.values(criteria[crt][val]).sort()); break
+ default:
+ return
+ }
+
+ stage++
} catch {
- return
+ stage--
}
- } while (!await fzf.doujin(Object.values(tags[tag]).sort()))
+ }
})()
const info = require("./info")
const du = require("./du")
-const fmt = (num, size) =>
+const fmt = (num, size) =>
`Number of doujins: ${num}
Total size: ${(size / 1e9).toFixed(2)}GB
Average size per doujin: ${(size / num / 1e6).toFixed(2)}MB`
+++ /dev/null
-require("./info").tags()
- .then(tags => Object.entries(tags)
- .sort((a, b) => b[1].length - a[1].length)
- .forEach(elem => console.log(elem[1].length, elem[0]))
- )
- .then(_ => {})
--- /dev/null
+require("./info").criteria()
+ .then(crits => crits[process.argv[2]])
+ .then(crit => Object.entries(crit)
+ .sort((a, b) => b[1].length - a[1].length)
+ .forEach(elem => console.log(elem[1].length, elem[0]))
+ )
+ .then(_ => {})
+++ /dev/null
-const fs = require("fs").promises
-const info = require("./info")
-
-info.doujins(true)
- .then(doujins => Object.entries(doujins))
- .then(doujins => doujins
- .map(([dirname, data]) =>
- fs.symlink(dirname, data.title, "dir")
- .then(_ => console.log(data.title))
- .catch(_ => {})))