├── .babelrc.js
├── .esdoc.json
├── .gitignore
├── .npmignore
├── .sgcrc
├── .travis.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── config
├── .eslintrc.js
├── release-rules.js
└── release.config.js
├── docs
├── ast
│ └── source
│ │ ├── .external-ecmascript.js.json
│ │ ├── TorrentLibrary.js.json
│ │ ├── docs
│ │ ├── events
│ │ │ └── events.js.json
│ │ └── types
│ │ │ └── types.js.json
│ │ └── filters
│ │ ├── filterBooleanProperty.js.json
│ │ ├── filterNumberProperty.js.json
│ │ ├── filterProperties.js.json
│ │ └── filterStringProperty.js.json
├── badge.svg
├── class
│ └── src
│ │ └── TorrentLibrary.js~TorrentLibrary.html
├── coverage.json
├── css
│ ├── github.css
│ ├── identifiers.css
│ ├── manual.css
│ ├── prettify-tomorrow.css
│ ├── search.css
│ ├── source.css
│ ├── style.css
│ └── test.css
├── file
│ └── src
│ │ ├── TorrentLibrary.js.html
│ │ ├── docs
│ │ ├── events
│ │ │ └── events.js.html
│ │ └── types
│ │ │ └── types.js.html
│ │ └── filters
│ │ ├── filterBooleanProperty.js.html
│ │ ├── filterNumberProperty.js.html
│ │ ├── filterProperties.js.html
│ │ └── filterStringProperty.js.html
├── function
│ └── index.html
├── identifiers.html
├── image
│ ├── badge.svg
│ ├── esdoc-logo-mini-black.png
│ ├── esdoc-logo-mini.png
│ ├── github.png
│ ├── manual-badge.svg
│ └── search.png
├── index.html
├── index.json
├── lint.json
├── manual
│ ├── CHANGELOG.html
│ ├── asset
│ │ ├── fileMapping.png
│ │ ├── filterMovies.png
│ │ ├── filterTvSeries.png
│ │ ├── foundMovies.png
│ │ └── foundTvSeries.png
│ ├── createPlaylist.html
│ ├── filterMoviesByParameters.html
│ ├── filterTvSeriesByParameters.html
│ ├── getCategoryForEachFile.html
│ ├── index.html
│ ├── listEachTvSerie.html
│ └── listFoundMovies.html
├── script
│ ├── inherited-summary.js
│ ├── inner-link.js
│ ├── manual.js
│ ├── patch-for-local.js
│ ├── prettify
│ │ ├── Apache-License-2.0.txt
│ │ └── prettify.js
│ ├── pretty-print.js
│ ├── search.js
│ ├── search_index.js
│ └── test-summary.js
├── source.html
├── test-file
│ └── test
│ │ ├── getters
│ │ ├── allFilesWithCategory.js.html
│ │ ├── allMovies.js.html
│ │ ├── allTvSeries.js.html
│ │ └── constants.js.html
│ │ └── methods
│ │ ├── addNewPath.js.html
│ │ ├── createFromJSON.js.html
│ │ ├── filterMovies.js.html
│ │ ├── filterTvSeries.js.html
│ │ ├── removeOldFiles.js.html
│ │ ├── scan.js.html
│ │ └── toJSON.js.html
├── test.html
└── typedef
│ └── index.html
├── index.js
├── manual
├── assets
│ ├── fileMapping.png
│ ├── filterMovies.png
│ ├── filterTvSeries.png
│ ├── foundMovies.png
│ └── foundTvSeries.png
└── examples
│ ├── createPlaylist.md
│ ├── filterMoviesByParameters.md
│ ├── filterTvSeriesByParameters.md
│ ├── getCategoryForEachFile.md
│ ├── listEachTvSerie.md
│ └── listFoundMovies.md
├── package-lock.json
├── package.json
├── src
├── TorrentLibrary.js
├── docs
│ ├── events
│ │ └── events.js
│ └── types
│ │ └── types.js
└── filters
│ ├── filterBooleanProperty.js
│ ├── filterNumberProperty.js
│ ├── filterProperties.js
│ └── filterStringProperty.js
└── test
├── _constants.js
├── fixtures
├── example.json
├── folder1
│ ├── Bad.Ass.2012.REMASTERED.TRUEFRENCH.DVDRiP.XviD-www.zone-telechargement.ws.avi
│ └── The.Blacklist.S04E21.FRENCH.WEBRip.XviD.avi
└── folder2
│ └── The.Blacklist.S04E14.FRENCH.WEBRip.XviD.avi
├── getters
├── allFilesWithCategory.js
├── allMovies.js
├── allTvSeries.js
└── constants.js
└── methods
├── addNewPath.js
├── createFromJSON.js
├── filterMovies.js
├── filterTvSeries.js
├── removeOldFiles.js
├── scan.js
└── toJSON.js
/.babelrc.js:
--------------------------------------------------------------------------------
1 | let env = process.env.BABEL_ENV || process.env.NODE_ENV;
2 | let presets = [];
3 | let plugins = ["@babel/plugin-proposal-object-rest-spread"];
4 | let ignore = ["src/docs/**/*.js"];
5 | let comments = false;
6 |
7 | // custom settings for prod/test ; for example babel-istanbul-plugin , etc.
8 | switch (env){
9 | case 'test':
10 | // ./fix-coverage/arrow-function-coverage-fix.js : Because some odd issue for istanbul/nyc
11 | plugins.push.apply(plugins, ["babel-plugin-istanbul"]);
12 | break;
13 |
14 | case 'production':
15 | // workaround : https://github.com/babel/minify/issues/790#issuecomment-365750066
16 | presets.push.apply(presets, [['minify', { removeUndefined: false }]] );
17 | break;
18 | }
19 |
20 | // default preset
21 | presets.push.apply(presets, ["@babel/preset-env"]);
22 |
23 | module.exports = {
24 | presets: presets,
25 | plugins: plugins,
26 | ignore: ignore,
27 | comments: comments,
28 | };
--------------------------------------------------------------------------------
/.esdoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "source": "./src",
3 | "destination": "./docs",
4 | "plugins": [
5 | {"name": "esdoc-standard-plugin",
6 | "option": {
7 | "test": {
8 | "source": "./test/",
9 | "interfaces": ["test"],
10 | "includes": ["\\.js$"],
11 | "excludes": ["_constants.js"]
12 | },
13 | "manual": {
14 | "globalIndex": true,
15 | "asset": "./manual/assets",
16 | "files": [
17 | "./manual/examples/getCategoryForEachFile.md",
18 | "./manual/examples/listFoundMovies.md",
19 | "./manual/examples/listEachTvSerie.md",
20 | "./manual/examples/filterMoviesByParameters.md",
21 | "./manual/examples/filterTvSeriesByParameters.md",
22 | "./manual/examples/createPlaylist.md",
23 | "./CHANGELOG.md"
24 | ]
25 | }
26 | }
27 | },
28 | {"name": "esdoc-ecmascript-proposal-plugin", "option": {"all": true}}
29 | ]
30 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # babel src to lib
61 | lib
62 |
63 | # intellij files
64 | *.iml
65 | .idea
66 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # uncompiled files
2 | src
--------------------------------------------------------------------------------
/.sgcrc:
--------------------------------------------------------------------------------
1 | {
2 | "scope": false,
3 | "body": true,
4 | "emoji": true,
5 | "lowercaseTypes": false,
6 | "initial-commit": {
7 | "isEnabled": true,
8 | "emoji": ":tada:",
9 | "message": "Initial commit"
10 | },
11 | "types": [
12 | {
13 | "emoji": ":wrench:",
14 | "type": "Chore:",
15 | "description": "Changes that affect the build system or external dependencies and moving files"
16 | },
17 | {
18 | "emoji": ":construction_worker:",
19 | "type": "CI:",
20 | "description": "Changes to our CI configuration files and scripts"
21 | },
22 | {
23 | "emoji": ":memo:",
24 | "type": "Docs:",
25 | "description": "Documentation only changes"
26 | },
27 | {
28 | "emoji": ":sparkles:",
29 | "type": "Feat:",
30 | "description": "New feature"
31 | },
32 | {
33 | "emoji": ":bug:",
34 | "type": "Fix:",
35 | "description": "Bug fix"
36 | },
37 | {
38 | "emoji": ":zap:",
39 | "type": "Perf:",
40 | "description": "Code change that improves performance"
41 | },
42 | {
43 | "emoji": ":hammer:",
44 | "type": "Refactor:",
45 | "description": "Code change that neither fixes a bug nor adds a feature"
46 | },
47 | {
48 | "emoji": ":art:",
49 | "type": "Style:",
50 | "description": "Changes that do not affect the meaning of the code"
51 | },
52 | {
53 | "emoji": ":white_check_mark:",
54 | "type": "Test:",
55 | "description": "Adding missing tests or correcting existing tests"
56 | },
57 | {
58 | "emoji": ":boom:",
59 | "type": "Breaking:",
60 | "description": "Breaking Change"
61 | }
62 | ],
63 | "rules": {
64 | "max-char": 72,
65 | "min-char": 10,
66 | "end-with-dot": false
67 | }
68 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | dist: trusty
3 | sudo: required
4 | cache:
5 | directories:
6 | - node_modules
7 | notifications:
8 | slack:
9 | on_success: change
10 | on_failure: always
11 | node_js:
12 | - 'node'
13 | - '7'
14 | - '6'
15 | os:
16 | - linux
17 | before_install:
18 | - npm i -g npm@latest
19 | - npm i -g travis-deploy-once
20 | - npm install -g greenkeeper-lockfile@latest
21 | install:
22 | - npm install
23 | - npm audit fix
24 | before_script:
25 | - npm prune
26 | - echo "GreenKeeper lockfile update"
27 | - greenkeeper-lockfile-update
28 | after_script:
29 | - greenkeeper-lockfile-upload
30 | after_success:
31 | - npm run coverage
32 | # In order to have production files for release : I should rerun compile in production mode in the master build
33 | - travis-deploy-once && npm run prepare && npx -p node@8 npm run semantic-release
34 | branches:
35 | except:
36 | - /^v\d+\.\d+\.\d+$/
37 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at jacques.yakoub+github@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 jy95
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # torrent-files-library [](https://travis-ci.org/jy95/torrent-files-library) [](https://coveralls.io/github/jy95/torrent-files-library?branch=master) [](https://david-dm.org/jy95/torrent-files-library) [](https://david-dm.org/jy95/torrent-files-library?type=dev) [](https://opensource.org/licenses/MIT) [](https://github.com/semantic-release/semantic-release) [](https://gitter.im/torrent-files-library-/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://greenkeeper.io/)
2 | > Scan directories to build a library of media files (movie or tv show) that follows torrent naming conventions
3 |
4 | PS: see the repository [mediaScan](https://github.com/jy95/mediaScan) for the TypeScript version of this lib.
5 |
6 | ## What to do with this library ?
7 |
8 | A lot of things :
9 | * Basic listing purposes :
10 | * [List found movies](https://jy95.github.io/torrent-files-library/manual/listFoundMovies.html)
11 | * [List each found tv serie](https://jy95.github.io/torrent-files-library/manual/listEachTvSerie.html)
12 | * [Detect the category of each file](https://jy95.github.io/torrent-files-library/manual/getCategoryForEachFile.html)
13 | * Filtering purposes :
14 | * [Filter movies based on search parameters](https://jy95.github.io/torrent-files-library/manual/filterMoviesByParameters.html)
15 | * [Filter tv-shown based on search parameters](https://jy95.github.io/torrent-files-library/manual/filterTvSeriesByParameters.html)
16 | * Miscellaneous purposes
17 | * [Create custom playlist(s)](https://jy95.github.io/torrent-files-library/manual/createPlaylist.html)
18 | * ...
19 |
20 | Don't hesitate to suggest new features : it is always worthy :)
21 |
22 | ## Documentation
23 | For more examples and API details, see [API documentation with manual](https://jy95.github.io/torrent-files-library/)
24 |
25 | ## Installation
26 |
27 | For npm users :
28 |
29 | ```shell
30 | $ npm install --save torrent-files-library
31 | ```
32 |
33 | for Yarn :
34 | ```shell
35 | $ yarn add torrent-files-library
36 | ```
37 |
38 | ## Test
39 |
40 | ```shell
41 | npm test
42 | ```
43 |
44 | To generate a test coverage report:
45 |
46 | ```shell
47 | npm run coverage
48 | ```
49 | ## Contributing
50 |
51 | * If you're unsure if a feature would make a good addition, you can always [create an issue](https://github.com/jy95/torrent-files-library/issues/new) first.
52 | * We aim for 100% test coverage. Please write tests for any new functionality or changes.
53 | * Any API changes should be fully documented.
54 | * Make sure your code meets our linting standards. Run `npm run lint` to check your code.
55 | * Be mindful of others when making suggestions and/or code reviewing.
56 |
--------------------------------------------------------------------------------
/config/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "extends": "airbnb-base",
3 | "rules" :{
4 | "linebreak-style": 0,
5 | "valid-jsdoc": "error",
6 | "max-len": ["error", { "ignoreComments": true }],
7 | "import/extensions": ['off', 'never'],
8 | "no-bitwise": ["error", { "allow": ["|"] }],
9 | "no-restricted-syntax": ['off', "ForInStatement"],
10 | "prefer-const" : "off"
11 | },
12 | "env" : {
13 | "es6" : true,
14 | "node": true,
15 | "mocha" : true
16 | }
17 | };
--------------------------------------------------------------------------------
/config/release-rules.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | {
3 | tag: 'Feat',
4 | release: 'minor',
5 | },
6 | {
7 | tag: 'Refactor',
8 | release: 'patch',
9 | },
10 | {
11 | tag: 'Fix',
12 | release: 'patch',
13 | },
14 | {
15 | tag: 'Perf',
16 | release: 'patch',
17 | },
18 | {
19 | tag: 'Breaking',
20 | release: 'major',
21 | },
22 | ];
23 |
--------------------------------------------------------------------------------
/config/release.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-useless-escape,max-len,no-template-curly-in-string */
2 | module.exports = {
3 | analyzeCommits: {
4 | preset: 'eslint',
5 | releaseRules: './config/release-rules.js',
6 | parserOpts: {
7 | headerPattern: /^(?::([\w-]*):)?\s*(\w*):\s*(.*)$/,
8 | headerCorrespondence: [
9 | 'emoji',
10 | 'tag',
11 | 'message',
12 | ],
13 | },
14 | },
15 | generateNotes: {
16 | preset: 'eslint',
17 | parserOpts: {
18 | headerPattern: /^(?::([\w-]*):)?\s*(\w*):\s*(.*)$/,
19 | headerCorrespondence: [
20 | 'emoji',
21 | 'tag',
22 | 'message',
23 | ],
24 | },
25 | },
26 | verifyConditions:
27 | ['@semantic-release/changelog', '@semantic-release/npm', '@semantic-release/git', '@semantic-release/github'],
28 | publish:
29 | ['@semantic-release/changelog', '@semantic-release/npm',
30 | {
31 | path: '@semantic-release/git',
32 | message: ':wrench: Chore: update package.json and CHANGELOG.md for release ${nextRelease.version} [skip ci]',
33 | }, '@semantic-release/github'],
34 | };
35 |
--------------------------------------------------------------------------------
/docs/badge.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/docs/coverage.json:
--------------------------------------------------------------------------------
1 | {
2 | "coverage": "100%",
3 | "expectCount": 39,
4 | "actualCount": 39,
5 | "files": {
6 | "src/filters/filterBooleanProperty.js": {
7 | "expectCount": 3,
8 | "actualCount": 3,
9 | "undocumentLines": []
10 | },
11 | "src/filters/filterNumberProperty.js": {
12 | "expectCount": 5,
13 | "actualCount": 5,
14 | "undocumentLines": []
15 | },
16 | "src/filters/filterProperties.js": {
17 | "expectCount": 3,
18 | "actualCount": 3,
19 | "undocumentLines": []
20 | },
21 | "src/filters/filterStringProperty.js": {
22 | "expectCount": 4,
23 | "actualCount": 4,
24 | "undocumentLines": []
25 | },
26 | "src/TorrentLibrary.js": {
27 | "expectCount": 24,
28 | "actualCount": 24,
29 | "undocumentLines": []
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/docs/css/github.css:
--------------------------------------------------------------------------------
1 | /* github markdown */
2 | .github-markdown {
3 | font-size: 16px;
4 | }
5 |
6 | .github-markdown h1,
7 | .github-markdown h2,
8 | .github-markdown h3,
9 | .github-markdown h4,
10 | .github-markdown h5 {
11 | margin-top: 1em;
12 | margin-bottom: 16px;
13 | font-weight: bold;
14 | padding: 0;
15 | }
16 |
17 | .github-markdown h1:nth-of-type(1) {
18 | margin-top: 0;
19 | }
20 |
21 | .github-markdown h1 {
22 | font-size: 2em;
23 | padding-bottom: 0.3em;
24 | }
25 |
26 | .github-markdown h2 {
27 | font-size: 1.75em;
28 | padding-bottom: 0.3em;
29 | }
30 |
31 | .github-markdown h3 {
32 | font-size: 1.5em;
33 | }
34 |
35 | .github-markdown h4 {
36 | font-size: 1.25em;
37 | }
38 |
39 | .github-markdown h5 {
40 | font-size: 1em;
41 | }
42 |
43 | .github-markdown ul, .github-markdown ol {
44 | padding-left: 2em;
45 | }
46 |
47 | .github-markdown pre > code {
48 | font-size: 0.85em;
49 | }
50 |
51 | .github-markdown table {
52 | margin-bottom: 1em;
53 | border-collapse: collapse;
54 | border-spacing: 0;
55 | }
56 |
57 | .github-markdown table tr {
58 | background-color: #fff;
59 | border-top: 1px solid #ccc;
60 | }
61 |
62 | .github-markdown table th,
63 | .github-markdown table td {
64 | padding: 6px 13px;
65 | border: 1px solid #ddd;
66 | }
67 |
68 | .github-markdown table tr:nth-child(2n) {
69 | background-color: #f8f8f8;
70 | }
71 |
72 | .github-markdown hr {
73 | border-right: 0;
74 | border-bottom: 1px solid #e5e5e5;
75 | border-left: 0;
76 | border-top: 0;
77 | }
78 |
79 | /** badge(.svg) does not have border */
80 | .github-markdown img:not([src*=".svg"]) {
81 | max-width: 100%;
82 | box-shadow: 1px 1px 1px rgba(0,0,0,0.5);
83 | }
84 |
--------------------------------------------------------------------------------
/docs/css/identifiers.css:
--------------------------------------------------------------------------------
1 | .identifiers-wrap {
2 | display: flex;
3 | align-items: flex-start;
4 | }
5 |
6 | .identifier-dir-tree {
7 | background: #fff;
8 | border: solid 1px #ddd;
9 | border-radius: 0.25em;
10 | top: 52px;
11 | position: -webkit-sticky;
12 | position: sticky;
13 | max-height: calc(100vh - 155px);
14 | overflow-y: scroll;
15 | min-width: 200px;
16 | margin-left: 1em;
17 | }
18 |
19 | .identifier-dir-tree-header {
20 | padding: 0.5em;
21 | background-color: #fafafa;
22 | border-bottom: solid 1px #ddd;
23 | }
24 |
25 | .identifier-dir-tree-content {
26 | padding: 0 0.5em 0;
27 | }
28 |
29 | .identifier-dir-tree-content > div {
30 | padding-top: 0.25em;
31 | padding-bottom: 0.25em;
32 | }
33 |
34 | .identifier-dir-tree-content a {
35 | color: inherit;
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/docs/css/manual.css:
--------------------------------------------------------------------------------
1 | .github-markdown .manual-toc {
2 | padding-left: 0;
3 | }
4 |
5 | .manual-index .manual-cards {
6 | display: flex;
7 | flex-wrap: wrap;
8 | }
9 |
10 | .manual-index .manual-card-wrap {
11 | width: 280px;
12 | padding: 10px 20px 10px 0;
13 | box-sizing: border-box;
14 | }
15 |
16 | .manual-index .manual-card-wrap > h1 {
17 | margin: 0;
18 | font-size: 1em;
19 | font-weight: 600;
20 | padding: 0.2em 0 0.2em 0.5em;
21 | border-radius: 0.1em 0.1em 0 0;
22 | border: none;
23 | }
24 |
25 | .manual-index .manual-card-wrap > h1 span {
26 | color: #555;
27 | }
28 |
29 | .manual-index .manual-card {
30 | height: 200px;
31 | overflow: hidden;
32 | border: solid 1px rgba(230, 230, 230, 0.84);
33 | border-radius: 0 0 0.1em 0.1em;
34 | padding: 8px;
35 | position: relative;
36 | }
37 |
38 | .manual-index .manual-card > div {
39 | transform: scale(0.4);
40 | transform-origin: 0 0;
41 | width: 250%;
42 | }
43 |
44 | .manual-index .manual-card > a {
45 | position: absolute;
46 | top: 0;
47 | left: 0;
48 | width: 100%;
49 | height: 100%;
50 | background: rgba(210, 210, 210, 0.1);
51 | }
52 |
53 | .manual-index .manual-card > a:hover {
54 | background: none;
55 | }
56 |
57 | .manual-index .manual-badge {
58 | margin: 0;
59 | }
60 |
61 | .manual-index .manual-user-index {
62 | margin-bottom: 1em;
63 | border-bottom: solid 1px #ddd;
64 | }
65 |
66 | .manual-root .navigation {
67 | padding-left: 4px;
68 | margin-top: 4px;
69 | }
70 |
71 | .navigation .manual-toc-root > div {
72 | padding-left: 0.25em;
73 | padding-right: 0.75em;
74 | }
75 |
76 | .github-markdown .manual-toc-title a {
77 | color: inherit;
78 | }
79 |
80 | .manual-breadcrumb-list {
81 | font-size: 0.8em;
82 | margin-bottom: 1em;
83 | }
84 |
85 | .manual-toc-title a:hover {
86 | color: #039BE5;
87 | }
88 |
89 | .manual-toc li {
90 | margin: 0.75em 0;
91 | list-style-type: none;
92 | }
93 |
94 | .navigation .manual-toc [class^="indent-h"] a {
95 | color: #666;
96 | }
97 |
98 | .navigation .manual-toc .indent-h1 a {
99 | color: #555;
100 | font-weight: 600;
101 | display: block;
102 | }
103 |
104 | .manual-toc .indent-h1 {
105 | display: block;
106 | margin: 0.4em 0 0 0.25em;
107 | padding: 0.2em 0 0.2em 0.5em;
108 | border-radius: 0.1em;
109 | }
110 |
111 | .manual-root .navigation .manual-toc li:not(.indent-h1) {
112 | margin-top: 0.5em;
113 | }
114 |
115 | .manual-toc .indent-h2 {
116 | display: none;
117 | margin-left: 1.5em;
118 | }
119 | .manual-toc .indent-h3 {
120 | display: none;
121 | margin-left: 2.5em;
122 | }
123 | .manual-toc .indent-h4 {
124 | display: none;
125 | margin-left: 3.5em;
126 | }
127 | .manual-toc .indent-h5 {
128 | display: none;
129 | margin-left: 4.5em;
130 | }
131 |
132 | .manual-nav li {
133 | margin: 0.75em 0;
134 | }
135 |
--------------------------------------------------------------------------------
/docs/css/prettify-tomorrow.css:
--------------------------------------------------------------------------------
1 | /* Tomorrow Theme */
2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */
3 | /* Pretty printing styles. Used with prettify.js. */
4 | /* SPAN elements with the classes below are added by prettyprint. */
5 | /* plain text */
6 | .pln {
7 | color: #4d4d4c; }
8 |
9 | @media screen {
10 | /* string content */
11 | .str {
12 | color: #718c00; }
13 |
14 | /* a keyword */
15 | .kwd {
16 | color: #8959a8; }
17 |
18 | /* a comment */
19 | .com {
20 | color: #8e908c; }
21 |
22 | /* a type name */
23 | .typ {
24 | color: #4271ae; }
25 |
26 | /* a literal value */
27 | .lit {
28 | color: #f5871f; }
29 |
30 | /* punctuation */
31 | .pun {
32 | color: #4d4d4c; }
33 |
34 | /* lisp open bracket */
35 | .opn {
36 | color: #4d4d4c; }
37 |
38 | /* lisp close bracket */
39 | .clo {
40 | color: #4d4d4c; }
41 |
42 | /* a markup tag name */
43 | .tag {
44 | color: #c82829; }
45 |
46 | /* a markup attribute name */
47 | .atn {
48 | color: #f5871f; }
49 |
50 | /* a markup attribute value */
51 | .atv {
52 | color: #3e999f; }
53 |
54 | /* a declaration */
55 | .dec {
56 | color: #f5871f; }
57 |
58 | /* a variable name */
59 | .var {
60 | color: #c82829; }
61 |
62 | /* a function name */
63 | .fun {
64 | color: #4271ae; } }
65 | /* Use higher contrast and text-weight for printable form. */
66 | @media print, projection {
67 | .str {
68 | color: #060; }
69 |
70 | .kwd {
71 | color: #006;
72 | font-weight: bold; }
73 |
74 | .com {
75 | color: #600;
76 | font-style: italic; }
77 |
78 | .typ {
79 | color: #404;
80 | font-weight: bold; }
81 |
82 | .lit {
83 | color: #044; }
84 |
85 | .pun, .opn, .clo {
86 | color: #440; }
87 |
88 | .tag {
89 | color: #006;
90 | font-weight: bold; }
91 |
92 | .atn {
93 | color: #404; }
94 |
95 | .atv {
96 | color: #060; } }
97 | /* Style */
98 | /*
99 | pre.prettyprint {
100 | background: white;
101 | font-family: Consolas, Monaco, 'Andale Mono', monospace;
102 | font-size: 12px;
103 | line-height: 1.5;
104 | border: 1px solid #ccc;
105 | padding: 10px; }
106 | */
107 |
108 | /* Specify class=linenums on a pre to get line numbering */
109 | ol.linenums {
110 | margin-top: 0;
111 | margin-bottom: 0; }
112 |
113 | /* IE indents via margin-left */
114 | li.L0,
115 | li.L1,
116 | li.L2,
117 | li.L3,
118 | li.L4,
119 | li.L5,
120 | li.L6,
121 | li.L7,
122 | li.L8,
123 | li.L9 {
124 | /* */ }
125 |
126 | /* Alternate shading for lines */
127 | li.L1,
128 | li.L3,
129 | li.L5,
130 | li.L7,
131 | li.L9 {
132 | /* */ }
133 |
--------------------------------------------------------------------------------
/docs/css/search.css:
--------------------------------------------------------------------------------
1 | /* search box */
2 | .search-box {
3 | position: absolute;
4 | top: 10px;
5 | right: 50px;
6 | padding-right: 8px;
7 | padding-bottom: 10px;
8 | line-height: normal;
9 | font-size: 12px;
10 | }
11 |
12 | .search-box img {
13 | width: 20px;
14 | vertical-align: top;
15 | }
16 |
17 | .search-input {
18 | display: inline;
19 | visibility: hidden;
20 | width: 0;
21 | padding: 2px;
22 | height: 1.5em;
23 | outline: none;
24 | background: transparent;
25 | border: 1px #0af;
26 | border-style: none none solid none;
27 | vertical-align: bottom;
28 | }
29 |
30 | .search-input-edge {
31 | display: none;
32 | width: 1px;
33 | height: 5px;
34 | background-color: #0af;
35 | vertical-align: bottom;
36 | }
37 |
38 | .search-result {
39 | position: absolute;
40 | display: none;
41 | height: 600px;
42 | width: 100%;
43 | padding: 0;
44 | margin-top: 5px;
45 | margin-left: 24px;
46 | background: white;
47 | box-shadow: 1px 1px 4px rgb(0,0,0);
48 | white-space: nowrap;
49 | overflow-y: scroll;
50 | }
51 |
52 | .search-result-import-path {
53 | color: #aaa;
54 | font-size: 12px;
55 | }
56 |
57 | .search-result li {
58 | list-style: none;
59 | padding: 2px 4px;
60 | }
61 |
62 | .search-result li a {
63 | display: block;
64 | }
65 |
66 | .search-result li.selected {
67 | background: #ddd;
68 | }
69 |
70 | .search-result li.search-separator {
71 | background: rgb(37, 138, 175);
72 | color: white;
73 | }
74 |
75 | .search-box.active .search-input {
76 | visibility: visible;
77 | transition: width 0.2s ease-out;
78 | width: 300px;
79 | }
80 |
81 | .search-box.active .search-input-edge {
82 | display: inline-block;
83 | }
84 |
85 |
--------------------------------------------------------------------------------
/docs/css/source.css:
--------------------------------------------------------------------------------
1 | table.files-summary {
2 | width: 100%;
3 | margin: 10px 0;
4 | border-spacing: 0;
5 | border: 0;
6 | border-collapse: collapse;
7 | text-align: right;
8 | }
9 |
10 | table.files-summary tbody tr:hover {
11 | background: #eee;
12 | }
13 |
14 | table.files-summary td:first-child,
15 | table.files-summary td:nth-of-type(2) {
16 | text-align: left;
17 | }
18 |
19 | table.files-summary[data-use-coverage="false"] td.coverage {
20 | display: none;
21 | }
22 |
23 | table.files-summary thead {
24 | background: #fafafa;
25 | }
26 |
27 | table.files-summary td {
28 | border: solid 1px #ddd;
29 | padding: 4px 10px;
30 | vertical-align: top;
31 | }
32 |
33 | table.files-summary td.identifiers > span {
34 | display: block;
35 | margin-top: 4px;
36 | }
37 | table.files-summary td.identifiers > span:first-child {
38 | margin-top: 0;
39 | }
40 |
41 | table.files-summary .coverage-count {
42 | font-size: 12px;
43 | color: #aaa;
44 | display: inline-block;
45 | min-width: 40px;
46 | }
47 |
48 | .total-coverage-count {
49 | position: relative;
50 | bottom: 2px;
51 | font-size: 12px;
52 | color: #666;
53 | font-weight: 500;
54 | padding-left: 5px;
55 | }
56 |
--------------------------------------------------------------------------------
/docs/css/test.css:
--------------------------------------------------------------------------------
1 | table.test-summary thead {
2 | background: #fafafa;
3 | }
4 |
5 | table.test-summary thead .test-description {
6 | width: 50%;
7 | }
8 |
9 | table.test-summary {
10 | width: 100%;
11 | margin: 10px 0;
12 | border-spacing: 0;
13 | border: 0;
14 | border-collapse: collapse;
15 | }
16 |
17 | table.test-summary thead .test-count {
18 | width: 3em;
19 | }
20 |
21 | table.test-summary tbody tr:hover {
22 | background-color: #eee;
23 | }
24 |
25 | table.test-summary td {
26 | border: solid 1px #ddd;
27 | padding: 4px 10px;
28 | vertical-align: top;
29 | }
30 |
31 | table.test-summary td p {
32 | margin: 0;
33 | }
34 |
35 | table.test-summary tr.test-interface .toggle {
36 | display: inline-block;
37 | float: left;
38 | margin-right: 4px;
39 | cursor: pointer;
40 | font-size: 0.8em;
41 | padding-top: 0.25em;
42 | }
43 |
44 | table.test-summary tr.test-interface .toggle.opened:before {
45 | content: '▼';
46 | }
47 |
48 | table.test-summary tr.test-interface .toggle.closed:before {
49 | content: '▶';
50 | }
51 |
52 | table.test-summary .test-target > span {
53 | display: block;
54 | margin-top: 4px;
55 | }
56 | table.test-summary .test-target > span:first-child {
57 | margin-top: 0;
58 | }
59 |
--------------------------------------------------------------------------------
/docs/file/src/docs/events/events.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | src/docs/events/events.js | torrent-files-library
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
28 |
29 |
65 |
66 | src/docs/events/events.js
67 |
// events
68 |
69 | /**
70 | * missing_parameter event
71 | * @typedef {Object} Events#missing_parameter
72 | * @property {string} functionName - Indicates the function name when the error occurs.
73 | * @example
74 | * TorrentLibraryInstance.on('missing_parameter',function(callback){
75 | * console.log('Parameter is missing in ' + callback.functionName);
76 | * })
77 | */
78 |
79 | /**
80 | * error_in_function event
81 | * @typedef {Object} Events#error_in_function
82 | * @property {string} functionName - Indicates the function name when the error occurs.
83 | * @property {string} error - The error message got by error.message
84 | * @example
85 | * TorrentLibraryInstance.on('error_in_function',function(callback){
86 | * console.log('Function ' + callback.functionName + ' has the following error : ' + callback.error);
87 | * })
88 | */
89 |
90 | /**
91 | * addNewPath event
92 | * @typedef {Object} Events#addNewPath
93 | * @property {...string} paths - all the paths that were added
94 | * @example
95 | * TorrentLibraryInstance.on('addNewPath',function(callback){
96 | * console.log('The following files were added : ' + callback.paths);
97 | * })
98 | */
99 |
100 | /**
101 | * scan event
102 | * @typedef {object} Events#scan
103 | * @property {...string} files - all the files that were found and added if not yet in lib
104 | * @example
105 | * TorrentLibraryInstance.on('scan',function(callback){
106 | * console.log('The following files were found : ' + callback.files);
107 | * })
108 | */
109 |
110 | /**
111 | * removeOldFiles event
112 | * @typedef {object} Events#removeOldFiles
113 | * @property {...string} files - all the files that were found and removed if not yet in lib
114 | * @example
115 | * TorrentLibraryInstance.on('removeOldFiles',function(callback){
116 | * console.log('The following files were added : ' + callback.files);
117 | * })
118 | */
119 |
120 |
121 |
122 |
123 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/docs/file/src/filters/filterBooleanProperty.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | src/filters/filterBooleanProperty.js | torrent-files-library
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
28 |
29 |
65 |
66 | src/filters/filterBooleanProperty.js
67 |
/**
68 | * Provides a map with valid default properties
69 | * @param {searchParameters} searchObject - search parameters
70 | * @return {Map<string, boolean>} the result map
71 | */
72 | export function filterDefaultBooleanProperties(searchObject) {
73 | const {
74 | extended, unrated, proper, repack, convert, hardcoded, retail, remastered,
75 | } = searchObject;
76 |
77 |
78 | const propertiesArray = [extended, unrated, proper,
79 | repack, convert, hardcoded, retail, remastered];
80 | const propertiesNames = ['extended', 'unrated', 'proper', 'repack', 'convert',
81 | 'hardcoded', 'retail', 'remastered'];
82 |
83 | return propertiesArray.reduce((propertiesMap, val, index) => {
84 | // eslint-disable-next-line max-len
85 | if (val === true || val === false) { propertiesMap.set(propertiesNames[index], val); }
86 | return propertiesMap;
87 | }, new Map());
88 | }
89 |
90 | /**
91 | * Remove the default boolean properties
92 | * @param {searchParameters} searchObject - search parameters
93 | * @return {searchParameters} searchParameters without these properties
94 | */
95 | export function excludeDefaultBooleanProperties(searchObject) {
96 | let {
97 | extended, unrated, proper, repack, convert, hardcoded, retail, remastered,
98 | ...rest
99 | } = searchObject;
100 | return rest;
101 | }
102 |
103 | /**
104 | * Filter the set based on boolean properties
105 | * @param {Set<TPN>} set The TPN set
106 | * @param {Map<string, boolean>} propertiesMap The map from filterDefaultBooleanProperties
107 | * @return {Set<TPN>} the filtered set
108 | */
109 | export function filterByBoolean(set, propertiesMap) {
110 | // first step : get an array so that we can do filter/reduce stuff
111 | // second step : iterate the propertiesMap and do filter and return the filtered array
112 | // val[0] : the key ; val[1] : the value
113 | return new Set(Array
114 | .from(propertiesMap.entries())
115 | .reduce(
116 | // eslint-disable-next-line max-len
117 | (currentMoviesArray, val) => currentMoviesArray.filter(TPN => TPN[val[0]] === val[1])
118 | , [...set],
119 | ));
120 | }
121 |
122 |
123 |
124 |
125 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/docs/file/src/filters/filterStringProperty.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | src/filters/filterStringProperty.js | torrent-files-library
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
28 |
29 |
65 |
66 | src/filters/filterStringProperty.js
67 |
/**
68 | * Provides a map with valid default properties
69 | * @param {searchParameters} searchObject - search parameters
70 | * @return {Map<string, string|string[]>} the result map
71 | */
72 | export function filterDefaultStringProperties(searchObject) {
73 | const {
74 | title, resolution, codec, audio, group, region, container, language, source,
75 | } = searchObject;
76 |
77 |
78 | const propertiesArray = [title, resolution, codec, audio, group,
79 | region, container, language, source];
80 | const propertiesNames = ['title', 'resolution', 'codec', 'audio', 'group',
81 | 'region', 'container', 'language', 'source'];
82 |
83 | return propertiesArray.reduce((propertiesMap, val, index) => {
84 | if (val !== undefined) {
85 | propertiesMap.set(propertiesNames[index], val);
86 | }
87 | return propertiesMap;
88 | }, new Map());
89 | }
90 |
91 | /**
92 | * Remove the default string properties
93 | * @param {searchParameters} searchObject - search parameters
94 | * @return {searchParameters} searchParameters without these properties
95 | */
96 | export function excludeDefaultStringProperties(searchObject) {
97 | let {
98 | title, resolution, codec, audio, group, region, container, language, source,
99 | ...rest
100 | } = searchObject;
101 | return rest;
102 | }
103 |
104 | /**
105 | * Filter function for filterByString
106 | * @param {string} property The property to be checked
107 | * @param {string[]|string} expected The expected result
108 | * @param {TPN} object the object to be checked
109 | * @return {boolean} the result
110 | */
111 | function filterFunctionByType(property, expected, object) {
112 | if (Array.isArray(expected)) { return expected.includes(object[property]); }
113 | return object[property] === expected;
114 | }
115 |
116 | /**
117 | * Filter the set based on string properties
118 | * @param {Set<TPN>} set The TPN set
119 | * @param {Map<string, string|string[]>} propertiesMap The map from filterDefaultStringProperties
120 | * @return {Set<TPN>} the filtered set
121 | */
122 | export function filterByString(set, propertiesMap) {
123 | // first step : get an array so that we can do filter/reduce stuff
124 | // second step : iterate the propertiesMap and do filter and return the filtered array
125 | // val[0] : the key ; val[1] : the value
126 | return new Set(Array
127 | .from(propertiesMap.entries())
128 | .reduce(
129 | // eslint-disable-next-line max-len
130 | (currentMoviesArray, val) => currentMoviesArray.filter(TPN => filterFunctionByType(val[0], val[1], TPN))
131 | , [...set],
132 | ));
133 | }
134 |
135 |
136 |
137 |
138 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
--------------------------------------------------------------------------------
/docs/image/badge.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/docs/image/esdoc-logo-mini-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/docs/image/esdoc-logo-mini-black.png
--------------------------------------------------------------------------------
/docs/image/esdoc-logo-mini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/docs/image/esdoc-logo-mini.png
--------------------------------------------------------------------------------
/docs/image/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/docs/image/github.png
--------------------------------------------------------------------------------
/docs/image/manual-badge.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/docs/image/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/docs/image/search.png
--------------------------------------------------------------------------------
/docs/lint.json:
--------------------------------------------------------------------------------
1 | []
--------------------------------------------------------------------------------
/docs/manual/asset/fileMapping.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/docs/manual/asset/fileMapping.png
--------------------------------------------------------------------------------
/docs/manual/asset/filterMovies.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/docs/manual/asset/filterMovies.png
--------------------------------------------------------------------------------
/docs/manual/asset/filterTvSeries.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/docs/manual/asset/filterTvSeries.png
--------------------------------------------------------------------------------
/docs/manual/asset/foundMovies.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/docs/manual/asset/foundMovies.png
--------------------------------------------------------------------------------
/docs/manual/asset/foundTvSeries.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/docs/manual/asset/foundTvSeries.png
--------------------------------------------------------------------------------
/docs/script/inherited-summary.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | function toggle(ev) {
3 | var button = ev.target;
4 | var parent = ev.target.parentElement;
5 | while(parent) {
6 | if (parent.tagName === 'TABLE' && parent.classList.contains('summary')) break;
7 | parent = parent.parentElement;
8 | }
9 |
10 | if (!parent) return;
11 |
12 | var tbody = parent.querySelector('tbody');
13 | if (button.classList.contains('opened')) {
14 | button.classList.remove('opened');
15 | button.classList.add('closed');
16 | tbody.style.display = 'none';
17 | } else {
18 | button.classList.remove('closed');
19 | button.classList.add('opened');
20 | tbody.style.display = 'block';
21 | }
22 | }
23 |
24 | var buttons = document.querySelectorAll('.inherited-summary thead .toggle');
25 | for (var i = 0; i < buttons.length; i++) {
26 | buttons[i].addEventListener('click', toggle);
27 | }
28 | })();
29 |
--------------------------------------------------------------------------------
/docs/script/inner-link.js:
--------------------------------------------------------------------------------
1 | // inner link(#foo) can not correctly scroll, because page has fixed header,
2 | // so, I manually scroll.
3 | (function(){
4 | var matched = location.hash.match(/errorLines=([\d,]+)/);
5 | if (matched) return;
6 |
7 | function adjust() {
8 | window.scrollBy(0, -55);
9 | var el = document.querySelector('.inner-link-active');
10 | if (el) el.classList.remove('inner-link-active');
11 |
12 | // ``[ ] . ' " @`` are not valid in DOM id. so must escape these.
13 | var id = location.hash.replace(/([\[\].'"@$])/g, '\\$1');
14 | var el = document.querySelector(id);
15 | if (el) el.classList.add('inner-link-active');
16 | }
17 |
18 | window.addEventListener('hashchange', adjust);
19 |
20 | if (location.hash) {
21 | setTimeout(adjust, 0);
22 | }
23 | })();
24 |
25 | (function(){
26 | var els = document.querySelectorAll('[href^="#"]');
27 | var href = location.href.replace(/#.*$/, ''); // remove existed hash
28 | for (var i = 0; i < els.length; i++) {
29 | var el = els[i];
30 | el.href = href + el.getAttribute('href'); // because el.href is absolute path
31 | }
32 | })();
33 |
--------------------------------------------------------------------------------
/docs/script/manual.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | var matched = location.pathname.match(/\/(manual\/.*\.html)$/);
3 | if (!matched) return;
4 |
5 | var currentName = matched[1];
6 | var cssClass = '.navigation .manual-toc li[data-link="' + currentName + '"]';
7 | var styleText = cssClass + '{ display: block; }\n';
8 | styleText += cssClass + '.indent-h1 a { color: #039BE5 }';
9 | var style = document.createElement('style');
10 | style.textContent = styleText;
11 | document.querySelector('head').appendChild(style);
12 | })();
13 |
--------------------------------------------------------------------------------
/docs/script/patch-for-local.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | if (location.protocol === 'file:') {
3 | var elms = document.querySelectorAll('a[href="./"]');
4 | for (var i = 0; i < elms.length; i++) {
5 | elms[i].href = './index.html';
6 | }
7 | }
8 | })();
9 |
--------------------------------------------------------------------------------
/docs/script/pretty-print.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | prettyPrint();
3 | var lines = document.querySelectorAll('.prettyprint.linenums li[class^="L"]');
4 | for (var i = 0; i < lines.length; i++) {
5 | lines[i].id = 'lineNumber' + (i + 1);
6 | }
7 |
8 | var matched = location.hash.match(/errorLines=([\d,]+)/);
9 | if (matched) {
10 | var lines = matched[1].split(',');
11 | for (var i = 0; i < lines.length; i++) {
12 | var id = '#lineNumber' + lines[i];
13 | var el = document.querySelector(id);
14 | el.classList.add('error-line');
15 | }
16 | return;
17 | }
18 |
19 | if (location.hash) {
20 | // ``[ ] . ' " @`` are not valid in DOM id. so must escape these.
21 | var id = location.hash.replace(/([\[\].'"@$])/g, '\\$1');
22 | var line = document.querySelector(id);
23 | if (line) line.classList.add('active');
24 | }
25 | })();
26 |
--------------------------------------------------------------------------------
/docs/script/search.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | var searchIndex = window.esdocSearchIndex;
3 | var searchBox = document.querySelector('.search-box');
4 | var input = document.querySelector('.search-input');
5 | var result = document.querySelector('.search-result');
6 | var selectedIndex = -1;
7 | var prevText;
8 |
9 | // active search box and focus when mouse enter on search box.
10 | searchBox.addEventListener('mouseenter', function(){
11 | searchBox.classList.add('active');
12 | input.focus();
13 | });
14 |
15 | // search with text when key is upped.
16 | input.addEventListener('keyup', function(ev){
17 | var text = ev.target.value.toLowerCase();
18 | if (!text) {
19 | result.style.display = 'none';
20 | result.innerHTML = '';
21 | return;
22 | }
23 |
24 | if (text === prevText) return;
25 | prevText = text;
26 |
27 | var html = {class: [], method: [], member: [], function: [], variable: [], typedef: [], external: [], file: [], test: [], testFile: []};
28 | var len = searchIndex.length;
29 | var kind;
30 | for (var i = 0; i < len; i++) {
31 | var pair = searchIndex[i];
32 | if (pair[0].indexOf(text) !== -1) {
33 | kind = pair[3];
34 | html[kind].push('' + pair[2] + '');
35 | }
36 | }
37 |
38 | var innerHTML = '';
39 | for (kind in html) {
40 | var list = html[kind];
41 | if (!list.length) continue;
42 | innerHTML += '' + kind + '\n' + list.join('\n');
43 | }
44 | result.innerHTML = innerHTML;
45 | if (innerHTML) result.style.display = 'block';
46 | selectedIndex = -1;
47 | });
48 |
49 | // down, up and enter key are pressed, select search result.
50 | input.addEventListener('keydown', function(ev){
51 | if (ev.keyCode === 40) {
52 | // arrow down
53 | var current = result.children[selectedIndex];
54 | var selected = result.children[selectedIndex + 1];
55 | if (selected && selected.classList.contains('search-separator')) {
56 | var selected = result.children[selectedIndex + 2];
57 | selectedIndex++;
58 | }
59 |
60 | if (selected) {
61 | if (current) current.classList.remove('selected');
62 | selectedIndex++;
63 | selected.classList.add('selected');
64 | }
65 | } else if (ev.keyCode === 38) {
66 | // arrow up
67 | var current = result.children[selectedIndex];
68 | var selected = result.children[selectedIndex - 1];
69 | if (selected && selected.classList.contains('search-separator')) {
70 | var selected = result.children[selectedIndex - 2];
71 | selectedIndex--;
72 | }
73 |
74 | if (selected) {
75 | if (current) current.classList.remove('selected');
76 | selectedIndex--;
77 | selected.classList.add('selected');
78 | }
79 | } else if (ev.keyCode === 13) {
80 | // enter
81 | var current = result.children[selectedIndex];
82 | if (current) {
83 | var link = current.querySelector('a');
84 | if (link) location.href = link.href;
85 | }
86 | } else {
87 | return;
88 | }
89 |
90 | ev.preventDefault();
91 | });
92 |
93 | // select search result when search result is mouse over.
94 | result.addEventListener('mousemove', function(ev){
95 | var current = result.children[selectedIndex];
96 | if (current) current.classList.remove('selected');
97 |
98 | var li = ev.target;
99 | while (li) {
100 | if (li.nodeName === 'LI') break;
101 | li = li.parentElement;
102 | }
103 |
104 | if (li) {
105 | selectedIndex = Array.prototype.indexOf.call(result.children, li);
106 | li.classList.add('selected');
107 | }
108 | });
109 |
110 | // clear search result when body is clicked.
111 | document.body.addEventListener('click', function(ev){
112 | selectedIndex = -1;
113 | result.style.display = 'none';
114 | result.innerHTML = '';
115 | });
116 |
117 | })();
118 |
--------------------------------------------------------------------------------
/docs/script/test-summary.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | function toggle(ev) {
3 | var button = ev.target;
4 | var parent = ev.target.parentElement;
5 | while(parent) {
6 | if (parent.tagName === 'TR' && parent.classList.contains('test-interface')) break;
7 | parent = parent.parentElement;
8 | }
9 |
10 | if (!parent) return;
11 |
12 | var direction;
13 | if (button.classList.contains('opened')) {
14 | button.classList.remove('opened');
15 | button.classList.add('closed');
16 | direction = 'closed';
17 | } else {
18 | button.classList.remove('closed');
19 | button.classList.add('opened');
20 | direction = 'opened';
21 | }
22 |
23 | var targetDepth = parseInt(parent.dataset.testDepth, 10) + 1;
24 | var nextElement = parent.nextElementSibling;
25 | while (nextElement) {
26 | var depth = parseInt(nextElement.dataset.testDepth, 10);
27 | if (depth >= targetDepth) {
28 | if (direction === 'opened') {
29 | if (depth === targetDepth) nextElement.style.display = '';
30 | } else if (direction === 'closed') {
31 | nextElement.style.display = 'none';
32 | var innerButton = nextElement.querySelector('.toggle');
33 | if (innerButton && innerButton.classList.contains('opened')) {
34 | innerButton.classList.remove('opened');
35 | innerButton.classList.add('closed');
36 | }
37 | }
38 | } else {
39 | break;
40 | }
41 | nextElement = nextElement.nextElementSibling;
42 | }
43 | }
44 |
45 | var buttons = document.querySelectorAll('.test-summary tr.test-interface .toggle');
46 | for (var i = 0; i < buttons.length; i++) {
47 | buttons[i].addEventListener('click', toggle);
48 | }
49 |
50 | var topDescribes = document.querySelectorAll('.test-summary tr[data-test-depth="0"]');
51 | for (var i = 0; i < topDescribes.length; i++) {
52 | topDescribes[i].style.display = '';
53 | }
54 | })();
55 |
--------------------------------------------------------------------------------
/docs/test-file/test/getters/allFilesWithCategory.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | test/getters/allFilesWithCategory.js | torrent-files-library
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
28 |
29 |
65 |
66 | test/getters/allFilesWithCategory.js
67 |
import test from 'ava';
68 | import TorrentLibrary from '../../index';
69 | import { files, folders } from '../_constants';
70 |
71 |
72 | // TESTS
73 | /** @test {TorrentLibrary#allFilesWithCategory} */
74 | test('Should correctly detect the category of each file', async (t) => {
75 | let libInstance = new TorrentLibrary();
76 | await t.notThrows(libInstance.addNewPath(...folders));
77 | await t.notThrows(libInstance.scan());
78 | t.deepEqual(
79 | new Map([
80 | [files[2], TorrentLibrary.MOVIES_TYPE],
81 | [files[0], TorrentLibrary.TV_SERIES_TYPE],
82 | [files[1], TorrentLibrary.TV_SERIES_TYPE],
83 | ]),
84 | libInstance.allFilesWithCategory,
85 | 'Not the same',
86 | );
87 | });
88 |
89 |
90 |
91 |
92 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/docs/test-file/test/getters/allMovies.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | test/getters/allMovies.js | torrent-files-library
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
28 |
29 |
65 |
66 | test/getters/allMovies.js
67 |
import test from 'ava';
68 | import path from 'path';
69 | import { parse as nameParser } from 'parse-torrent-title';
70 | import TorrentLibrary from '../../index';
71 | import { files, folders } from '../_constants';
72 |
73 | // TESTS
74 | /** @test {TorrentLibrary#allMovies} */
75 | test('Returns the movies', async (t) => {
76 | let libInstance = new TorrentLibrary();
77 | await t.notThrows(libInstance.addNewPath(...folders));
78 | await t.notThrows(libInstance.scan());
79 | t.deepEqual(
80 | new Set([
81 | Object.assign(
82 | nameParser(path.basename(files[2])),
83 | { filePath: files[2] },
84 | ),
85 | ]),
86 | libInstance.allMovies,
87 | 'Not the same',
88 | );
89 | });
90 |
91 |
92 |
93 |
94 |
95 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/docs/test-file/test/getters/allTvSeries.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | test/getters/allTvSeries.js | torrent-files-library
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
28 |
29 |
65 |
66 | test/getters/allTvSeries.js
67 |
import test from 'ava';
68 | import path from 'path';
69 | import { parse as nameParser } from 'parse-torrent-title';
70 | import TorrentLibrary from '../../index';
71 | import { files, folders } from '../_constants';
72 |
73 | // TESTS
74 | /** @test {TorrentLibrary#allTvSeries} */
75 | test('Returns the tv-shows', async (t) => {
76 | let libInstance = new TorrentLibrary();
77 | await t.notThrows(libInstance.addNewPath(...folders));
78 | await t.notThrows(libInstance.scan());
79 | t.deepEqual(
80 | new Map([
81 | [nameParser(path.basename(files[0])).title, new Set([
82 | Object.assign(
83 | nameParser(path.basename(files[0])),
84 | { filePath: files[0] },
85 | ),
86 | Object.assign(
87 | nameParser(path.basename(files[1])),
88 | { filePath: files[1] },
89 | ),
90 | ])],
91 | ]),
92 | libInstance.allTvSeries,
93 | 'Not the same',
94 | );
95 | });
96 |
97 |
98 |
99 |
100 |
101 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/docs/test-file/test/getters/constants.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | test/getters/constants.js | torrent-files-library
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
28 |
29 |
65 |
66 | test/getters/constants.js
67 |
import test from 'ava';
68 | import videosExtension from 'video-extensions';
69 | import TorrentLibrary from '../../index';
70 |
71 | /** @test {TorrentLibrary.MOVIES_TYPE} */
72 | test('Constant MOVIES_TYPE', (t) => {
73 | t.is(
74 | TorrentLibrary.MOVIES_TYPE,
75 | 'MOVIES',
76 | 'Someone changed this constant value !',
77 | );
78 | });
79 |
80 | /** @test {TorrentLibrary.TV_SERIES_TYPE} */
81 | test('Constant TV_SERIES', (t) => {
82 | t.is(
83 | TorrentLibrary.TV_SERIES_TYPE,
84 | 'TV_SERIES',
85 | 'Someone changed this constant value !',
86 | );
87 | });
88 |
89 | /** @test {TorrentLibrary.listVideosExtension} */
90 | test('List of videos extension', (t) => {
91 | t.is(
92 | TorrentLibrary.listVideosExtension(),
93 | videosExtension,
94 | 'Someone changed this constant value !',
95 | );
96 | });
97 |
98 |
99 |
100 |
101 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/docs/test-file/test/methods/addNewPath.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | test/methods/addNewPath.js | torrent-files-library
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
28 |
29 |
65 |
66 | test/methods/addNewPath.js
67 |
import test from 'ava';
68 | import path from 'path';
69 | import * as sinon from 'sinon';
70 | import TorrentLibrary from '../../index';
71 | import { folders } from '../_constants';
72 |
73 | // TESTS
74 | /** @test {TorrentLibrary#addNewPath} */
75 | test('missing parameter', async (t) => {
76 | let eventSpy = sinon.spy();
77 | let libInstance = new TorrentLibrary();
78 | libInstance.on('missing_parameter', eventSpy);
79 | await t.throws(libInstance.addNewPath());
80 | t.truthy(eventSpy.called, 'Event did not fire.');
81 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
82 | t.is(
83 | libInstance.hasPathsProvidedByUser(),
84 | false, 'No paths by user should be added',
85 | );
86 | });
87 |
88 | /** @test {TorrentLibrary#addNewPath} */
89 | test('Not an existent path', async (t) => {
90 | let eventSpy = sinon.spy();
91 | let libInstance = new TorrentLibrary();
92 |
93 | libInstance.on('error_in_function', eventSpy);
94 | await t.throws(libInstance.addNewPath(path.join(__dirname, 'wrongPath')));
95 | t.truthy(eventSpy.called, 'Event did not fire.');
96 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
97 | t.is(
98 | libInstance.hasPathsProvidedByUser(),
99 | false, 'No paths by user should be added',
100 | );
101 | });
102 |
103 | /** @test {TorrentLibrary#addNewPath} */
104 | test('existent paths', async (t) => {
105 | let eventSpy = sinon.spy();
106 | let libInstance = new TorrentLibrary();
107 | libInstance.on('addNewPath', eventSpy);
108 | await t.notThrows(libInstance.addNewPath(...folders));
109 | t.truthy(eventSpy.called, 'Event did not fire.');
110 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
111 | t.is(
112 | libInstance.hasPathsProvidedByUser(),
113 | true, 'The path should be added',
114 | );
115 | });
116 |
117 |
118 |
119 |
120 |
121 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
--------------------------------------------------------------------------------
/docs/test-file/test/methods/createFromJSON.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | test/methods/createFromJSON.js | torrent-files-library
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
28 |
29 |
65 |
66 | test/methods/createFromJSON.js
67 |
import test from 'ava';
68 | import TorrentLibrary from '../../index';
69 | import { folders } from '../_constants';
70 |
71 | /** @test {TorrentLibrary.createFromJSON} */
72 | test('create a perfect copy of instance', async (t) => {
73 | let libInstance = new TorrentLibrary();
74 | await t.notThrows(libInstance.addNewPath(...folders));
75 | await t.notThrows(libInstance.scan());
76 | const jsonFromLib = JSON.parse(libInstance.toJSON());
77 | const createdInstance = TorrentLibrary.createFromJSON(jsonFromLib);
78 | t.deepEqual(
79 | createdInstance.allFilesWithCategory,
80 | libInstance.allFilesWithCategory,
81 | 'allFilesWithCategory different',
82 | );
83 | t.deepEqual(
84 | createdInstance.allMovies,
85 | libInstance.allMovies, 'allMovies different',
86 | );
87 | t.deepEqual(
88 | createdInstance.allTvSeries,
89 | libInstance.allTvSeries, 'allTvSeries different',
90 | );
91 | });
92 |
93 | // dummy test for ES6 code coverage
94 | /** @test {TorrentLibrary.createFromJSON} */
95 | test('empty instance(s)', async (t) => {
96 | TorrentLibrary.createFromJSON({});
97 | t.pass();
98 | });
99 |
100 |
101 |
102 |
103 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/docs/test-file/test/methods/scan.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | test/methods/scan.js | torrent-files-library
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
28 |
29 |
65 |
66 | test/methods/scan.js
67 |
import test from 'ava';
68 | import * as sinon from 'sinon';
69 | import { parse as nameParser } from 'parse-torrent-title';
70 | import TorrentLibrary from '../../index';
71 | import { folders } from '../_constants';
72 |
73 | // TESTS
74 | /** @test {TorrentLibrary#scan} */
75 | test('Scan without user provided paths', async (t) => {
76 | let eventSpy = sinon.spy();
77 | let libInstance = new TorrentLibrary();
78 | libInstance.on('scan', eventSpy);
79 | await t.notThrows(libInstance.scan());
80 | t.truthy(eventSpy.called, 'Event did not fire.');
81 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
82 | });
83 |
84 | /** @test {TorrentLibrary#scan} */
85 | test('Scan with user provided paths', async (t) => {
86 | let eventSpy = sinon.spy();
87 | let libInstance = new TorrentLibrary();
88 | libInstance.on('scan', eventSpy);
89 | // whatever path that should exists
90 | await t.notThrows(libInstance.addNewPath(...folders));
91 | await t.notThrows(libInstance.scan());
92 | t.truthy(eventSpy.called, 'Event did not fire.');
93 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
94 | });
95 |
96 | // test to handle default parameters
97 | /** @test {TorrentLibrary#scan} */
98 | test('Scan with user provided paths and custom parser', async (t) => {
99 | let eventSpy = sinon.spy();
100 | let libInstance = new TorrentLibrary({}, nameParser);
101 | libInstance.on('scan', eventSpy);
102 | // whatever path that should exists
103 | await t.notThrows(libInstance.addNewPath(...folders));
104 | await t.notThrows(libInstance.scan());
105 | t.truthy(eventSpy.called, 'Event did not fire.');
106 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
107 | });
108 |
109 | /** @test {TorrentLibrary#scan} */
110 | test('Scan with user provided paths and wrong custom parser', async (t) => {
111 | let eventSpy = sinon.spy();
112 | let libInstance = new TorrentLibrary({}, {});
113 | libInstance.on('error_in_function', eventSpy);
114 | // whatever path that should exists
115 | await t.notThrows(libInstance.addNewPath(...folders));
116 | await t.throws(libInstance.scan());
117 | t.truthy(eventSpy.called, 'Event did not fire.');
118 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
119 | });
120 |
121 |
122 |
123 |
124 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
--------------------------------------------------------------------------------
/docs/test-file/test/methods/toJSON.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | test/methods/toJSON.js | torrent-files-library
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
28 |
29 |
65 |
66 | test/methods/toJSON.js
67 |
import test from 'ava';
68 | import TorrentLibrary from '../../index';
69 | import { expectedJson, folders } from '../_constants';
70 |
71 | /** @test {TorrentLibrary#toJSON} */
72 | test('return a valid stringified JSON', async (t) => {
73 | const expectedJsonString = JSON.stringify(expectedJson);
74 | let libInstance = new TorrentLibrary();
75 | await t.notThrows(libInstance.addNewPath(...folders));
76 | await t.notThrows(libInstance.scan());
77 | const dataFromInstance = libInstance.toJSON();
78 | t.deepEqual(
79 | JSON.stringify(JSON.parse(dataFromInstance)),
80 | expectedJsonString,
81 | 'Not the same JSON',
82 | );
83 | });
84 |
85 |
86 |
87 |
88 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib/TorrentLibrary').default;
2 |
--------------------------------------------------------------------------------
/manual/assets/fileMapping.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/manual/assets/fileMapping.png
--------------------------------------------------------------------------------
/manual/assets/filterMovies.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/manual/assets/filterMovies.png
--------------------------------------------------------------------------------
/manual/assets/filterTvSeries.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/manual/assets/filterTvSeries.png
--------------------------------------------------------------------------------
/manual/assets/foundMovies.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/manual/assets/foundMovies.png
--------------------------------------------------------------------------------
/manual/assets/foundTvSeries.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/manual/assets/foundTvSeries.png
--------------------------------------------------------------------------------
/manual/examples/createPlaylist.md:
--------------------------------------------------------------------------------
1 | # Create custom playlist(s)
2 |
3 | ```js
4 | const TorrentLibrary = require("torrent-files-library");
5 | const m3u = require('m3u'); // a basic playlist writer (in m3u format)
6 |
7 | let paths = [
8 | 'D:/DDL/SERIES TV/Le juge et le pilote',
9 | 'D:/DDL/ANIME',
10 | ];
11 |
12 | // create an instance
13 | let libInstance = new TorrentLibrary();
14 |
15 | // add these paths inside this lib
16 | libInstance
17 | .addNewPath(...paths)
18 | .then(() => libInstance.scan())
19 | .then(() => {
20 | console.log('Now time to search all episodes <=5 in season 1 of Hardcastle And McCormick / Assassination Classroom , in one of following container avi/mp4');
21 | let filteredMap = libInstance.filterTvSeries({
22 | title: ['Hardcastle And McCormick', 'Assassination Classroom'],
23 | season: 1,
24 | episode: '<=5',
25 | container: ['avi', 'mp4'],
26 | });
27 | var writer = m3u.writer();
28 | for (let [foundTvShow, episodeSet] of filteredMap.entries()) {
29 | let foundSeasons = new Set([...episodeSet].map(episode => episode.season));
30 | for (let seasonNumber of foundSeasons) {
31 | writer.comment(`${foundTvShow} - Season ${seasonNumber}`);
32 | let seasonEpisodes = [...episodeSet].filter(episode => episode.season === seasonNumber);
33 | seasonEpisodes.forEach(episode => writer.file(`${episode.filePath}`));
34 | }
35 | }
36 | let m3uAsString = writer.toString();
37 | // save this result into a *.m3u file , using fs.writeFile or whatever you want to do that
38 | // ...
39 | })
40 | .catch((err) => {
41 | console.log(err.message);
42 | });
43 | ```
44 |
45 | ```m3u
46 | # Assassination Classroom - Season 1
47 | D:\DDL\ANIME\Assassination.Classroom.S01.FRENCH.720p.WEB-DL.x264-GODSPACE\Assassination.Classroom.S01E01.FRENCH.720p.WEB-DL.x264-GODSPACE.mp4
48 | D:\DDL\ANIME\Assassination.Classroom.S01.FRENCH.720p.WEB-DL.x264-GODSPACE\Assassination.Classroom.S01E02.FRENCH.720p.WEB-DL.x264-GODSPACE.mp4
49 | D:\DDL\ANIME\Assassination.Classroom.S01.FRENCH.720p.WEB-DL.x264-GODSPACE\Assassination.Classroom.S01E03.FRENCH.720p.WEB-DL.x264-GODSPACE.mp4
50 | D:\DDL\ANIME\Assassination.Classroom.S01.FRENCH.720p.WEB-DL.x264-GODSPACE\Assassination.Classroom.S01E04.FRENCH.720p.WEB-DL.x264-GODSPACE.mp4
51 | D:\DDL\ANIME\Assassination.Classroom.S01.FRENCH.720p.WEB-DL.x264-GODSPACE\Assassination.Classroom.S01E05.FRENCH.720p.WEB-DL.x264-GODSPACE.mp4
52 | # Hardcastle And McCormick - Season 1
53 | D:\DDL\SERIES TV\Le juge et le pilote\Saison 1\Hardcastle.And.McCormick.1x01.Le.Monstre.D_acier.avi
54 | D:\DDL\SERIES TV\Le juge et le pilote\Saison 1\Hardcastle.And.McCormick.1x03.Le.Canard.De.Cristal.avi
55 | D:\DDL\SERIES TV\Le juge et le pilote\Saison 1\Hardcastle.And.McCormick.1x04.Je.Ne.Sais.Pas.Ou.Je.Vais.Mais.J.y.Vais..avi
56 | D:\DDL\SERIES TV\Le juge et le pilote\Saison 1\Hardcastle.And.McCormick.1x05.La.Veuve.Noire..avi
57 | ```
--------------------------------------------------------------------------------
/manual/examples/filterMoviesByParameters.md:
--------------------------------------------------------------------------------
1 | # Filter movies by parameters
2 |
3 | ```js
4 | const TorrentLibrary = require("torrent-files-library");
5 |
6 | let paths = [
7 | "D:/",
8 | ];
9 |
10 | // create an instance
11 | let libInstance = new TorrentLibrary();
12 |
13 | // add these paths inside this lib
14 | libInstance
15 | .addNewPath(...paths)
16 | .then( () => {
17 | return libInstance.scan();
18 | })
19 | .then( () => {
20 | console.log('Now time to search all remastered movies with year >= 2012 in one of following container avi/mp4');
21 | let filteredSet = libInstance.filterMovies({
22 | year: '>=2012',
23 | remastered: true,
24 | container: ['avi', 'mp4'],
25 | });
26 | for (let movie of filteredSet) {
27 | console.log(`${movie.title + ((movie.year) ? ` - ${movie.year}` : '')} at ${movie.filePath}`);
28 | }
29 | })
30 | .catch( (err) => {
31 | console.log(err.message);
32 | });
33 | ```
34 |
35 | 
--------------------------------------------------------------------------------
/manual/examples/filterTvSeriesByParameters.md:
--------------------------------------------------------------------------------
1 | # Filter tv series by parameters
2 |
3 | ```js
4 | const TorrentLibrary = require("torrent-files-library");
5 |
6 | let paths = [
7 | 'D:/DDL/SERIES TV/Le juge et le pilote',
8 | 'D:/DDL/ANIME',
9 | ];
10 |
11 | // create an instance
12 | let libInstance = new TorrentLibrary();
13 |
14 | // add these paths inside this lib
15 | libInstance
16 | .addNewPath(...paths)
17 | .then(() => libInstance.scan())
18 | .then(() => {
19 | console.log('Now time to search all episodes <=5 in season 1 of Hardcastle And McCormick / Assassination Classroom , in one of following container avi/mp4');
20 | let filteredMap = libInstance.filterTvSeries({
21 | title: ['Hardcastle And McCormick', 'Assassination Classroom'],
22 | season: 1,
23 | episode: '<=5',
24 | container: ['avi', 'mp4'],
25 | });
26 | for (let [foundTvShow, episodeSet] of filteredMap.entries()) {
27 | console.log(`\n${foundTvShow}`);
28 | console.log('\t Total found episodes : ', episodeSet.size);
29 | let foundSeasons = new Set([...episodeSet].map(episode => episode.season));
30 | console.log('\t Found season(s) count : ', foundSeasons.size);
31 | for (let seasonNumber of foundSeasons) {
32 | console.log('\t\t Season %d', seasonNumber);
33 | let seasonEpisodes = [...episodeSet].filter(episode => episode.season === seasonNumber);
34 | console.log(`\t\t\t Season count : ${seasonEpisodes.length}`);
35 | console.log('\t\t\t Files : ');
36 | seasonEpisodes.forEach(episode => console.log(`\t\t\t ${episode.filePath}`));
37 | }
38 | }
39 | })
40 | .catch((err) => {
41 | console.log(err.message);
42 | });
43 | ```
44 |
45 | 
--------------------------------------------------------------------------------
/manual/examples/getCategoryForEachFile.md:
--------------------------------------------------------------------------------
1 | # Get category for each file
2 |
3 | ```js
4 | const TorrentLibrary = require("torrent-files-library");
5 |
6 | let paths = [
7 | "D:\\DDL\\FILMS", // a path where I can find both movies and tv-series
8 | "D:\\DDL\\SERIES TV\\Le juge et le pilote" // a path where I can find episodes of a tv-serie
9 | ];
10 |
11 | // create an instance
12 | let libInstance = new TorrentLibrary();
13 |
14 | // add these paths inside this lib
15 | libInstance
16 | .addNewPath(...paths)
17 | .then( (message) => {
18 | console.log(message);
19 | return libInstance.scan();
20 | })
21 | .then( (otherMessage) => {
22 | console.log(otherMessage);
23 |
24 | setTimeout(function(){
25 | // display the found files and their category
26 | libInstance
27 | .allFilesWithCategory
28 | .forEach(function(value,key){
29 | console.log(key + " : " + value);
30 | });
31 | }, 1000);
32 | })
33 | .catch( (err) => {
34 | console.log(err.message);
35 | });
36 | ```
37 | 
--------------------------------------------------------------------------------
/manual/examples/listEachTvSerie.md:
--------------------------------------------------------------------------------
1 | # List each found tv serie
2 |
3 | ```js
4 | const TorrentLibrary = require("torrent-files-library");
5 |
6 | let paths = [
7 | "D:/DDL/FILMS", // a path where I can find both movies and tv-series
8 | "D:\\DDL\\SERIES TV\\Le juge et le pilote" // a path where I can find episodes of a tv-serie
9 | ];
10 |
11 | // create an instance
12 | let libInstance = new TorrentLibrary();
13 |
14 | // add these paths inside this lib
15 | libInstance
16 | .addNewPath(...paths)
17 | .then( (message) => {
18 | console.log(message);
19 | return libInstance.scan();
20 | })
21 | .then( (otherMessage) => {
22 | console.log(otherMessage);
23 | console.log("I found these tv-series :");
24 | let mapSeries = libInstance.allTvSeries;
25 |
26 | for (let [foundTvShow,episodeSet] of mapSeries.entries() ) {
27 | console.log("\n"+foundTvShow);
28 | console.log("\t Total found episodes : ", episodeSet.size);
29 | let foundSeasons = new Set([...episodeSet].map( episode => episode.season));
30 | console.log("\t Found season(s) count : ", foundSeasons.size);
31 | for (let seasonNumber of foundSeasons){
32 | console.log("\t\t Season %d", seasonNumber);
33 | let seasonEpisodes = [...episodeSet].filter(episode => episode.season === seasonNumber);
34 | console.log("\t\t\t Season count : " + seasonEpisodes.length);
35 | console.log("\t\t\t Files : ");
36 | seasonEpisodes.forEach( episode => console.log("\t\t\t " + episode.filePath));
37 | }
38 | }
39 |
40 | })
41 | .catch( (err) => {
42 | console.log(err.message);
43 | });
44 | ```
45 |
46 | 
--------------------------------------------------------------------------------
/manual/examples/listFoundMovies.md:
--------------------------------------------------------------------------------
1 | # List found movies
2 |
3 | ```js
4 | const TorrentLibrary = require("torrent-files-library");
5 |
6 | let paths = [
7 | "D:\\DDL\\FILMS", // a path where I can find both movies and tv-series
8 | "D:\\DDL\\SERIES TV\\Le juge et le pilote" // a path where I can find episodes of a tv-serie
9 | ];
10 |
11 | // create an instance
12 | let libInstance = new TorrentLibrary();
13 |
14 | // add these paths inside this lib
15 | libInstance
16 | .addNewPath(...paths)
17 | .then( (message) => {
18 | console.log(message);
19 | return libInstance.scan();
20 | })
21 | .then( (otherMessage) => {
22 | console.log(otherMessage);
23 | console.log("I found these movie(s) : ");
24 | setTimeout(function(){
25 | // display the found movie(s)
26 | for (let movie of libInstance.allMovies) {
27 | console.log(movie.title + ((movie.year) ? " - " + movie.year : "") + " at " + movie.filePath );
28 | }
29 | }, 1000);
30 | })
31 | .catch( (err) => {
32 | console.log(err.message);
33 | });
34 | ```
35 | 
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "torrent-files-library",
3 | "version": "1.5.1",
4 | "description": "Scan directories to build a library of media files (movie or tv show) that follows torrent naming conventions",
5 | "main": "index.js",
6 | "files": [
7 | "index.js",
8 | "lib/"
9 | ],
10 | "scripts": {
11 | "semantic-release": "semantic-release -e ./config/release.config.js",
12 | "compile": "babel -d lib/ src/ --source-maps both",
13 | "prepare": "cross-env BABEL_ENV=production npm run compile",
14 | "test": "cross-env BABEL_ENV=test npm run compile && nyc ava",
15 | "coverage": "nyc report --reporter=text-lcov | coveralls",
16 | "coverage-html": "nyc report --reporter=html",
17 | "esdoc": "node_modules/.bin/esdoc",
18 | "lint": "./node_modules/.bin/eslint -c config/.eslintrc.js ./src ./test --fix"
19 | },
20 | "engines": {
21 | "node": ">=6",
22 | "npm": ">=5"
23 | },
24 | "keywords": [
25 | "torrent",
26 | "library",
27 | "parser",
28 | "torrent file",
29 | "torrent files",
30 | "parse torrent",
31 | "parse torrent file",
32 | "parse torrent files",
33 | "parse torrent name",
34 | "torrent library"
35 | ],
36 | "author": "jy95",
37 | "license": "MIT",
38 | "repository": {
39 | "type": "git",
40 | "url": "https://github.com/jy95/torrent-files-library.git"
41 | },
42 | "greenkeeper": {
43 | "commitMessages": {
44 | "initialBadge": "Docs: Add Greenkeeper badge",
45 | "initialDependencies": "Upgrade: Update dependencies",
46 | "initialBranches": "Build: Whitelist greenkeeper branches",
47 | "dependencyUpdate": "Upgrade: Update ${dependency} to version ${version}",
48 | "devDependencyUpdate": "Upgrade: Update ${dependency} to version ${version}",
49 | "dependencyPin": "Fix: Pin ${dependency} to ${oldVersion}",
50 | "devDependencyPin": "Fix: Pin ${dependency} to ${oldVersion}"
51 | }
52 | },
53 | "devDependencies": {
54 | "@babel/cli": "^7.1.2",
55 | "@babel/core": "^7.1.2",
56 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0",
57 | "@babel/preset-env": "^7.1.0",
58 | "@babel/register": "^7.0.0",
59 | "@semantic-release/changelog": "^3.0.1",
60 | "@semantic-release/git": "^7.0.3",
61 | "ava": "^0.25.0",
62 | "babel-plugin-istanbul": "^5.1.1",
63 | "babel-preset-minify": "^0.5.0",
64 | "conventional-changelog-eslint": "^3.0.0",
65 | "coveralls": "^3.0.0",
66 | "cross-env": "^6.0.0",
67 | "esdoc": "^1.0.4",
68 | "esdoc-ecmascript-proposal-plugin": "^1.0.0",
69 | "esdoc-standard-plugin": "^1.0.0",
70 | "eslint": "^6.0.0",
71 | "eslint-config-airbnb-base": "^14.0.0",
72 | "eslint-plugin-import": "^2.8.0",
73 | "nyc": "^14.0.0",
74 | "semantic-release": "^15.12.1",
75 | "sinon": "^7.2.4"
76 | },
77 | "dependencies": {
78 | "bluebird": "^3.5.1",
79 | "filehound": "^1.17.3",
80 | "lodash": "^4.17.11",
81 | "parse-torrent-title": "^1.1.0",
82 | "video-extensions": "^1.1.0"
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/docs/events/events.js:
--------------------------------------------------------------------------------
1 | // events
2 |
3 | /**
4 | * missing_parameter event
5 | * @typedef {Object} Events#missing_parameter
6 | * @property {string} functionName - Indicates the function name when the error occurs.
7 | * @example
8 | * TorrentLibraryInstance.on('missing_parameter',function(callback){
9 | * console.log('Parameter is missing in ' + callback.functionName);
10 | * })
11 | */
12 |
13 | /**
14 | * error_in_function event
15 | * @typedef {Object} Events#error_in_function
16 | * @property {string} functionName - Indicates the function name when the error occurs.
17 | * @property {string} error - The error message got by error.message
18 | * @example
19 | * TorrentLibraryInstance.on('error_in_function',function(callback){
20 | * console.log('Function ' + callback.functionName + ' has the following error : ' + callback.error);
21 | * })
22 | */
23 |
24 | /**
25 | * addNewPath event
26 | * @typedef {Object} Events#addNewPath
27 | * @property {...string} paths - all the paths that were added
28 | * @example
29 | * TorrentLibraryInstance.on('addNewPath',function(callback){
30 | * console.log('The following files were added : ' + callback.paths);
31 | * })
32 | */
33 |
34 | /**
35 | * scan event
36 | * @typedef {object} Events#scan
37 | * @property {...string} files - all the files that were found and added if not yet in lib
38 | * @example
39 | * TorrentLibraryInstance.on('scan',function(callback){
40 | * console.log('The following files were found : ' + callback.files);
41 | * })
42 | */
43 |
44 | /**
45 | * removeOldFiles event
46 | * @typedef {object} Events#removeOldFiles
47 | * @property {...string} files - all the files that were found and removed if not yet in lib
48 | * @example
49 | * TorrentLibraryInstance.on('removeOldFiles',function(callback){
50 | * console.log('The following files were added : ' + callback.files);
51 | * })
52 | */
53 |
--------------------------------------------------------------------------------
/src/docs/types/types.js:
--------------------------------------------------------------------------------
1 | // ESDoc custom typedef
2 |
3 | /**
4 | * The result of parsing file name
5 | * @typedef {Object} TPN
6 | * @see {@link https://github.com/clement-escolano/parse-torrent-title}
7 | * @property {(string)} title - The file title
8 | * @property {(number)} [season] - The season number
9 | * @property {(number)} [episode] - The episode number
10 | * @property {(number)} [year] - The year
11 | * @property {(string)} [resolution] - The resolution
12 | * @property {(string)} [codec] - The codec
13 | * @property {(string)} [audio] - The audio
14 | * @property {(string)} [group] - The group that releases this file
15 | * @property {(string)} [region] - The region
16 | * @property {(string)} [container] - The container
17 | * @property {(string)} [language] - The file language
18 | * @property {(boolean)} [extended] - extended ?
19 | * @property {(boolean)} [unrated] - unrated ?
20 | * @property {(boolean)} [proper] - proper ?
21 | * @property {(boolean)} [repack] - repack ?
22 | * @property {(boolean)} [convert] - convert ?
23 | * @property {(boolean)} [hardcoded] - hardcoded ?
24 | * @property {(boolean)} [retail] - retail ?
25 | * @property {(boolean)} [remastered] - remastered ?
26 | * @property {(string)} [source] - the source
27 | * @example
28 | * {
29 | * "container":"avi",
30 | * "source":"webrip",
31 | * "codec":"xvid",
32 | * "season":4,
33 | * "episode":14,
34 | * "language":"french",
35 | * "title":"The Blacklist"
36 | * }
37 | */
38 |
39 | /**
40 | * The extended TPN object
41 | * @typedef {TPN} TPN_Extended
42 | * @property {string} filePath - additionnal property useful for this library
43 | * @example
44 | * {
45 | * "container":"avi",
46 | * "source":"webrip",
47 | * "codec":"xvid",
48 | * "season":4,
49 | * "episode":14,
50 | * "language":"french",
51 | * "title":"The Blacklist",
52 | * "filePath":"D:\\workspaceNodeJs\\torrent-files-library\\test\\folder2\\The.Blacklist.S04E14.FRENCH.WEBRip.XviD.avi"
53 | * }
54 | */
55 |
56 | /**
57 | * The sub way to store all kind of media files found in paths
58 | * @typedef {Set| Map} StorageVar
59 | */
60 |
61 | /**
62 | * The master variable where we store all kind of media files found in paths
63 | * @typedef {Map} StoreVar
64 | * @example
65 | * // An example of the variable after the scan method
66 | * [
67 | * "MOVIES" : [
68 | * {
69 | * "year": 2014,
70 | * "resolution": '1080p',
71 | * "source": 'brrip',
72 | * "codec": 'x264',
73 | * "container": 'mkv',
74 | * "title": 'Captain Russia The Summer Soldier',
75 | * "filePath": "D:\somePath\Captain Russia The Summer Soldier (2014) 1080p BrRip x264.MKV"
76 | * }
77 | * ],
78 | * "TV_SERIES" : [
79 | * "The Blacklist" : [
80 | * {
81 | * "season": 4,
82 | * "episode": 21,
83 | * "source": "webrip",
84 | * "codec": "xvid",
85 | * "container": "avi",
86 | * "language": "french"
87 | * "filePath" : "D:\somePath\The.Blacklist.S04E21.FRENCH.WEBRip.XviD.avi"
88 | * }
89 | * ]
90 | * ]
91 | * ]
92 | */
93 |
94 | /**
95 | * The search syntax for number properties : a operator follows by a number
96 | * @typedef {string} numberSearchSyntax
97 | * @example
98 | * '<=25'
99 | * @example
100 | * '=25'
101 | */
102 |
103 | /**
104 | * Number expression for number filtering
105 | * @typedef {Object} numberExpressionObject
106 | * @property {string} operator The operator for matching process
107 | * @property {number} number The extracted number for matching process
108 | * @example
109 | * { operator: '>=' , number: 5 }
110 | */
111 |
112 | /**
113 | * allows user to provide custom filtering stuff
114 | * @typedef {Object} additionalProperties
115 | * @property {string} type Filter type - Possible values are 'number' , 'string' , 'number'
116 | * @property {string} name The requested property
117 | * @property {boolean|string|string[]|number|numberSearchSyntax} value The requested value
118 | * @example
119 | * { type: 'number', name: 'AnotherField2', value: '<=25' }
120 | * { type: 'boolean', name: 'AnotherField', value: true }
121 | * { type: 'number', name: 'AnotherField2', value: 25 }
122 | * { type: 'string', name: 'AnotherField', value: ['NothingExists', 'NothingExists'] }
123 | */
124 |
125 | /**
126 | * search parameters object
127 | * @typedef {Object} searchParameters - search parameters.
128 | * @property {boolean} [extended=undefined] - extended ?
129 | * @property {boolean} [unrated=undefined] - unrated ?
130 | * @property {boolean} [proper=undefined] - proper ?
131 | * @property {boolean} [repack=undefined] - repack ?
132 | * @property {boolean} [convert=undefined] - convert ?
133 | * @property {boolean} [hardcoded=undefined] - hardcoded ?
134 | * @property {boolean} [retail=undefined] - retail ?
135 | * @property {boolean} [remastered=undefined] - remastered ?
136 | * @property {number|numberSearchSyntax} [season=undefined] - the season
137 | * @property {number|numberSearchSyntax} [episode=undefined] - the episode
138 | * @property {number|numberSearchSyntax} [year=undefined] - the year
139 | * @property {string|string[]} [title=undefined] - the title
140 | * @property {string|string[]} [resolution=undefined] - the resolution
141 | * @property {string|string[]} [codec=undefined] - the codec
142 | * @property {string|string[]} [audio=undefined] - the audio
143 | * @property {string|string[]} [group=undefined] - the group
144 | * @property {string|string[]} [region=undefined] - the region
145 | * @property {string|string[]} [container=undefined] - the container
146 | * @property {string|string[]} [language=undefined] - the language
147 | * @property {string|string[]} [source=undefined] - the source
148 | * @property {additionalProperties[]} [additionalProperties=[]] - additional Properties
149 | */
150 |
151 | /**
152 | * A parsing function to be used with this lib
153 | * @typedef {Function} customParsingFunction
154 | * @param {string} title - The file name
155 | * @return {TPN} the result
156 | * @example
157 | * // default parser used in this lib
158 | * const parser = require("parse-torrent-title").parser;
159 | * @example
160 | * // extended default parser as explained here : https://github.com/clement-escolano/parse-torrent-title#advanced-usage
161 | * const ptt = require("parse-torrent-title");
162 | * ptt.addHandler("part", /Part[. ]([0-9])/i, { type: "integer" });
163 | * const parser = ptt.parse;
164 | * @example
165 | * // original parser used in this lib : https://github.com/jy95/torrent-name-parser
166 | * const parser = require('torrent-name-parser');
167 | */
168 |
--------------------------------------------------------------------------------
/src/filters/filterBooleanProperty.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Provides a map with valid default properties
3 | * @param {searchParameters} searchObject - search parameters
4 | * @return {Map} the result map
5 | */
6 | export function filterDefaultBooleanProperties(searchObject) {
7 | const {
8 | extended, unrated, proper, repack, convert, hardcoded, retail, remastered,
9 | } = searchObject;
10 |
11 |
12 | const propertiesArray = [extended, unrated, proper,
13 | repack, convert, hardcoded, retail, remastered];
14 | const propertiesNames = ['extended', 'unrated', 'proper', 'repack', 'convert',
15 | 'hardcoded', 'retail', 'remastered'];
16 |
17 | return propertiesArray.reduce((propertiesMap, val, index) => {
18 | // eslint-disable-next-line max-len
19 | if (val === true || val === false) { propertiesMap.set(propertiesNames[index], val); }
20 | return propertiesMap;
21 | }, new Map());
22 | }
23 |
24 | /**
25 | * Remove the default boolean properties
26 | * @param {searchParameters} searchObject - search parameters
27 | * @return {searchParameters} searchParameters without these properties
28 | */
29 | export function excludeDefaultBooleanProperties(searchObject) {
30 | let {
31 | extended, unrated, proper, repack, convert, hardcoded, retail, remastered,
32 | ...rest
33 | } = searchObject;
34 | return rest;
35 | }
36 |
37 | /**
38 | * Filter the set based on boolean properties
39 | * @param {Set} set The TPN set
40 | * @param {Map} propertiesMap The map from filterDefaultBooleanProperties
41 | * @return {Set} the filtered set
42 | */
43 | export function filterByBoolean(set, propertiesMap) {
44 | // first step : get an array so that we can do filter/reduce stuff
45 | // second step : iterate the propertiesMap and do filter and return the filtered array
46 | // val[0] : the key ; val[1] : the value
47 | return new Set(Array
48 | .from(propertiesMap.entries())
49 | .reduce(
50 | // eslint-disable-next-line max-len
51 | (currentMoviesArray, val) => currentMoviesArray.filter(TPN => TPN[val[0]] === val[1])
52 | , [...set],
53 | ));
54 | }
55 |
--------------------------------------------------------------------------------
/src/filters/filterNumberProperty.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Convert the param to valid expression object for filter function
3 | * @param {number|numberSearchSyntax} param The param to be converted
4 | * @return {Object} If valid, returns a object. If not, returns undefined
5 | * @property {string} operator The operator for matching process
6 | * @property {number} number The extracted number for matching process
7 | * @example
8 | * // returns { operator: '==' , number: 5 }
9 | * convertToValidExpression(5);
10 | * @example
11 | * // returns { operator: '>=' , number: 5 }
12 | * convertToValidExpression(">5");
13 | * @example
14 | * // returns undefined
15 | * convertToValidExpression(undefined);
16 | */
17 | export function convertToValidExpression(param) {
18 | const validExpression = /^(==|>|<|>=|<=)(\d+)$/;
19 | let returnValue;
20 | // if it is a valid number expression like the regex
21 | if (validExpression.test(param)) {
22 | let result = param.match(validExpression);
23 | returnValue = {
24 | operator: result[1],
25 | number: Number(result[2]),
26 | };
27 | }
28 | // if the param is a number
29 | if (Number.isInteger(param)) {
30 | returnValue = {
31 | operator: '==',
32 | number: param,
33 | };
34 | }
35 | return returnValue;
36 | }
37 |
38 | /**
39 | * Filter function for filterByNumber
40 | * @param {string} property The property to be checked
41 | * @param {Object} expressionObject The object from convertToValidExpression
42 | * @param {string} expressionObject.operator The operator for matching process
43 | * @param {number} expressionObject.number The extracted number for matching process
44 | * @param {TPN} object the object to be checked
45 | * @return {boolean} the result
46 | */
47 | function resolveExpression(property, expressionObject, object) {
48 | let { operator, number } = expressionObject;
49 | // No : eval is not all evil but you should know what you are doing
50 | // eslint-disable-next-line no-eval
51 | return eval(`${object[property]}${operator}${number}`);
52 | }
53 |
54 | /**
55 | * Provides a map with valid default properties
56 | * @param {searchParameters} searchObject - search parameters
57 | * @return {Map} the result map
58 | */
59 | export function filterDefaultNumberProperties(searchObject) {
60 | const {
61 | season, episode, year,
62 | } = searchObject;
63 |
64 |
65 | const propertiesArray = [season, episode, year];
66 | const propertiesNames = ['season', 'episode', 'year'];
67 |
68 | return propertiesArray.reduce((propertiesMap, val, index) => {
69 | if (val !== undefined) {
70 | propertiesMap.set(propertiesNames[index], convertToValidExpression(val));
71 | }
72 | return propertiesMap;
73 | }, new Map());
74 | }
75 |
76 | /**
77 | * Remove the default number properties
78 | * @param {searchParameters} searchObject - search parameters
79 | * @return {searchParameters} searchParameters without these properties
80 | */
81 | export function excludeDefaultNumberProperties(searchObject) {
82 | const {
83 | season, episode, year,
84 | ...rest
85 | } = searchObject;
86 | return rest;
87 | }
88 |
89 | /**
90 | * Filter the set based on string properties
91 | * @param {Set} set The TPN set
92 | * @param {Map} propertiesMap The map from filterDefaultStringProperties
93 | * @return {Set} the filtered set
94 | */
95 | export function filterByNumber(set, propertiesMap) {
96 | // first step : get an array so that we can do filter/reduce stuff
97 | // second step : iterate the propertiesMap and do filter and return the filtered array
98 | // val[0] : the key ; val[1] : the value
99 | return new Set(Array
100 | .from(propertiesMap.entries())
101 | .reduce(
102 | // eslint-disable-next-line max-len
103 | (currentMoviesArray, val) => currentMoviesArray.filter(TPN => resolveExpression(val[0], val[1], TPN))
104 | , [...set],
105 | ));
106 | }
107 |
--------------------------------------------------------------------------------
/src/filters/filterProperties.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-useless-escape,max-len */
2 | /**
3 | * Boolean properties filter
4 | */
5 | import {
6 | filterDefaultBooleanProperties,
7 | filterByBoolean,
8 | excludeDefaultBooleanProperties,
9 | } from './filterBooleanProperty';
10 |
11 | /**
12 | * Number properties filter
13 | */
14 | import {
15 | convertToValidExpression,
16 | excludeDefaultNumberProperties,
17 | filterDefaultNumberProperties,
18 | filterByNumber,
19 | } from './filterNumberProperty';
20 |
21 | /**
22 | * String properties filter
23 | */
24 | import {
25 | excludeDefaultStringProperties,
26 | filterDefaultStringProperties,
27 | filterByString,
28 | } from './filterStringProperty';
29 |
30 | /**
31 | * Handle searchParameters provided by user to maps
32 | * @param {searchParameters} searchParameters - search parameters.
33 | * @return {{booleanFieldsSearchMap: Map, numberFieldsSearchMap: Map, stringFieldsSearchMap: Map}} an object that contains mapped properties for search
34 | */
35 | function mapProperties(searchParameters) {
36 | // organize search based on field type : boolean - string - number
37 | const booleanFieldsSearchMap = filterDefaultBooleanProperties(searchParameters);
38 | let leftSearchParameters = excludeDefaultBooleanProperties(searchParameters);
39 |
40 | const numberFieldsSearchMap = filterDefaultNumberProperties(leftSearchParameters);
41 | leftSearchParameters = excludeDefaultNumberProperties(leftSearchParameters);
42 |
43 | const stringFieldsSearchMap = filterDefaultStringProperties(leftSearchParameters);
44 | leftSearchParameters = excludeDefaultStringProperties(leftSearchParameters);
45 |
46 | let { additionalProperties } = leftSearchParameters;
47 | // add the optional new properties , optionally provided by user
48 | /* istanbul ignore else */
49 | if (additionalProperties !== undefined) {
50 | additionalProperties
51 | .filter(newProperty => newProperty.type === 'boolean')
52 | .forEach((newProperty) => {
53 | booleanFieldsSearchMap.set(newProperty.name, newProperty.value);
54 | });
55 |
56 | additionalProperties
57 | .filter(newProperty => newProperty.type === 'number')
58 | .forEach((newProperty) => {
59 | let expression = convertToValidExpression(newProperty.value);
60 | /* istanbul ignore else */
61 | if (expression !== undefined) {
62 | numberFieldsSearchMap.set(newProperty.name, expression);
63 | }
64 | });
65 |
66 | additionalProperties
67 | .filter(newProperty => newProperty.type === 'string')
68 | .forEach((newProperty) => {
69 | stringFieldsSearchMap.set(newProperty.name, [...newProperty.value]);
70 | });
71 | }
72 |
73 | return {
74 | booleanFieldsSearchMap,
75 | numberFieldsSearchMap,
76 | stringFieldsSearchMap,
77 | };
78 | }
79 |
80 | /**
81 | * Filter the movies based on search parameters
82 | * @param {searchParameters} searchParameters - search parameters.
83 | * @param {Set} allMovies - the movies set
84 | * @return {Set} the filtered movie set
85 | */
86 | export function filterMoviesByProperties(searchParameters, allMovies) {
87 | const {
88 | booleanFieldsSearchMap, stringFieldsSearchMap,
89 | numberFieldsSearchMap,
90 | } = mapProperties(searchParameters);
91 | const propertiesWithAllProperties
92 | = [booleanFieldsSearchMap, stringFieldsSearchMap, numberFieldsSearchMap];
93 | let result = allMovies;
94 | [filterByBoolean, filterByString, filterByNumber]
95 | .forEach((filterFunction, index) => {
96 | result = filterFunction(result, propertiesWithAllProperties[index]);
97 | });
98 | return result;
99 | }
100 |
101 | /**
102 | * Filter the tv series based on search parameters
103 | * @param {searchParameters} searchParameters - search parameters.
104 | * @param {(Map>)} allTvSeries - the tvSeries map
105 | * @return {(Map>)} the filtered tvSeries map
106 | */
107 | export function filterTvSeriesByProperties(searchParameters, allTvSeries) {
108 | const {
109 | booleanFieldsSearchMap, stringFieldsSearchMap,
110 | numberFieldsSearchMap,
111 | } = mapProperties(searchParameters);
112 | const propertiesWithAllProperties
113 | = [booleanFieldsSearchMap, stringFieldsSearchMap, numberFieldsSearchMap];
114 | let result = allTvSeries;
115 | // filtering stuff
116 | // it also removes all entries that have an empty Set so that we can clearly see only valid things
117 |
118 | [filterByBoolean, filterByString, filterByNumber]
119 | .forEach((filterFunction, index) => {
120 | result = new Map(Array
121 | .from(
122 | result.entries(),
123 | ([showName, showSet]) => [showName, filterFunction(showSet, propertiesWithAllProperties[index])],
124 | )
125 | // eslint-disable-next-line no-unused-vars
126 | .filter(([showName, showSet]) => showSet.size > 0));
127 | });
128 |
129 | return result;
130 | }
131 |
--------------------------------------------------------------------------------
/src/filters/filterStringProperty.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Provides a map with valid default properties
3 | * @param {searchParameters} searchObject - search parameters
4 | * @return {Map} the result map
5 | */
6 | export function filterDefaultStringProperties(searchObject) {
7 | const {
8 | title, resolution, codec, audio, group, region, container, language, source,
9 | } = searchObject;
10 |
11 |
12 | const propertiesArray = [title, resolution, codec, audio, group,
13 | region, container, language, source];
14 | const propertiesNames = ['title', 'resolution', 'codec', 'audio', 'group',
15 | 'region', 'container', 'language', 'source'];
16 |
17 | return propertiesArray.reduce((propertiesMap, val, index) => {
18 | if (val !== undefined) {
19 | propertiesMap.set(propertiesNames[index], val);
20 | }
21 | return propertiesMap;
22 | }, new Map());
23 | }
24 |
25 | /**
26 | * Remove the default string properties
27 | * @param {searchParameters} searchObject - search parameters
28 | * @return {searchParameters} searchParameters without these properties
29 | */
30 | export function excludeDefaultStringProperties(searchObject) {
31 | let {
32 | title, resolution, codec, audio, group, region, container, language, source,
33 | ...rest
34 | } = searchObject;
35 | return rest;
36 | }
37 |
38 | /**
39 | * Filter function for filterByString
40 | * @param {string} property The property to be checked
41 | * @param {string[]|string} expected The expected result
42 | * @param {TPN} object the object to be checked
43 | * @return {boolean} the result
44 | */
45 | function filterFunctionByType(property, expected, object) {
46 | if (Array.isArray(expected)) { return expected.includes(object[property]); }
47 | return object[property] === expected;
48 | }
49 |
50 | /**
51 | * Filter the set based on string properties
52 | * @param {Set} set The TPN set
53 | * @param {Map} propertiesMap The map from filterDefaultStringProperties
54 | * @return {Set} the filtered set
55 | */
56 | export function filterByString(set, propertiesMap) {
57 | // first step : get an array so that we can do filter/reduce stuff
58 | // second step : iterate the propertiesMap and do filter and return the filtered array
59 | // val[0] : the key ; val[1] : the value
60 | return new Set(Array
61 | .from(propertiesMap.entries())
62 | .reduce(
63 | // eslint-disable-next-line max-len
64 | (currentMoviesArray, val) => currentMoviesArray.filter(TPN => filterFunctionByType(val[0], val[1], TPN))
65 | , [...set],
66 | ));
67 | }
68 |
--------------------------------------------------------------------------------
/test/_constants.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 | import { basename, join } from 'path';
3 | import { parse as nameParser } from 'parse-torrent-title';
4 |
5 | export const folders = [join(__dirname, 'fixtures', 'folder1'),
6 | join(__dirname, 'fixtures', 'folder2')];
7 |
8 | export const files = [
9 | join(
10 | folders[0],
11 | 'The.Blacklist.S04E21.FRENCH.WEBRip.XviD.avi',
12 | ),
13 | join(
14 | folders[1],
15 | 'The.Blacklist.S04E14.FRENCH.WEBRip.XviD.avi',
16 | ),
17 | join(
18 | folders[0],
19 | 'Bad.Ass.2012.REMASTERED.TRUEFRENCH.DVDRiP.XviD' +
20 | '-www.zone-telechargement.ws.avi',
21 | ),
22 | ];
23 |
24 | // json for test
25 | export const expectedJson = {
26 | paths: [
27 | ...folders,
28 | ],
29 | allFilesWithCategory: [
30 | [
31 | files[2],
32 | 'MOVIES',
33 | ],
34 | [
35 | files[0],
36 | 'TV_SERIES',
37 | ],
38 | [
39 | files[1],
40 | 'TV_SERIES',
41 | ],
42 | ],
43 | movies: [
44 | Object.assign(nameParser(basename(files[2])), {
45 | filePath: files[2],
46 | }),
47 | ],
48 | 'tv-series': [
49 | [
50 | 'The Blacklist',
51 | [
52 | Object.assign(nameParser(basename(files[0])), {
53 | filePath: files[0],
54 | }),
55 | Object.assign(nameParser(basename(files[1])), {
56 | filePath: files[1],
57 | }),
58 | ],
59 | ],
60 | ],
61 | };
62 |
63 |
--------------------------------------------------------------------------------
/test/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "paths":[
3 | "D:\\workspaceNodeJs\\torrent-files-library\\test\\folder1",
4 | "D:\\workspaceNodeJs\\torrent-files-library\\test\\folder2"
5 | ],
6 | "allFilesWithCategory":[
7 | [
8 | "D:\\workspaceNodeJs\\torrent-files-library\\test\\folder1\\Bad.Ass.2012.LiMiTED.TRUEFRENCH.DVDRiP.XviD-www.zone-telechargement.ws.avi",
9 | "MOVIES"
10 | ],
11 | [
12 | "D:\\workspaceNodeJs\\torrent-files-library\\test\\folder1\\The.Blacklist.S04E21.FRENCH.WEBRip.XviD.avi",
13 | "TV_SERIES"
14 | ],
15 | [
16 | "D:\\workspaceNodeJs\\torrent-files-library\\test\\folder2\\The.Blacklist.S04E14.FRENCH.WEBRip.XviD.avi",
17 | "TV_SERIES"
18 | ]
19 | ],
20 | "movies":[
21 | {
22 | "year":2012,
23 | "container":"avi",
24 | "source":"dvdrip",
25 | "codec":"xvid",
26 | "language":"truefrench",
27 | "title":"Bad Ass",
28 | "filePath":"D:\\workspaceNodeJs\\torrent-files-library\\test\\folder1\\Bad.Ass.2012.LiMiTED.TRUEFRENCH.DVDRiP.XviD-www.zone-telechargement.ws.avi"
29 | }
30 | ],
31 | "tv-series":[
32 | [
33 | "The Blacklist",
34 | [
35 | {
36 | "container":"avi",
37 | "source":"webrip",
38 | "codec":"xvid",
39 | "season":4,
40 | "episode":21,
41 | "language":"french",
42 | "title":"The Blacklist",
43 | "filePath":"D:\\workspaceNodeJs\\torrent-files-library\\test\\folder1\\The.Blacklist.S04E21.FRENCH.WEBRip.XviD.avi"
44 | },
45 | {
46 | "container":"avi",
47 | "source":"webrip",
48 | "codec":"xvid",
49 | "season":4,
50 | "episode":14,
51 | "language":"french",
52 | "title":"The Blacklist",
53 | "filePath":"D:\\workspaceNodeJs\\torrent-files-library\\test\\folder2\\The.Blacklist.S04E14.FRENCH.WEBRip.XviD.avi"
54 | }
55 | ]
56 | ]
57 | ]
58 | }
--------------------------------------------------------------------------------
/test/fixtures/folder1/Bad.Ass.2012.REMASTERED.TRUEFRENCH.DVDRiP.XviD-www.zone-telechargement.ws.avi:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/test/fixtures/folder1/Bad.Ass.2012.REMASTERED.TRUEFRENCH.DVDRiP.XviD-www.zone-telechargement.ws.avi
--------------------------------------------------------------------------------
/test/fixtures/folder1/The.Blacklist.S04E21.FRENCH.WEBRip.XviD.avi:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/test/fixtures/folder1/The.Blacklist.S04E21.FRENCH.WEBRip.XviD.avi
--------------------------------------------------------------------------------
/test/fixtures/folder2/The.Blacklist.S04E14.FRENCH.WEBRip.XviD.avi:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/test/fixtures/folder2/The.Blacklist.S04E14.FRENCH.WEBRip.XviD.avi
--------------------------------------------------------------------------------
/test/getters/allFilesWithCategory.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import TorrentLibrary from '../../index';
3 | import { files, folders } from '../_constants';
4 |
5 |
6 | // TESTS
7 | /** @test {TorrentLibrary#allFilesWithCategory} */
8 | test('Should correctly detect the category of each file', async (t) => {
9 | let libInstance = new TorrentLibrary();
10 | await t.notThrows(libInstance.addNewPath(...folders));
11 | await t.notThrows(libInstance.scan());
12 | t.deepEqual(
13 | new Map([
14 | [files[2], TorrentLibrary.MOVIES_TYPE],
15 | [files[0], TorrentLibrary.TV_SERIES_TYPE],
16 | [files[1], TorrentLibrary.TV_SERIES_TYPE],
17 | ]),
18 | libInstance.allFilesWithCategory,
19 | 'Not the same',
20 | );
21 | });
22 |
--------------------------------------------------------------------------------
/test/getters/allMovies.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import path from 'path';
3 | import { parse as nameParser } from 'parse-torrent-title';
4 | import TorrentLibrary from '../../index';
5 | import { files, folders } from '../_constants';
6 |
7 | // TESTS
8 | /** @test {TorrentLibrary#allMovies} */
9 | test('Returns the movies', async (t) => {
10 | let libInstance = new TorrentLibrary();
11 | await t.notThrows(libInstance.addNewPath(...folders));
12 | await t.notThrows(libInstance.scan());
13 | t.deepEqual(
14 | new Set([
15 | Object.assign(
16 | nameParser(path.basename(files[2])),
17 | { filePath: files[2] },
18 | ),
19 | ]),
20 | libInstance.allMovies,
21 | 'Not the same',
22 | );
23 | });
24 |
25 |
--------------------------------------------------------------------------------
/test/getters/allTvSeries.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import path from 'path';
3 | import { parse as nameParser } from 'parse-torrent-title';
4 | import TorrentLibrary from '../../index';
5 | import { files, folders } from '../_constants';
6 |
7 | // TESTS
8 | /** @test {TorrentLibrary#allTvSeries} */
9 | test('Returns the tv-shows', async (t) => {
10 | let libInstance = new TorrentLibrary();
11 | await t.notThrows(libInstance.addNewPath(...folders));
12 | await t.notThrows(libInstance.scan());
13 | t.deepEqual(
14 | new Map([
15 | [nameParser(path.basename(files[0])).title, new Set([
16 | Object.assign(
17 | nameParser(path.basename(files[0])),
18 | { filePath: files[0] },
19 | ),
20 | Object.assign(
21 | nameParser(path.basename(files[1])),
22 | { filePath: files[1] },
23 | ),
24 | ])],
25 | ]),
26 | libInstance.allTvSeries,
27 | 'Not the same',
28 | );
29 | });
30 |
31 |
--------------------------------------------------------------------------------
/test/getters/constants.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import videosExtension from 'video-extensions';
3 | import TorrentLibrary from '../../index';
4 |
5 | /** @test {TorrentLibrary.MOVIES_TYPE} */
6 | test('Constant MOVIES_TYPE', (t) => {
7 | t.is(
8 | TorrentLibrary.MOVIES_TYPE,
9 | 'MOVIES',
10 | 'Someone changed this constant value !',
11 | );
12 | });
13 |
14 | /** @test {TorrentLibrary.TV_SERIES_TYPE} */
15 | test('Constant TV_SERIES', (t) => {
16 | t.is(
17 | TorrentLibrary.TV_SERIES_TYPE,
18 | 'TV_SERIES',
19 | 'Someone changed this constant value !',
20 | );
21 | });
22 |
23 | /** @test {TorrentLibrary.listVideosExtension} */
24 | test('List of videos extension', (t) => {
25 | t.is(
26 | TorrentLibrary.listVideosExtension(),
27 | videosExtension,
28 | 'Someone changed this constant value !',
29 | );
30 | });
31 |
--------------------------------------------------------------------------------
/test/methods/addNewPath.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import path from 'path';
3 | import * as sinon from 'sinon';
4 | import TorrentLibrary from '../../index';
5 | import { folders } from '../_constants';
6 |
7 | // TESTS
8 | /** @test {TorrentLibrary#addNewPath} */
9 | test('missing parameter', async (t) => {
10 | let eventSpy = sinon.spy();
11 | let libInstance = new TorrentLibrary();
12 | libInstance.on('missing_parameter', eventSpy);
13 | await t.throws(libInstance.addNewPath());
14 | t.truthy(eventSpy.called, 'Event did not fire.');
15 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
16 | t.is(
17 | libInstance.hasPathsProvidedByUser(),
18 | false, 'No paths by user should be added',
19 | );
20 | });
21 |
22 | /** @test {TorrentLibrary#addNewPath} */
23 | test('Not an existent path', async (t) => {
24 | let eventSpy = sinon.spy();
25 | let libInstance = new TorrentLibrary();
26 |
27 | libInstance.on('error_in_function', eventSpy);
28 | await t.throws(libInstance.addNewPath(path.join(__dirname, 'wrongPath')));
29 | t.truthy(eventSpy.called, 'Event did not fire.');
30 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
31 | t.is(
32 | libInstance.hasPathsProvidedByUser(),
33 | false, 'No paths by user should be added',
34 | );
35 | });
36 |
37 | /** @test {TorrentLibrary#addNewPath} */
38 | test('existent paths', async (t) => {
39 | let eventSpy = sinon.spy();
40 | let libInstance = new TorrentLibrary();
41 | libInstance.on('addNewPath', eventSpy);
42 | await t.notThrows(libInstance.addNewPath(...folders));
43 | t.truthy(eventSpy.called, 'Event did not fire.');
44 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
45 | t.is(
46 | libInstance.hasPathsProvidedByUser(),
47 | true, 'The path should be added',
48 | );
49 | });
50 |
51 |
--------------------------------------------------------------------------------
/test/methods/createFromJSON.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import TorrentLibrary from '../../index';
3 | import { folders } from '../_constants';
4 |
5 | /** @test {TorrentLibrary.createFromJSON} */
6 | test('create a perfect copy of instance', async (t) => {
7 | let libInstance = new TorrentLibrary();
8 | await t.notThrows(libInstance.addNewPath(...folders));
9 | await t.notThrows(libInstance.scan());
10 | const jsonFromLib = JSON.parse(libInstance.toJSON());
11 | const createdInstance = TorrentLibrary.createFromJSON(jsonFromLib);
12 | t.deepEqual(
13 | createdInstance.allFilesWithCategory,
14 | libInstance.allFilesWithCategory,
15 | 'allFilesWithCategory different',
16 | );
17 | t.deepEqual(
18 | createdInstance.allMovies,
19 | libInstance.allMovies, 'allMovies different',
20 | );
21 | t.deepEqual(
22 | createdInstance.allTvSeries,
23 | libInstance.allTvSeries, 'allTvSeries different',
24 | );
25 | });
26 |
27 | // dummy test for ES6 code coverage
28 | /** @test {TorrentLibrary.createFromJSON} */
29 | test('empty instance(s)', async (t) => {
30 | TorrentLibrary.createFromJSON({});
31 | t.pass();
32 | });
33 |
--------------------------------------------------------------------------------
/test/methods/filterMovies.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import * as sinon from 'sinon';
3 | import { parse as nameParser } from 'parse-torrent-title';
4 | import path from 'path';
5 | import TorrentLibrary from '../../index';
6 | import { files, folders } from '../_constants';
7 |
8 | /** @test {TorrentLibrary#filterMovies} */
9 | test('Should work without parameters', async (t) => {
10 | let eventSpy = sinon.spy();
11 | let libInstance = new TorrentLibrary();
12 | libInstance.on('scan', eventSpy);
13 | // whatever path that should exists
14 | await t.notThrows(libInstance.addNewPath(...folders));
15 | await t.notThrows(libInstance.scan());
16 | t.truthy(eventSpy.called, 'Event did not fire.');
17 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
18 | t.deepEqual(
19 | new Set([
20 | Object.assign(
21 | nameParser(path.basename(files[2])),
22 | { filePath: files[2] },
23 | ),
24 | ]),
25 | libInstance.filterMovies(),
26 | 'Not the same',
27 | );
28 | });
29 |
30 | /** @test {TorrentLibrary#filterMovies} */
31 | test('default boolean parameters search', async (t) => {
32 | let eventSpy = sinon.spy();
33 | let libInstance = new TorrentLibrary();
34 | libInstance.on('scan', eventSpy);
35 | // whatever path that should exists
36 | await t.notThrows(libInstance.addNewPath(...folders));
37 | await t.notThrows(libInstance.scan());
38 | t.truthy(eventSpy.called, 'Event did not fire.');
39 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
40 |
41 | // A simple filter that should returns the only movie that we have
42 | t.deepEqual(
43 | new Set([
44 | Object.assign(
45 | nameParser(path.basename(files[2])),
46 | { filePath: files[2] },
47 | ),
48 | ]),
49 | libInstance.filterMovies({
50 | remastered: true,
51 | }),
52 | 'Not the same',
53 | );
54 |
55 | // A complex filter that should returns nothing
56 | t.deepEqual(
57 | new Set(),
58 | libInstance.filterMovies({
59 | extended: true,
60 | unrated: true,
61 | proper: true,
62 | repack: true,
63 | convert: true,
64 | hardcoded: true,
65 | retail: true,
66 | remastered: true,
67 | additionalProperties: [
68 | { type: 'boolean', name: 'AnotherField', value: true },
69 | ],
70 | }),
71 | 'Not the same',
72 | );
73 | });
74 |
75 | /** @test {TorrentLibrary#filterMovies} */
76 | test('default number parameters search', async (t) => {
77 | let eventSpy = sinon.spy();
78 | let libInstance = new TorrentLibrary();
79 | libInstance.on('scan', eventSpy);
80 | // whatever path that should exists
81 | await t.notThrows(libInstance.addNewPath(...folders));
82 | await t.notThrows(libInstance.scan());
83 | t.truthy(eventSpy.called, 'Event did not fire.');
84 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
85 |
86 | // A simple filter that should returns the only movie that we have
87 | t.deepEqual(
88 | new Set([
89 | Object.assign(
90 | nameParser(path.basename(files[2])),
91 | { filePath: files[2] },
92 | ),
93 | ]),
94 | libInstance.filterMovies({
95 | year: 2012,
96 | }),
97 | 'Not the same',
98 | );
99 |
100 | // A complex filter that should returns nothing
101 | t.deepEqual(
102 | new Set(),
103 | libInstance.filterMovies({
104 | year: '>=2012',
105 | additionalProperties: [
106 | { type: 'number', name: "whateverFieldThatDoesn'tExist", value: '<50' },
107 | { type: 'number', name: 'AnotherField', value: undefined },
108 | { type: 'number', name: 'AnotherField2', value: '<=25' },
109 | { type: 'number', name: 'AnotherField3', value: '>25' },
110 | { type: 'number', name: 'AnotherField4', value: '==25' },
111 | ],
112 | }),
113 | 'Not the same',
114 | );
115 | });
116 |
117 | /** @test {TorrentLibrary#filterMovies} */
118 | test('default string parameters search', async (t) => {
119 | let eventSpy = sinon.spy();
120 | let libInstance = new TorrentLibrary();
121 | libInstance.on('scan', eventSpy);
122 | // whatever path that should exists
123 | await t.notThrows(libInstance.addNewPath(...folders));
124 | await t.notThrows(libInstance.scan());
125 | t.truthy(eventSpy.called, 'Event did not fire.');
126 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
127 |
128 | // A simple filter that should returns the only movie that we have
129 | t.deepEqual(
130 | new Set([
131 | Object.assign(
132 | nameParser(path.basename(files[2])),
133 | { filePath: files[2] },
134 | ),
135 | ]),
136 | libInstance.filterMovies({
137 | title: 'Bad Ass',
138 | }),
139 | 'Not the same',
140 | );
141 |
142 | // A complex filter that should returns nothing
143 | t.deepEqual(
144 | new Set(),
145 | libInstance.filterMovies({
146 | title: 'Bad Ass',
147 | additionalProperties: [
148 | {
149 | type: 'string',
150 | name: 'whateverField',
151 | value: ['NothingExists'],
152 | },
153 | {
154 | type: 'string',
155 | name: 'AnotherField',
156 | value: ['NothingExists', 'NothingExists'],
157 | },
158 | { type: 'string', name: 'AnotherField2', value: '<=25' },
159 | { type: 'string', name: 'AnotherField3', value: '>25' },
160 | ],
161 | }),
162 | 'Not the same',
163 | );
164 | });
165 |
--------------------------------------------------------------------------------
/test/methods/filterTvSeries.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import * as sinon from 'sinon';
3 | import { parse as nameParser } from 'parse-torrent-title';
4 | import path from 'path';
5 | import TorrentLibrary from '../../index';
6 | import { files, folders } from '../_constants';
7 |
8 | /** @test {TorrentLibrary#filterTvSeries} */
9 | test('Should work without parameters', async (t) => {
10 | let eventSpy = sinon.spy();
11 | let libInstance = new TorrentLibrary();
12 | libInstance.on('scan', eventSpy);
13 | // whatever path that should exists
14 | await t.notThrows(libInstance.addNewPath(...folders));
15 | await t.notThrows(libInstance.scan());
16 | t.truthy(eventSpy.called, 'Event did not fire.');
17 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
18 | t.deepEqual(
19 | new Map([
20 | [nameParser(path.basename(files[0])).title, new Set([
21 | Object.assign(
22 | nameParser(path.basename(files[0])),
23 | { filePath: files[0] },
24 | ),
25 | Object.assign(
26 | nameParser(path.basename(files[1])),
27 | { filePath: files[1] },
28 | ),
29 | ])],
30 | ]),
31 | libInstance.filterTvSeries(),
32 | 'Not the same',
33 | );
34 | });
35 |
36 | /** @test {TorrentLibrary#filterTvSeries} */
37 | test('default boolean parameters search', async (t) => {
38 | let eventSpy = sinon.spy();
39 | let libInstance = new TorrentLibrary();
40 | libInstance.on('scan', eventSpy);
41 | // whatever path that should exists
42 | await t.notThrows(libInstance.addNewPath(...folders));
43 | await t.notThrows(libInstance.scan());
44 | t.truthy(eventSpy.called, 'Event did not fire.');
45 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
46 |
47 |
48 | // A complex filter that should returns nothing
49 | t.deepEqual(
50 | new Map(),
51 | libInstance.filterTvSeries({
52 | extended: true,
53 | unrated: true,
54 | proper: true,
55 | repack: true,
56 | convert: true,
57 | hardcoded: true,
58 | retail: true,
59 | remastered: true,
60 | additionalProperties: [
61 | { type: 'boolean', name: 'AnotherField', value: true },
62 | ],
63 | }),
64 | 'Not the same',
65 | );
66 | });
67 |
68 | /** @test {TorrentLibrary#filterTvSeries} */
69 | test('default number parameters search', async (t) => {
70 | let eventSpy = sinon.spy();
71 | let libInstance = new TorrentLibrary();
72 | libInstance.on('scan', eventSpy);
73 | // whatever path that should exists
74 | await t.notThrows(libInstance.addNewPath(...folders));
75 | await t.notThrows(libInstance.scan());
76 | t.truthy(eventSpy.called, 'Event did not fire.');
77 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
78 |
79 | // A simple filter that should returns the two tv series that we have
80 | t.deepEqual(
81 | new Map([
82 | [nameParser(path.basename(files[0])).title, new Set([
83 | Object.assign(
84 | nameParser(path.basename(files[0])),
85 | { filePath: files[0] },
86 | ),
87 | Object.assign(
88 | nameParser(path.basename(files[1])),
89 | { filePath: files[1] },
90 | ),
91 | ])],
92 | ]),
93 | libInstance.filterTvSeries({
94 | season: 4,
95 | }),
96 | 'Not the same',
97 | );
98 |
99 | // A complex filter that should returns nothing
100 | t.deepEqual(
101 | new Map(),
102 | libInstance.filterTvSeries({
103 | season: '>=4',
104 | additionalProperties: [
105 | { type: 'number', name: "whateverFieldThatDoesn'tExist", value: '<50' },
106 | { type: 'number', name: 'AnotherField', value: undefined },
107 | { type: 'number', name: 'AnotherField2', value: '<=25' },
108 | { type: 'number', name: 'AnotherField3', value: '>25' },
109 | { type: 'number', name: 'AnotherField4', value: '==25' },
110 | ],
111 | }),
112 | 'Not the same',
113 | );
114 | });
115 |
116 | /** @test {TorrentLibrary#filterTvSeries} */
117 | test('default string parameters search', async (t) => {
118 | let eventSpy = sinon.spy();
119 | let libInstance = new TorrentLibrary();
120 | libInstance.on('scan', eventSpy);
121 | // whatever path that should exists
122 | await t.notThrows(libInstance.addNewPath(...folders));
123 | await t.notThrows(libInstance.scan());
124 | t.truthy(eventSpy.called, 'Event did not fire.');
125 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
126 |
127 | // A simple filter that should returns the only movie that we have
128 | t.deepEqual(
129 | new Map([
130 | [nameParser(path.basename(files[0])).title, new Set([
131 | Object.assign(
132 | nameParser(path.basename(files[0])),
133 | { filePath: files[0] },
134 | ),
135 | Object.assign(
136 | nameParser(path.basename(files[1])),
137 | { filePath: files[1] },
138 | ),
139 | ])],
140 | ]),
141 | libInstance.filterTvSeries({
142 | title: 'The Blacklist',
143 | }),
144 | 'Not the same',
145 | );
146 |
147 | // A complex filter that should returns nothing
148 | t.deepEqual(
149 | new Map(),
150 | libInstance.filterTvSeries({
151 | title: 'The Blacklist',
152 | additionalProperties: [
153 | {
154 | type: 'string',
155 | name: 'whateverField',
156 | value: ['NothingExists'],
157 | },
158 | {
159 | type: 'string',
160 | name: 'AnotherField',
161 | value: ['NothingExists', 'NothingExists'],
162 | },
163 | { type: 'string', name: 'AnotherField2', value: '<=25' },
164 | { type: 'string', name: 'AnotherField3', value: '>25' },
165 | ],
166 | }),
167 | 'Not the same',
168 | );
169 | });
170 |
--------------------------------------------------------------------------------
/test/methods/removeOldFiles.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import path from 'path';
3 | import * as sinon from 'sinon';
4 | import { parse as nameParser } from 'parse-torrent-title';
5 | import TorrentLibrary from '../../index';
6 | import { files, folders, expectedJson } from '../_constants';
7 |
8 | /** @test {TorrentLibrary#removeOldFiles} */
9 | test('Should not be able to remove not present files', async (t) => {
10 | let libInstance = new TorrentLibrary();
11 | await t.notThrows(libInstance.addNewPath(...folders));
12 | await t.notThrows(libInstance.scan());
13 | const wrongFile = path.join(
14 | __dirname, 'folder1',
15 | 'The.Blacklist.S04E22.FRENCH.WEBRip.XviD.avi',
16 | );
17 | const allFiles = libInstance.allFilesWithCategory;
18 | const expectedTvSeriesMap = libInstance.allTvSeries;
19 | await t.notThrows(libInstance.removeOldFiles(wrongFile));
20 | t.deepEqual(
21 | libInstance.allFilesWithCategory,
22 | allFiles,
23 | 'Nothing should have changed!',
24 | );
25 | t.deepEqual(
26 | libInstance.allTvSeries,
27 | expectedTvSeriesMap,
28 | 'Nothing should have changed!',
29 | );
30 | });
31 |
32 | /** @test {TorrentLibrary#removeOldFiles} */
33 | test('Should be able to remove a movie', async (t) => {
34 | let libInstance = new TorrentLibrary();
35 | let eventSpy = sinon.spy();
36 | await t.notThrows(libInstance.addNewPath(...folders));
37 | await t.notThrows(libInstance.scan());
38 | const allFilesWithoutMovie = libInstance.allFilesWithCategory;
39 | // files[2] ; Bad Ass
40 | allFilesWithoutMovie.delete(files[2]);
41 |
42 | libInstance.on('removeOldFiles', eventSpy);
43 | // files[2] ; Bad Ass
44 | await t.notThrows(libInstance.removeOldFiles(files[2]));
45 | t.deepEqual(
46 | libInstance.allMovies,
47 | new Set(),
48 | 'The movie should have been removed!',
49 | );
50 |
51 | t.deepEqual(
52 | libInstance.allFilesWithCategory,
53 | allFilesWithoutMovie,
54 | 'The movie should have been removed!',
55 | );
56 | t.truthy(eventSpy.called, 'Event did not fire.');
57 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
58 | });
59 |
60 | /** @test {TorrentLibrary#removeOldFiles} */
61 | test('Should be able to remove an tv-serie episode', async (t) => {
62 | let libInstance = new TorrentLibrary();
63 | let eventSpy = sinon.spy();
64 | await t.notThrows(libInstance.addNewPath(...folders));
65 | await t.notThrows(libInstance.scan());
66 | const allFilesWithoutIt = libInstance.allFilesWithCategory;
67 | // files[1] ; The.Blacklist.S04E21
68 | allFilesWithoutIt.delete(files[1]);
69 |
70 | libInstance.on('removeOldFiles', eventSpy);
71 | // files[1] ; The.Blacklist.S04E21
72 | await t.notThrows(libInstance.removeOldFiles(files[1]));
73 | t.deepEqual(
74 | libInstance.allTvSeries,
75 | new Map([
76 | [nameParser(path.basename(files[0])).title, new Set([
77 | Object.assign(
78 | nameParser(path.basename(files[0])),
79 | { filePath: files[0] },
80 | ),
81 | ])],
82 | ]),
83 | 'The tv-series should still exist!',
84 | );
85 |
86 | t.deepEqual(
87 | libInstance.allFilesWithCategory,
88 | allFilesWithoutIt,
89 | 'The tv-series episode should have been removed!',
90 | );
91 | t.truthy(eventSpy.called, 'Event did not fire.');
92 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
93 | });
94 |
95 | /** @test {TorrentLibrary#removeOldFiles} */
96 | test('Should be able to remove multiples files : Tv-serie', async (t) => {
97 | let libInstance = new TorrentLibrary();
98 | let eventSpy = sinon.spy();
99 | await t.notThrows(libInstance.addNewPath(...folders));
100 | await t.notThrows(libInstance.scan());
101 | const allFilesWithoutIt = libInstance.allFilesWithCategory;
102 |
103 | allFilesWithoutIt.delete(files[1]);
104 | allFilesWithoutIt.delete(files[0]);
105 |
106 | libInstance.on('removeOldFiles', eventSpy);
107 | await t.notThrows(libInstance.removeOldFiles(...files.slice(0, 2)));
108 | t.deepEqual(
109 | libInstance.allTvSeries,
110 | new Map(),
111 | 'The tv-series episodes should have all been removed!',
112 | );
113 |
114 | t.deepEqual(
115 | libInstance.allFilesWithCategory,
116 | allFilesWithoutIt,
117 | 'The tv-series episodes should have all been removed!',
118 | );
119 | t.truthy(eventSpy.called, 'Event did not fire.');
120 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
121 | });
122 |
123 | // test to handle default parameters
124 | /** @test {TorrentLibrary#removeOldFiles} */
125 | test('Should not be able to remove files : wrong custom parser', async (t) => {
126 | let libInstance = TorrentLibrary.createFromJSON(expectedJson, {});
127 | let eventSpy = sinon.spy();
128 | libInstance.on('error_in_function', eventSpy);
129 | await t.throws(libInstance.removeOldFiles(...files));
130 | t.truthy(eventSpy.called, 'Event did not fire.');
131 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
132 | });
133 |
--------------------------------------------------------------------------------
/test/methods/scan.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import * as sinon from 'sinon';
3 | import { parse as nameParser } from 'parse-torrent-title';
4 | import TorrentLibrary from '../../index';
5 | import { folders } from '../_constants';
6 |
7 | // TESTS
8 | /** @test {TorrentLibrary#scan} */
9 | test('Scan without user provided paths', async (t) => {
10 | let eventSpy = sinon.spy();
11 | let libInstance = new TorrentLibrary();
12 | libInstance.on('scan', eventSpy);
13 | await t.notThrows(libInstance.scan());
14 | t.truthy(eventSpy.called, 'Event did not fire.');
15 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
16 | });
17 |
18 | /** @test {TorrentLibrary#scan} */
19 | test('Scan with user provided paths', async (t) => {
20 | let eventSpy = sinon.spy();
21 | let libInstance = new TorrentLibrary();
22 | libInstance.on('scan', eventSpy);
23 | // whatever path that should exists
24 | await t.notThrows(libInstance.addNewPath(...folders));
25 | await t.notThrows(libInstance.scan());
26 | t.truthy(eventSpy.called, 'Event did not fire.');
27 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
28 | });
29 |
30 | // test to handle default parameters
31 | /** @test {TorrentLibrary#scan} */
32 | test('Scan with user provided paths and custom parser', async (t) => {
33 | let eventSpy = sinon.spy();
34 | let libInstance = new TorrentLibrary({}, nameParser);
35 | libInstance.on('scan', eventSpy);
36 | // whatever path that should exists
37 | await t.notThrows(libInstance.addNewPath(...folders));
38 | await t.notThrows(libInstance.scan());
39 | t.truthy(eventSpy.called, 'Event did not fire.');
40 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
41 | });
42 |
43 | /** @test {TorrentLibrary#scan} */
44 | test('Scan with user provided paths and wrong custom parser', async (t) => {
45 | let eventSpy = sinon.spy();
46 | let libInstance = new TorrentLibrary({}, {});
47 | libInstance.on('error_in_function', eventSpy);
48 | // whatever path that should exists
49 | await t.notThrows(libInstance.addNewPath(...folders));
50 | await t.throws(libInstance.scan());
51 | t.truthy(eventSpy.called, 'Event did not fire.');
52 | t.truthy(eventSpy.calledOnce, 'Event fired more than once');
53 | });
54 |
--------------------------------------------------------------------------------
/test/methods/toJSON.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import TorrentLibrary from '../../index';
3 | import { expectedJson, folders } from '../_constants';
4 |
5 | /** @test {TorrentLibrary#toJSON} */
6 | test('return a valid stringified JSON', async (t) => {
7 | const expectedJsonString = JSON.stringify(expectedJson);
8 | let libInstance = new TorrentLibrary();
9 | await t.notThrows(libInstance.addNewPath(...folders));
10 | await t.notThrows(libInstance.scan());
11 | const dataFromInstance = libInstance.toJSON();
12 | t.deepEqual(
13 | JSON.stringify(JSON.parse(dataFromInstance)),
14 | expectedJsonString,
15 | 'Not the same JSON',
16 | );
17 | });
18 |
--------------------------------------------------------------------------------