feat: remove web-crawler and use electron (chromium) itself as crawler, implementing a function to download nhentai favorite torrents
This commit is contained in:
parent
f54edba6fc
commit
1618ac552b
@ -34,6 +34,7 @@
|
||||
}
|
||||
],
|
||||
"no-constant-condition": ["error", { "checkLoops": false }],
|
||||
"no-throw-literal": "error",
|
||||
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
|
@ -9,6 +9,9 @@ const electronMock: DeepPartial<typeof Electron> = {
|
||||
return path.resolve('test-paths', name);
|
||||
},
|
||||
quit(): void {},
|
||||
getAppPath(): string {
|
||||
return path.resolve(__dirname, '..');
|
||||
},
|
||||
},
|
||||
BrowserWindow: class {
|
||||
public webContents: DeepPartial<WebContents> = {
|
||||
|
414
package-lock.json
generated
414
package-lock.json
generated
@ -894,17 +894,6 @@
|
||||
"integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/jsdom": {
|
||||
"version": "16.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-16.2.3.tgz",
|
||||
"integrity": "sha512-BREatezSn74rmLIDksuqGNFUTi9HNAWWQXYpFBFLK9U6wlMCO4M0QCa8CMpDsZQuqxSO9XifVLT5Q1P0vgKLqw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"@types/parse5": "*",
|
||||
"@types/tough-cookie": "*"
|
||||
}
|
||||
},
|
||||
"@types/json-schema": {
|
||||
"version": "7.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz",
|
||||
@ -980,12 +969,6 @@
|
||||
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/parse5": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-5.0.3.tgz",
|
||||
"integrity": "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/puppeteer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-3.0.1.tgz",
|
||||
@ -1031,12 +1014,6 @@
|
||||
"integrity": "sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/tough-cookie": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz",
|
||||
"integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/uglify-js": {
|
||||
"version": "3.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.9.3.tgz",
|
||||
@ -1515,41 +1492,17 @@
|
||||
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
|
||||
"dev": true
|
||||
},
|
||||
"abab": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz",
|
||||
"integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg=="
|
||||
},
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
|
||||
},
|
||||
"acorn": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz",
|
||||
"integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ=="
|
||||
},
|
||||
"acorn-globals": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz",
|
||||
"integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==",
|
||||
"requires": {
|
||||
"acorn": "^7.1.1",
|
||||
"acorn-walk": "^7.1.1"
|
||||
}
|
||||
},
|
||||
"acorn-jsx": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz",
|
||||
"integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn-walk": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
|
||||
"integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA=="
|
||||
},
|
||||
"agent-base": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz",
|
||||
@ -2191,11 +2144,6 @@
|
||||
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
|
||||
"dev": true
|
||||
},
|
||||
"browser-process-hrtime": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz",
|
||||
"integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow=="
|
||||
},
|
||||
"browser-stdout": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
|
||||
@ -3294,26 +3242,6 @@
|
||||
"integrity": "sha1-Xv1sLupeof1rasV+wEJ7GEUkJOo=",
|
||||
"dev": true
|
||||
},
|
||||
"cssom": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
|
||||
"integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw=="
|
||||
},
|
||||
"cssstyle": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
|
||||
"integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
|
||||
"requires": {
|
||||
"cssom": "~0.3.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"cssom": {
|
||||
"version": "0.3.8",
|
||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
|
||||
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"cuint": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz",
|
||||
@ -3344,16 +3272,6 @@
|
||||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"data-urls": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz",
|
||||
"integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==",
|
||||
"requires": {
|
||||
"abab": "^2.0.3",
|
||||
"whatwg-mimetype": "^2.3.0",
|
||||
"whatwg-url": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"date-fns": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.12.0.tgz",
|
||||
@ -3379,11 +3297,6 @@
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
|
||||
},
|
||||
"decimal.js": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.0.tgz",
|
||||
"integrity": "sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw=="
|
||||
},
|
||||
"decode-uri-component": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||
@ -3416,7 +3329,8 @@
|
||||
"deep-is": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
|
||||
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
|
||||
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
|
||||
"dev": true
|
||||
},
|
||||
"deepmerge": {
|
||||
"version": "4.2.2",
|
||||
@ -3612,21 +3526,6 @@
|
||||
"integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
|
||||
"dev": true
|
||||
},
|
||||
"domexception": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz",
|
||||
"integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==",
|
||||
"requires": {
|
||||
"webidl-conversions": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"webidl-conversions": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz",
|
||||
"integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"dotenv": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz",
|
||||
@ -4236,18 +4135,6 @@
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||
},
|
||||
"escodegen": {
|
||||
"version": "1.14.3",
|
||||
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz",
|
||||
"integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==",
|
||||
"requires": {
|
||||
"esprima": "^4.0.1",
|
||||
"estraverse": "^4.2.0",
|
||||
"esutils": "^2.0.2",
|
||||
"optionator": "^0.8.1",
|
||||
"source-map": "~0.6.1"
|
||||
}
|
||||
},
|
||||
"eslint": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.6.0.tgz",
|
||||
@ -4661,11 +4548,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
|
||||
},
|
||||
"esquery": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz",
|
||||
@ -4695,12 +4577,14 @@
|
||||
"estraverse": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
|
||||
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="
|
||||
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
|
||||
"dev": true
|
||||
},
|
||||
"esutils": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
|
||||
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
|
||||
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
|
||||
"dev": true
|
||||
},
|
||||
"events": {
|
||||
"version": "3.0.0",
|
||||
@ -4975,7 +4859,8 @@
|
||||
"fast-levenshtein": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
|
||||
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
|
||||
"dev": true
|
||||
},
|
||||
"fd-slicer": {
|
||||
"version": "1.1.0",
|
||||
@ -5802,14 +5687,6 @@
|
||||
"integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==",
|
||||
"dev": true
|
||||
},
|
||||
"html-encoding-sniffer": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz",
|
||||
"integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==",
|
||||
"requires": {
|
||||
"whatwg-encoding": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
@ -6223,11 +6100,6 @@
|
||||
"resolved": "https://registry.npmjs.org/inversify/-/inversify-5.0.1.tgz",
|
||||
"integrity": "sha512-Ieh06s48WnEYGcqHepdsJUIJUXpwH5o5vodAX+DK2JA/gjy4EbEcQZxw+uFfzysmKjiLXGYwNG3qDZsKVMcINQ=="
|
||||
},
|
||||
"ip-regex": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz",
|
||||
"integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk="
|
||||
},
|
||||
"is-accessor-descriptor": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
|
||||
@ -6417,11 +6289,6 @@
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"is-potential-custom-element-name": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz",
|
||||
"integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c="
|
||||
},
|
||||
"is-promise": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
|
||||
@ -6690,84 +6557,6 @@
|
||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
|
||||
},
|
||||
"jsdom": {
|
||||
"version": "16.3.0",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.3.0.tgz",
|
||||
"integrity": "sha512-zggeX5UuEknpdZzv15+MS1dPYG0J/TftiiNunOeNxSl3qr8Z6cIlQpN0IdJa44z9aFxZRIVqRncvEhQ7X5DtZg==",
|
||||
"requires": {
|
||||
"abab": "^2.0.3",
|
||||
"acorn": "^7.1.1",
|
||||
"acorn-globals": "^6.0.0",
|
||||
"cssom": "^0.4.4",
|
||||
"cssstyle": "^2.2.0",
|
||||
"data-urls": "^2.0.0",
|
||||
"decimal.js": "^10.2.0",
|
||||
"domexception": "^2.0.1",
|
||||
"escodegen": "^1.14.1",
|
||||
"html-encoding-sniffer": "^2.0.1",
|
||||
"is-potential-custom-element-name": "^1.0.0",
|
||||
"nwsapi": "^2.2.0",
|
||||
"parse5": "5.1.1",
|
||||
"request": "^2.88.2",
|
||||
"request-promise-native": "^1.0.8",
|
||||
"saxes": "^5.0.0",
|
||||
"symbol-tree": "^3.2.4",
|
||||
"tough-cookie": "^3.0.1",
|
||||
"w3c-hr-time": "^1.0.2",
|
||||
"w3c-xmlserializer": "^2.0.0",
|
||||
"webidl-conversions": "^6.1.0",
|
||||
"whatwg-encoding": "^1.0.5",
|
||||
"whatwg-mimetype": "^2.3.0",
|
||||
"whatwg-url": "^8.0.0",
|
||||
"ws": "^7.2.3",
|
||||
"xml-name-validator": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"request": {
|
||||
"version": "2.88.2",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
|
||||
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
|
||||
"requires": {
|
||||
"aws-sign2": "~0.7.0",
|
||||
"aws4": "^1.8.0",
|
||||
"caseless": "~0.12.0",
|
||||
"combined-stream": "~1.0.6",
|
||||
"extend": "~3.0.2",
|
||||
"forever-agent": "~0.6.1",
|
||||
"form-data": "~2.3.2",
|
||||
"har-validator": "~5.1.3",
|
||||
"http-signature": "~1.2.0",
|
||||
"is-typedarray": "~1.0.0",
|
||||
"isstream": "~0.1.2",
|
||||
"json-stringify-safe": "~5.0.1",
|
||||
"mime-types": "~2.1.19",
|
||||
"oauth-sign": "~0.9.0",
|
||||
"performance-now": "^2.1.0",
|
||||
"qs": "~6.5.2",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"tough-cookie": "~2.5.0",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"tough-cookie": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
|
||||
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
|
||||
"requires": {
|
||||
"psl": "^1.1.28",
|
||||
"punycode": "^2.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"jsesc": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||
@ -6880,15 +6669,6 @@
|
||||
"readable-stream": "^2.0.5"
|
||||
}
|
||||
},
|
||||
"levn": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
|
||||
"integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
|
||||
"requires": {
|
||||
"prelude-ls": "~1.1.2",
|
||||
"type-check": "~0.3.2"
|
||||
}
|
||||
},
|
||||
"lighthouse-logger": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.2.0.tgz",
|
||||
@ -7044,11 +6824,6 @@
|
||||
"integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.sortby": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
|
||||
"integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg="
|
||||
},
|
||||
"lodash.template": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
|
||||
@ -8148,11 +7923,6 @@
|
||||
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
|
||||
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
|
||||
},
|
||||
"nwsapi": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz",
|
||||
"integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ=="
|
||||
},
|
||||
"nyc": {
|
||||
"version": "15.1.0",
|
||||
"resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz",
|
||||
@ -8469,19 +8239,6 @@
|
||||
"integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==",
|
||||
"dev": true
|
||||
},
|
||||
"optionator": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
|
||||
"integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
|
||||
"requires": {
|
||||
"deep-is": "~0.1.3",
|
||||
"fast-levenshtein": "~2.0.6",
|
||||
"levn": "~0.3.0",
|
||||
"prelude-ls": "~1.1.2",
|
||||
"type-check": "~0.3.2",
|
||||
"word-wrap": "~1.2.3"
|
||||
}
|
||||
},
|
||||
"ora": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/ora/-/ora-4.0.5.tgz",
|
||||
@ -8998,11 +8755,6 @@
|
||||
"integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
|
||||
"dev": true
|
||||
},
|
||||
"prelude-ls": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
|
||||
},
|
||||
"prepend-http": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
|
||||
@ -9526,42 +9278,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"request-promise-core": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz",
|
||||
"integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==",
|
||||
"requires": {
|
||||
"lodash": "^4.17.19"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": {
|
||||
"version": "4.17.19",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
||||
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"request-promise-native": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz",
|
||||
"integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==",
|
||||
"requires": {
|
||||
"request-promise-core": "1.1.4",
|
||||
"stealthy-require": "^1.1.1",
|
||||
"tough-cookie": "^2.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tough-cookie": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
|
||||
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
|
||||
"requires": {
|
||||
"psl": "^1.1.28",
|
||||
"punycode": "^2.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
@ -9793,14 +9509,6 @@
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
||||
},
|
||||
"saxes": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
|
||||
"integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==",
|
||||
"requires": {
|
||||
"xmlchars": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
|
||||
@ -10133,7 +9841,8 @@
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map-resolve": {
|
||||
"version": "0.5.2",
|
||||
@ -10410,11 +10119,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"stealthy-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
|
||||
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
|
||||
},
|
||||
"stream-browserify": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
|
||||
@ -10628,11 +10332,6 @@
|
||||
"svelte-dev-helper": "^1.1.9"
|
||||
}
|
||||
},
|
||||
"symbol-tree": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="
|
||||
},
|
||||
"table": {
|
||||
"version": "5.4.6",
|
||||
"resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
|
||||
@ -11039,24 +10738,6 @@
|
||||
"repeat-string": "^1.6.1"
|
||||
}
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz",
|
||||
"integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==",
|
||||
"requires": {
|
||||
"ip-regex": "^2.1.0",
|
||||
"psl": "^1.1.28",
|
||||
"punycode": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"tr46": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz",
|
||||
"integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==",
|
||||
"requires": {
|
||||
"punycode": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"tree-kill": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
||||
@ -11203,14 +10884,6 @@
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
|
||||
},
|
||||
"type-check": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
|
||||
"integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
|
||||
"requires": {
|
||||
"prelude-ls": "~1.1.2"
|
||||
}
|
||||
},
|
||||
"type-detect": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||
@ -11522,22 +11195,6 @@
|
||||
"integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==",
|
||||
"dev": true
|
||||
},
|
||||
"w3c-hr-time": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
|
||||
"integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==",
|
||||
"requires": {
|
||||
"browser-process-hrtime": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"w3c-xmlserializer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz",
|
||||
"integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==",
|
||||
"requires": {
|
||||
"xml-name-validator": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"watchpack": {
|
||||
"version": "1.7.4",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz",
|
||||
@ -11834,11 +11491,6 @@
|
||||
"webdriver": "6.3.5"
|
||||
}
|
||||
},
|
||||
"webidl-conversions": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
|
||||
"integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w=="
|
||||
},
|
||||
"webpack": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.1.tgz",
|
||||
@ -12070,36 +11722,6 @@
|
||||
"source-map": "~0.6.1"
|
||||
}
|
||||
},
|
||||
"whatwg-encoding": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz",
|
||||
"integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==",
|
||||
"requires": {
|
||||
"iconv-lite": "0.4.24"
|
||||
}
|
||||
},
|
||||
"whatwg-mimetype": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz",
|
||||
"integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g=="
|
||||
},
|
||||
"whatwg-url": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.1.0.tgz",
|
||||
"integrity": "sha512-vEIkwNi9Hqt4TV9RdnaBPNt+E2Sgmo3gePebCRgZ1R7g6d23+53zCTnuB0amKI4AXq6VM8jj2DUAa0S1vjJxkw==",
|
||||
"requires": {
|
||||
"lodash.sortby": "^4.7.0",
|
||||
"tr46": "^2.0.2",
|
||||
"webidl-conversions": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"webidl-conversions": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz",
|
||||
"integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"which": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||
@ -12151,7 +11773,8 @@
|
||||
"word-wrap": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ=="
|
||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
|
||||
"dev": true
|
||||
},
|
||||
"wordwrap": {
|
||||
"version": "1.0.0",
|
||||
@ -12243,12 +11866,8 @@
|
||||
"ws": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz",
|
||||
"integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA=="
|
||||
},
|
||||
"xml-name-validator": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
|
||||
"integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw=="
|
||||
"integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==",
|
||||
"dev": true
|
||||
},
|
||||
"xml2js": {
|
||||
"version": "0.4.23",
|
||||
@ -12272,11 +11891,6 @@
|
||||
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=",
|
||||
"dev": true
|
||||
},
|
||||
"xmlchars": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
|
||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
|
||||
},
|
||||
"xmldom": {
|
||||
"version": "0.1.31",
|
||||
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz",
|
||||
|
@ -48,7 +48,6 @@
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
"inversify": "^5.0.1",
|
||||
"jsdom": "^16.2.2",
|
||||
"minimist": "^1.2.5",
|
||||
"node-fetch": "^2.6.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
@ -62,7 +61,6 @@
|
||||
"@types/chai": "^4.2.12",
|
||||
"@types/chai-fs": "^2.0.2",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/jsdom": "^16.2.3",
|
||||
"@types/minimist": "^1.2.0",
|
||||
"@types/mocha": "^8.0.1",
|
||||
"@types/node": "^12.12.54",
|
||||
|
24
src/main.ts
24
src/main.ts
@ -6,7 +6,6 @@ import { app } from 'electron';
|
||||
import { isDev } from './main/core/env';
|
||||
import { IAppWindow } from './main/modules/app-window/i-app-window';
|
||||
import { ILogger } from './main/modules/logger/i-logger';
|
||||
import { ISession } from './main/modules/session/i-session';
|
||||
|
||||
/**
|
||||
* have a read: https://github.com/nodejs/node/issues/20392, over 100 comments as of 2020-07-26
|
||||
@ -21,22 +20,19 @@ process.on('unhandledRejection', (reason) => {
|
||||
process.on('uncaughtException', (error) => {
|
||||
const logger: ILogger = container.get('logger');
|
||||
void logger.exception(error);
|
||||
if (isDev()) {
|
||||
// eslint-disable-next-line no-console -- only for development purposes
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
async function createWindow(): Promise<void> {
|
||||
const session: ISession = container.get('session');
|
||||
session.setHeaders();
|
||||
|
||||
const appWindowMain: IAppWindow = container.get('app-window-main');
|
||||
|
||||
// and load the index.html of the app.
|
||||
await appWindowMain.open();
|
||||
|
||||
// Open the DevTools.
|
||||
if (isDev()) {
|
||||
// eslint-disable-next-line no-unused-expressions -- eslint can't handle optional chaining, yet
|
||||
appWindowMain.window?.webContents.openDevTools();
|
||||
}
|
||||
appWindowMain.window?.on('closed', () => {
|
||||
app.quit();
|
||||
});
|
||||
}
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
@ -46,11 +42,7 @@ app.on('ready', createWindow);
|
||||
|
||||
// Quit when all windows are closed.
|
||||
app.on('window-all-closed', () => {
|
||||
// On OS X it is common for applications and their menu bar
|
||||
// to stay active until the user quits explicitly with Cmd + Q
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
app.quit();
|
||||
});
|
||||
|
||||
app.on('activate', async () => {
|
||||
|
@ -1,16 +1,17 @@
|
||||
import 'reflect-metadata';
|
||||
import { Container, interfaces } from 'inversify';
|
||||
import { MainAppWindow } from '../modules/app-window/main-app-window';
|
||||
import { I18nTranslator } from '../modules/i18n/i18n-translator';
|
||||
import { Logger } from '../modules/logger/logger';
|
||||
import { NhentaiApi } from '../modules/nhentai/nhentai-api';
|
||||
import '../modules/nhentai/nhentai-ipc-controller';
|
||||
import { Session } from '../modules/session/session';
|
||||
import { NhentaiAppWindow } from '../modules/nhentai/nhentai-app-window';
|
||||
import { SessionHelper } from '../modules/session/session-helper';
|
||||
import { Store } from '../modules/store/store';
|
||||
import { WebCrawler } from '../modules/web-crawler/web-crawler';
|
||||
import BindingToSyntax = interfaces.BindingToSyntax;
|
||||
|
||||
export const container = {
|
||||
original: new Container({ defaultScope: 'Singleton' }),
|
||||
original: new Container({ defaultScope: 'Singleton', skipBaseClassChecks: true }),
|
||||
bind<T>(key: string): BindingToSyntax<T> {
|
||||
return this.original.bind<T>(Symbol.for(key));
|
||||
},
|
||||
@ -24,12 +25,13 @@ export const container = {
|
||||
|
||||
container.bind('logger').to(Logger);
|
||||
|
||||
container.bind('i18n-translator').to(I18nTranslator);
|
||||
|
||||
container.bind('store').to(Store);
|
||||
|
||||
container.bind('web-crawler').to(WebCrawler);
|
||||
container.bind('session-helper').to(SessionHelper);
|
||||
|
||||
container.bind('nhentai-api').to(NhentaiApi);
|
||||
container.bind('nhentai-app-window').to(NhentaiAppWindow);
|
||||
|
||||
container.bind('app-window-main').to(MainAppWindow);
|
||||
|
||||
container.bind('session').to(Session);
|
||||
|
@ -1,14 +1,19 @@
|
||||
import { BrowserWindow } from 'electron';
|
||||
import { app, BrowserWindow, Event, LoadFileOptions, LoadURLOptions, NewWindowEvent } from 'electron';
|
||||
import os from 'os';
|
||||
import { injectable } from 'inversify';
|
||||
import path from 'path';
|
||||
import { isDev } from '../../core/env';
|
||||
import { ISessionHelper } from '../session/i-session-helper';
|
||||
import { IAppWindow } from './i-app-window';
|
||||
import BrowserWindowConstructorOptions = Electron.BrowserWindowConstructorOptions;
|
||||
|
||||
let defaultOptions = {
|
||||
let defaultOptions: BrowserWindowConstructorOptions = {
|
||||
width: 1600,
|
||||
height: 900,
|
||||
webPreferences: {
|
||||
enableRemoteModule: false,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
devTools: isDev(),
|
||||
},
|
||||
};
|
||||
|
||||
@ -25,12 +30,23 @@ switch (os.platform()) {
|
||||
break;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export abstract class AppWindow implements IAppWindow {
|
||||
protected static default = {};
|
||||
|
||||
protected _window: BrowserWindow | null = null;
|
||||
|
||||
protected constructor(options: BrowserWindowConstructorOptions = {}) {
|
||||
this.initialize(options);
|
||||
protected readonly sessionHelper: ISessionHelper;
|
||||
|
||||
protected options: BrowserWindowConstructorOptions;
|
||||
|
||||
protected uri: string;
|
||||
|
||||
protected abstract loadOptions: LoadFileOptions | LoadURLOptions;
|
||||
|
||||
protected constructor(sessionHelper: ISessionHelper, uri: string, options: BrowserWindowConstructorOptions = {}) {
|
||||
this.sessionHelper = sessionHelper;
|
||||
this.options = { ...defaultOptions, ...options };
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public get window(): BrowserWindow | null {
|
||||
@ -38,14 +54,29 @@ export abstract class AppWindow implements IAppWindow {
|
||||
}
|
||||
|
||||
public open(): Promise<void> {
|
||||
if (this.isClosed()) {
|
||||
this.initialize();
|
||||
this._window = new BrowserWindow(this.options);
|
||||
|
||||
this.sessionHelper.setCsp(this._window, this.getCsp());
|
||||
|
||||
this._window.on('closed', () => {
|
||||
this._window = null;
|
||||
});
|
||||
|
||||
if (isDev()) {
|
||||
this._window.webContents.openDevTools();
|
||||
}
|
||||
|
||||
if (this._window) {
|
||||
return this._window.loadFile('frontend/index.html');
|
||||
this._window.webContents.on('will-navigate', this.onWillNavigate);
|
||||
this._window.webContents.on('new-window', this.onNewWindow);
|
||||
|
||||
return this.load(this._window);
|
||||
}
|
||||
|
||||
public close(force: boolean = false): void {
|
||||
if (force) {
|
||||
this._window?.destroy();
|
||||
} else {
|
||||
return Promise.reject(new Error('the window was not initialized'));
|
||||
this._window?.close();
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,11 +84,18 @@ export abstract class AppWindow implements IAppWindow {
|
||||
return !this._window;
|
||||
}
|
||||
|
||||
private initialize(options: BrowserWindowConstructorOptions = {}): void {
|
||||
this._window = new BrowserWindow({ ...defaultOptions, ...options });
|
||||
|
||||
this._window.on('closed', () => {
|
||||
this._window = null;
|
||||
});
|
||||
protected getCsp(): IContentSecurityPolicy {
|
||||
return {};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- it is used in child classes
|
||||
protected onWillNavigate(event: Event, navigationUrl: string): void {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
protected onNewWindow(event: NewWindowEvent): void {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
protected abstract load(window: BrowserWindow): Promise<void>;
|
||||
}
|
||||
|
21
src/main/modules/app-window/file-app-window.ts
Normal file
21
src/main/modules/app-window/file-app-window.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { BrowserWindow, BrowserWindowConstructorOptions, LoadFileOptions } from 'electron';
|
||||
import { ISessionHelper } from '../session/i-session-helper';
|
||||
import { AppWindow } from './app-window';
|
||||
|
||||
export abstract class FileAppWindow extends AppWindow {
|
||||
protected loadOptions: LoadFileOptions;
|
||||
|
||||
protected constructor(
|
||||
sessionHelper: ISessionHelper,
|
||||
uri: string,
|
||||
options: BrowserWindowConstructorOptions = {},
|
||||
loadOptions: LoadFileOptions = {}
|
||||
) {
|
||||
super(sessionHelper, uri, options);
|
||||
this.loadOptions = loadOptions;
|
||||
}
|
||||
|
||||
protected load(window: BrowserWindow): Promise<void> {
|
||||
return window.loadFile(this.uri, this.loadOptions);
|
||||
}
|
||||
}
|
@ -3,5 +3,6 @@ import BrowserWindow = Electron.BrowserWindow;
|
||||
export interface IAppWindow {
|
||||
window: BrowserWindow | null;
|
||||
open(): Promise<void>;
|
||||
close(force?: boolean): void;
|
||||
isClosed(): boolean;
|
||||
}
|
||||
|
6
src/main/modules/app-window/i-url-app-window.ts
Normal file
6
src/main/modules/app-window/i-url-app-window.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { LoadURLOptions } from 'electron';
|
||||
import { IAppWindow } from './i-app-window';
|
||||
|
||||
export interface IUrlAppWindow extends IAppWindow {
|
||||
loadUrlSafe(url: string, options?: LoadURLOptions): Promise<void>;
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { AppWindow } from './app-window';
|
||||
import { inject } from '../../core/inject';
|
||||
import { ISessionHelper } from '../session/i-session-helper';
|
||||
import { FileAppWindow } from './file-app-window';
|
||||
|
||||
@injectable()
|
||||
export class MainAppWindow extends AppWindow {
|
||||
public constructor() {
|
||||
super({
|
||||
export class MainAppWindow extends FileAppWindow {
|
||||
public constructor(@inject('session-helper') sessionHelper: ISessionHelper) {
|
||||
super(sessionHelper, 'frontend/index.html', {
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
},
|
||||
|
57
src/main/modules/app-window/url-app-window.ts
Normal file
57
src/main/modules/app-window/url-app-window.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { BrowserWindow, BrowserWindowConstructorOptions, LoadURLOptions } from 'electron';
|
||||
import { promisify } from 'util';
|
||||
import { ISessionHelper } from '../session/i-session-helper';
|
||||
import { AppWindow } from './app-window';
|
||||
import { IUrlAppWindow } from './i-url-app-window';
|
||||
import { WindowClosedError } from './window-closed-error';
|
||||
|
||||
export abstract class UrlAppWindow extends AppWindow implements IUrlAppWindow {
|
||||
protected loadOptions: LoadURLOptions;
|
||||
|
||||
protected constructor(
|
||||
sessionHelper: ISessionHelper,
|
||||
uri: string,
|
||||
options: BrowserWindowConstructorOptions = {},
|
||||
loadOptions: LoadURLOptions = {}
|
||||
) {
|
||||
const securedOptions: BrowserWindowConstructorOptions = {
|
||||
...options,
|
||||
...{
|
||||
webPreferences: {
|
||||
enableRemoteModule: false,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
super(sessionHelper, uri, securedOptions);
|
||||
this.loadOptions = loadOptions;
|
||||
}
|
||||
|
||||
public async loadUrlSafe(url: string, options?: LoadURLOptions): Promise<void> {
|
||||
if (!this._window) {
|
||||
throw new WindowClosedError();
|
||||
}
|
||||
const waitInterval = 1000;
|
||||
let failedLoad = true;
|
||||
do {
|
||||
await new Promise((resolve) => {
|
||||
if (!this._window) {
|
||||
throw new WindowClosedError();
|
||||
}
|
||||
this._window.webContents.once('did-navigate', (event, navigationUrl, httpResponseCode) => {
|
||||
failedLoad = HttpCode.BAD_REQUEST <= httpResponseCode;
|
||||
resolve();
|
||||
});
|
||||
void this._window.loadURL(url, options);
|
||||
});
|
||||
if (failedLoad) {
|
||||
await promisify(setTimeout)(waitInterval);
|
||||
}
|
||||
} while (failedLoad);
|
||||
}
|
||||
|
||||
protected load(window: BrowserWindow): Promise<void> {
|
||||
return window.loadURL(this.uri, this.loadOptions);
|
||||
}
|
||||
}
|
5
src/main/modules/app-window/window-closed-error.ts
Normal file
5
src/main/modules/app-window/window-closed-error.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export class WindowClosedError extends TypeError {
|
||||
public constructor(message = 'window is closed') {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
/**
|
||||
* Error thrown when cookies could not be saved.
|
||||
*/
|
||||
export class CookieSaveError extends Error {
|
||||
public constructor(message: string = 'failed to save cookies') {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
/**
|
||||
* Error thrown when there is an error while initializing services or the app in general.
|
||||
*
|
||||
* You're pretty much fucked at this point, try to avoid unstable code on initialization
|
||||
*/
|
||||
export class InitializationError extends Error {
|
||||
public constructor(message: string = 'initialization failed') {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
/**
|
||||
* generic web crawler error
|
||||
*/
|
||||
export class WebCrawlerError extends Error {
|
||||
public constructor(message: string = 'web crawler failed') {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import { WebCrawlerError } from './web-crawler-error';
|
||||
|
||||
/**
|
||||
* Error thrown when the web crawler can't work with the provided form.
|
||||
*/
|
||||
export class WebCrawlerFormError extends WebCrawlerError {
|
||||
public constructor(message: string = 'web crawler failed') {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import { WebCrawlerError } from './web-crawler-error';
|
||||
|
||||
/**
|
||||
* Error thrown when the web crawler can't login
|
||||
*/
|
||||
export class WebCrawlerLoginError extends WebCrawlerError {
|
||||
public constructor(message: string = 'login failed') {
|
||||
super(message);
|
||||
}
|
||||
}
|
3
src/main/modules/i18n/i-i18n-translator.ts
Normal file
3
src/main/modules/i18n/i-i18n-translator.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export interface II18nTranslator {
|
||||
t(text: string): string;
|
||||
}
|
9
src/main/modules/i18n/i18n-translator.ts
Normal file
9
src/main/modules/i18n/i18n-translator.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { II18nTranslator } from './i-i18n-translator';
|
||||
|
||||
@injectable()
|
||||
export class I18nTranslator implements II18nTranslator {
|
||||
public t(text: string): string {
|
||||
return text;
|
||||
}
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
export interface INhentaiApi {
|
||||
isLoggedIn(): Promise<boolean>;
|
||||
login(name: string, password: string): Promise<void>;
|
||||
getFavorites(): Promise<NodeJS.ReadableStream>;
|
||||
}
|
||||
|
5
src/main/modules/nhentai/i-nhentai-app-window.ts
Normal file
5
src/main/modules/nhentai/i-nhentai-app-window.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { IUrlAppWindow } from '../app-window/i-url-app-window';
|
||||
|
||||
export interface INhentaiAppWindow extends IUrlAppWindow {
|
||||
getFavorites(): Promise<NodeJS.ReadableStream>;
|
||||
}
|
@ -1,129 +1,17 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { RequestInit, Response } from 'node-fetch';
|
||||
import { inject } from '../../core/inject';
|
||||
import { WebCrawlerFormError } from '../error/web-crawler-form-error';
|
||||
import { WebCrawlerLoginError } from '../error/web-crawler-login-error';
|
||||
import { IWebCrawler } from '../web-crawler/i-web-crawler';
|
||||
import { INhentaiApi } from './i-nhentai-api';
|
||||
|
||||
const domain = 'nhentai.net';
|
||||
const url = `https://${domain}/`;
|
||||
|
||||
const paths = {
|
||||
books: 'g/',
|
||||
login: 'login/',
|
||||
favorites: 'favorites/',
|
||||
};
|
||||
|
||||
const usernameInput = 'username_or_email';
|
||||
const passwordInput = 'password';
|
||||
|
||||
interface ILoginMeta {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
interface ILoginAuth {
|
||||
[usernameInput]: string;
|
||||
[passwordInput]: string;
|
||||
}
|
||||
|
||||
interface ILoginParams extends ILoginMeta, ILoginAuth {}
|
||||
import { INhentaiAppWindow } from './i-nhentai-app-window';
|
||||
|
||||
@injectable()
|
||||
export class NhentaiApi implements INhentaiApi {
|
||||
private webCrawler: IWebCrawler;
|
||||
private readonly appWindow: INhentaiAppWindow;
|
||||
|
||||
public constructor(@inject('web-crawler') webCrawler: IWebCrawler) {
|
||||
this.webCrawler = webCrawler;
|
||||
public constructor(@inject('nhentai-app-window') appWindow: INhentaiAppWindow) {
|
||||
this.appWindow = appWindow;
|
||||
}
|
||||
|
||||
public isLoggedIn(): Promise<boolean> {
|
||||
return this.webCrawler
|
||||
.fetch(`${url}${paths.favorites}`, { redirect: 'manual' })
|
||||
.then((res: Response) => res.status === HttpCode.OK);
|
||||
}
|
||||
|
||||
public login(name: string, password: string): Promise<void> {
|
||||
return this.getLoginMeta()
|
||||
.then((meta: ILoginMeta) => {
|
||||
const loginParams: ILoginParams = {
|
||||
...meta,
|
||||
...{
|
||||
[usernameInput]: name,
|
||||
[passwordInput]: password,
|
||||
},
|
||||
};
|
||||
|
||||
return this.postNHentai(paths.login, {
|
||||
body: encodeURI(
|
||||
Object.keys(loginParams)
|
||||
.map((key: keyof ILoginParams) => `${key}=${loginParams[key]}`)
|
||||
.join('&')
|
||||
),
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
redirect: 'manual',
|
||||
});
|
||||
})
|
||||
.then(() => {})
|
||||
.catch(() => Promise.reject(new WebCrawlerLoginError()));
|
||||
}
|
||||
|
||||
private getNHentai(path: string): Promise<Document> {
|
||||
return this.webCrawler
|
||||
.fetch(`${url}${path}`)
|
||||
.then((res: Response) => res.text())
|
||||
.then((text: string) => {
|
||||
const { document } = new JSDOM(text).window;
|
||||
return document;
|
||||
});
|
||||
}
|
||||
|
||||
private postNHentai(path: string, requestInit: RequestInit = {}): Promise<Response> {
|
||||
const postUrl = `${url}${path}`;
|
||||
return this.webCrawler.fetch(postUrl, {
|
||||
...requestInit,
|
||||
...{
|
||||
headers: {
|
||||
...requestInit.headers,
|
||||
...{
|
||||
Host: domain,
|
||||
Referer: postUrl,
|
||||
},
|
||||
},
|
||||
},
|
||||
method: 'post',
|
||||
});
|
||||
}
|
||||
|
||||
private getLoginMeta(): Promise<ILoginMeta> {
|
||||
return this.getNHentai(paths.login).then((document: Document) => {
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
||||
for (let i = 0; i < document.forms.length; i++) {
|
||||
const form: HTMLFormElement = document.forms[i];
|
||||
const valueStore: ILoginMeta = {};
|
||||
let isLoginForm = false;
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
||||
for (let j = 0; j < form.elements.length; j++) {
|
||||
const input = form.elements[j];
|
||||
const name = input.getAttribute('name');
|
||||
|
||||
if (name === usernameInput || name === passwordInput) {
|
||||
isLoginForm = true;
|
||||
} else if (name) {
|
||||
const value = input.getAttribute('value');
|
||||
if (value) {
|
||||
valueStore[name] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isLoginForm) {
|
||||
return valueStore;
|
||||
}
|
||||
}
|
||||
return Promise.reject(new WebCrawlerFormError());
|
||||
});
|
||||
public getFavorites(): Promise<NodeJS.ReadableStream> {
|
||||
return this.appWindow.getFavorites();
|
||||
}
|
||||
}
|
||||
|
223
src/main/modules/nhentai/nhentai-app-window.ts
Normal file
223
src/main/modules/nhentai/nhentai-app-window.ts
Normal file
@ -0,0 +1,223 @@
|
||||
import { WebContents } from 'electron';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { Readable } from 'stream';
|
||||
import { URL } from 'url';
|
||||
import { createReadStream, remove } from 'fs-extra';
|
||||
import { injectable } from 'inversify';
|
||||
import { inject } from '../../core/inject';
|
||||
import { UrlAppWindow } from '../app-window/url-app-window';
|
||||
import { WindowClosedError } from '../app-window/window-closed-error';
|
||||
import { ISessionHelper } from '../session/i-session-helper';
|
||||
import { INhentaiAppWindow } from './i-nhentai-app-window';
|
||||
import { IFavorite } from './nhentai';
|
||||
import {
|
||||
url as nhentaiUrl,
|
||||
hostname as nhentaiHostname,
|
||||
paths as nhentaiPaths,
|
||||
getFavoritePageUrl,
|
||||
nextFavoritePageSelector,
|
||||
coverLinkSelector,
|
||||
downloadLinkId,
|
||||
getGalleryId,
|
||||
} from './nhentai-util';
|
||||
|
||||
const waitInterval = 2000;
|
||||
|
||||
@injectable()
|
||||
export class NhentaiAppWindow extends UrlAppWindow implements INhentaiAppWindow {
|
||||
public constructor(@inject('session-helper') sessionHelper: ISessionHelper) {
|
||||
super(sessionHelper, nhentaiUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws WindowClosedError when this._window is null
|
||||
*/
|
||||
public async getFavorites(): Promise<NodeJS.ReadableStream> {
|
||||
const error = new WindowClosedError();
|
||||
|
||||
if (this.isClosed()) {
|
||||
await this.open();
|
||||
}
|
||||
|
||||
if (!(await this.isLoggedIn())) {
|
||||
await this.login();
|
||||
}
|
||||
|
||||
if (!this._window) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const bookUrls: string[] = [];
|
||||
for await (const wc of this.getFavoritePageWebContentsGenerator()) {
|
||||
bookUrls.push(
|
||||
...((await wc.executeJavaScript(
|
||||
`Array.from(document.querySelectorAll('${coverLinkSelector}')).map((el) => el.href)`
|
||||
)) as string[])
|
||||
);
|
||||
}
|
||||
|
||||
const readable = Readable.from(
|
||||
(async function* (thisArg): AsyncGenerator<IFavorite> {
|
||||
for (const bookUrl of bookUrls) {
|
||||
yield await thisArg.getBookTorrent(bookUrl);
|
||||
}
|
||||
})(this),
|
||||
{
|
||||
objectMode: true,
|
||||
}
|
||||
);
|
||||
readable.once('close', this.close);
|
||||
|
||||
return readable;
|
||||
}
|
||||
|
||||
protected getCsp(): IContentSecurityPolicy {
|
||||
return {
|
||||
'default-src': ['nhentai.net'],
|
||||
'script-src': ['nhentai.net', "'unsafe-eval'"],
|
||||
'script-src-elem': ['*.nhentai.net', "'unsafe-inline'", '*.google.com', '*.gstatic.com'],
|
||||
'style-src': [
|
||||
'*.nhentai.net',
|
||||
"'unsafe-inline'",
|
||||
'fonts.googleapis.com',
|
||||
'cdnjs.cloudflare.com',
|
||||
'maxcdn.bootstrapcdn.com',
|
||||
'*.gstatic.com',
|
||||
],
|
||||
'img-src': ['*.nhentai.net', 'data:', '*.gstatic.com', '*.google.com'],
|
||||
'font-src': ['fonts.gstatic.com', 'cdnjs.cloudflare.com', 'maxcdn.bootstrapcdn.com'],
|
||||
'frame-src': ['*.google.com'],
|
||||
'connect-src': ['nhentai.net', '*.google.com'],
|
||||
'worker-src': ['*.google.com'],
|
||||
};
|
||||
}
|
||||
|
||||
protected onWillNavigate(event: Electron.Event, navigationUrl: string): void {
|
||||
const url = new URL(navigationUrl);
|
||||
|
||||
if (url.hostname !== nhentaiHostname) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws WindowClosedError when this._window is null
|
||||
*/
|
||||
private async isLoggedIn(): Promise<boolean> {
|
||||
if (!this._window) {
|
||||
throw new WindowClosedError();
|
||||
}
|
||||
return this._window.webContents
|
||||
.executeJavaScript(
|
||||
`fetch('${
|
||||
nhentaiUrl + nhentaiPaths.favorites
|
||||
}', {credentials: 'include', redirect: 'manual'}).then((res) => res.status)`
|
||||
)
|
||||
.then((status: number) => status === HttpCode.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws WindowClosedError when this._window is null
|
||||
*/
|
||||
private async login(): Promise<void> {
|
||||
if (!this._window) {
|
||||
throw new WindowClosedError();
|
||||
}
|
||||
await this.loadUrlSafe(nhentaiUrl + nhentaiPaths.login);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setInterval(() => {
|
||||
this.isLoggedIn()
|
||||
.then((loggedIn) => {
|
||||
if (loggedIn) {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
.catch((reason) => reject(reason));
|
||||
}, waitInterval);
|
||||
});
|
||||
}
|
||||
|
||||
private async *getFavoritePageWebContentsGenerator(): AsyncGenerator<WebContents, undefined> {
|
||||
const error = new WindowClosedError();
|
||||
if (!this._window) {
|
||||
throw error;
|
||||
}
|
||||
await this.loadUrlSafe(getFavoritePageUrl(1));
|
||||
yield this._window.webContents;
|
||||
do {
|
||||
try {
|
||||
const hasNextPage = (await this._window.webContents.executeJavaScript(
|
||||
`!!document.querySelector('${nextFavoritePageSelector}')`
|
||||
)) as boolean;
|
||||
if (hasNextPage) {
|
||||
yield new Promise<WebContents>((resolve) => {
|
||||
if (this._window) {
|
||||
this._window.webContents.once('did-finish-load', () => {
|
||||
if (this._window) {
|
||||
resolve(this._window.webContents);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
void this._window.webContents.executeJavaScript(
|
||||
`document.querySelector('${nextFavoritePageSelector}').click()`
|
||||
);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async getBookTorrent(bookUrl: string): Promise<IFavorite> {
|
||||
const galleryId = getGalleryId(bookUrl);
|
||||
const fileName = `${galleryId}.torrent`;
|
||||
const filePath = path.resolve(os.tmpdir(), fileName);
|
||||
await this.loadUrlSafe(bookUrl);
|
||||
await new Promise<string>((resolve, reject) => {
|
||||
if (!this._window) {
|
||||
throw new WindowClosedError();
|
||||
}
|
||||
this._window.webContents.session.once('will-download', (event, item) => {
|
||||
item.setSavePath(filePath);
|
||||
item.once('done', (doneEvent, state) => {
|
||||
switch (state) {
|
||||
case 'completed':
|
||||
resolve();
|
||||
break;
|
||||
case 'cancelled':
|
||||
case 'interrupted':
|
||||
default:
|
||||
reject(new Error(state));
|
||||
break;
|
||||
}
|
||||
});
|
||||
item.on('updated', () => {
|
||||
item.resume();
|
||||
});
|
||||
});
|
||||
void this._window.webContents.executeJavaScript(`document.getElementById('${downloadLinkId}').click()`);
|
||||
});
|
||||
|
||||
const readable = createReadStream(filePath, { emitClose: true });
|
||||
|
||||
readable.once('close', () => {
|
||||
void remove(filePath);
|
||||
});
|
||||
|
||||
return {
|
||||
name: fileName,
|
||||
torrentFile: readable,
|
||||
};
|
||||
}
|
||||
}
|
@ -1,26 +1,44 @@
|
||||
import { dialog } from 'electron';
|
||||
import path from 'path';
|
||||
import { createWriteStream } from 'fs-extra';
|
||||
import { container } from '../../core/container';
|
||||
import { II18nTranslator } from '../i18n/i-i18n-translator';
|
||||
import { answer } from '../ipc/annotations/answer';
|
||||
import { INhentaiApi } from './i-nhentai-api';
|
||||
import { IFavorite } from './nhentai';
|
||||
|
||||
export class NhentaiIpcController implements IIpcController {
|
||||
private nhentaiApi: INhentaiApi;
|
||||
private readonly nhentaiApi: INhentaiApi;
|
||||
|
||||
public constructor(nhentaiApi: INhentaiApi) {
|
||||
private readonly translator: II18nTranslator;
|
||||
|
||||
private constructor(nhentaiApi: INhentaiApi, translator: II18nTranslator) {
|
||||
this.nhentaiApi = nhentaiApi;
|
||||
this.translator = translator;
|
||||
}
|
||||
|
||||
@answer(IpcChannel.LOGIN)
|
||||
public login(credentials: ICredentials): Promise<void> {
|
||||
return this.nhentaiApi.login(credentials.name, credentials.password);
|
||||
}
|
||||
@answer(IpcChannel.NHENTAI_SAVE_FAVORITES)
|
||||
public async nhentaiSaveFavorites(): Promise<void> {
|
||||
const result = await dialog.showOpenDialog({
|
||||
properties: ['openDirectory'],
|
||||
title: this.translator.t('Select torrent file save location'),
|
||||
});
|
||||
|
||||
@answer(IpcChannel.LOGGED_IN)
|
||||
public loggedIn(): Promise<boolean> {
|
||||
return this.nhentaiApi.isLoggedIn();
|
||||
const favoritesStream = await this.nhentaiApi.getFavorites();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
favoritesStream.on('data', (favorite: IFavorite) => {
|
||||
const writable = createWriteStream(path.resolve(result.filePaths[0], favorite.name));
|
||||
favorite.torrentFile.pipe(writable);
|
||||
});
|
||||
|
||||
favoritesStream.once('close', resolve);
|
||||
});
|
||||
}
|
||||
|
||||
public get(): NhentaiIpcController {
|
||||
const nhentaiApi: INhentaiApi = container.get('nhentai-api');
|
||||
return new NhentaiIpcController(nhentaiApi);
|
||||
const translator: II18nTranslator = container.get('i18n-translator');
|
||||
return new NhentaiIpcController(nhentaiApi, translator);
|
||||
}
|
||||
}
|
||||
|
26
src/main/modules/nhentai/nhentai-util.ts
Normal file
26
src/main/modules/nhentai/nhentai-util.ts
Normal file
@ -0,0 +1,26 @@
|
||||
export const hostname = 'nhentai.net';
|
||||
export const url = `https://${hostname}/`;
|
||||
|
||||
export const paths = {
|
||||
books: 'g/',
|
||||
login: 'login/',
|
||||
favorites: 'favorites/',
|
||||
};
|
||||
|
||||
export const downloadLinkId = 'download';
|
||||
|
||||
export const nextFavoritePageSelector = 'a.next';
|
||||
export const coverLinkSelector = 'a.cover';
|
||||
|
||||
export function getFavoritePageUrl(page: number): string {
|
||||
return `${url + paths.favorites}?page=${page}`;
|
||||
}
|
||||
|
||||
export function getGalleryId(bookUrl: string): string {
|
||||
const regExpExecArray = /https:\/\/nhentai\.net\/g\/(\d+)/.exec(bookUrl);
|
||||
if (regExpExecArray && regExpExecArray[1]) {
|
||||
return regExpExecArray[1];
|
||||
} else {
|
||||
throw new TypeError(`could not get nhentai gallery if from the book url ${bookUrl}`);
|
||||
}
|
||||
}
|
4
src/main/modules/nhentai/nhentai.d.ts
vendored
Normal file
4
src/main/modules/nhentai/nhentai.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
export interface IFavorite {
|
||||
name: string;
|
||||
torrentFile: NodeJS.ReadableStream;
|
||||
}
|
5
src/main/modules/session/i-session-helper.ts
Normal file
5
src/main/modules/session/i-session-helper.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { BrowserWindow } from 'electron';
|
||||
|
||||
export interface ISessionHelper {
|
||||
setCsp(window: BrowserWindow, csp: IContentSecurityPolicy): void;
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
export interface ISession {
|
||||
setHeaders(): void;
|
||||
}
|
43
src/main/modules/session/session-helper.ts
Normal file
43
src/main/modules/session/session-helper.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { isDev } from '../../core/env';
|
||||
import { ISessionHelper } from './i-session-helper';
|
||||
|
||||
const defaultCsp: IContentSecurityPolicy = {
|
||||
'default-src': ["'self'"],
|
||||
'style-src': ["'unsafe-inline'"],
|
||||
'object-src': ["'none'"],
|
||||
};
|
||||
|
||||
@injectable()
|
||||
export class SessionHelper implements ISessionHelper {
|
||||
private static stringifyCspHeader(csp: IContentSecurityPolicy): string {
|
||||
return Object.entries(csp)
|
||||
.map((directive: [string, CspValue[]]) => `${directive[0]} ${directive[1]?.join(' ')}`)
|
||||
.join('; ');
|
||||
}
|
||||
|
||||
public setCsp(window: Electron.BrowserWindow, csp: IContentSecurityPolicy): void {
|
||||
const mergedCsp: IContentSecurityPolicy = { ...defaultCsp, ...csp };
|
||||
|
||||
if (isDev()) {
|
||||
mergedCsp['default-src'] = ['devtools:'].concat(mergedCsp['default-src'] ?? []);
|
||||
mergedCsp['script-src'] = ["'unsafe-eval'"].concat(mergedCsp['script-src'] ?? []);
|
||||
mergedCsp['script-src-elem'] = ['file:', 'devtools:', "'unsafe-inline'"].concat(
|
||||
mergedCsp['script-src-elem'] ?? []
|
||||
);
|
||||
mergedCsp['style-src'] = ['devtools:', "'unsafe-inline'"].concat(mergedCsp['style-src'] ?? []);
|
||||
mergedCsp['img-src'] = ['devtools:'].concat(mergedCsp['img-src'] ?? []);
|
||||
mergedCsp['connect-src'] = ['devtools:', 'data:'].concat(mergedCsp['connect-src'] ?? []);
|
||||
mergedCsp['worker-src'] = ['devtools:'].concat(mergedCsp['worker-src'] ?? []);
|
||||
}
|
||||
|
||||
window.webContents.session.webRequest.onHeadersReceived((details, callback) => {
|
||||
callback({
|
||||
responseHeaders: {
|
||||
...details.responseHeaders,
|
||||
'Content-Security-Policy': SessionHelper.stringifyCspHeader(mergedCsp),
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
23
src/main/modules/session/session.d.ts
vendored
Normal file
23
src/main/modules/session/session.d.ts
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
type CspValue = '*' | "'none'" | "'self'" | "'unsafe-inline'" | "'unsafe-eval'" | string;
|
||||
|
||||
/**
|
||||
* This interface represents a content security policy.
|
||||
*
|
||||
* @see https://www.w3.org/TR/CSP/
|
||||
* @see https://content-security-policy.com/
|
||||
*/
|
||||
interface IContentSecurityPolicy {
|
||||
'child-src'?: CspValue[];
|
||||
'connect-src'?: CspValue[];
|
||||
'default-src'?: CspValue[];
|
||||
'font-src'?: CspValue[];
|
||||
'frame-src'?: CspValue[];
|
||||
'img-src'?: CspValue[];
|
||||
'media-src'?: CspValue[];
|
||||
'object-src'?: ["'none'"];
|
||||
'script-src'?: CspValue[];
|
||||
'script-src-elem'?: CspValue[];
|
||||
'script-src-attr'?: CspValue[];
|
||||
'style-src'?: CspValue[];
|
||||
'worker-src'?: CspValue[];
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
import { session } from 'electron';
|
||||
import { injectable } from 'inversify';
|
||||
import { isDev } from '../../core/env';
|
||||
import { ISession } from './i-session';
|
||||
|
||||
@injectable()
|
||||
export class Session implements ISession {
|
||||
public setHeaders(): void {
|
||||
// these headers only work on web requests, file:// protocol is handled via meta tags in the html
|
||||
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
||||
callback({
|
||||
responseHeaders: {
|
||||
...details.responseHeaders,
|
||||
'Content-Security-Policy': isDev()
|
||||
? [
|
||||
'default-src devtools:;' +
|
||||
"script-src 'unsafe-eval';" +
|
||||
"script-src-elem file: devtools: 'sha256-hl04hLzKBpmsfWF2wIA/0Vs6ZNV5T9ZNFY//3uXrgSk=';" +
|
||||
"style-src devtools: 'unsafe-inline';" +
|
||||
'connect-src devtools: data:',
|
||||
]
|
||||
: ["default-src 'self';" + "style-src 'unsafe-inline'"],
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
import { CookieJar } from 'jsdom';
|
||||
import { RequestInit, Response } from 'node-fetch';
|
||||
|
||||
export interface IWebCrawler {
|
||||
cookieJar: CookieJar;
|
||||
fetch(url: string, requestInit?: RequestInit): Promise<Response>;
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
import rewiremock from 'rewiremock';
|
||||
import '../../../../mocks/electron';
|
||||
|
||||
import { expect } from 'chai';
|
||||
import 'mocha';
|
||||
import nock from 'nock';
|
||||
import { Response } from 'node-fetch';
|
||||
import sinon from 'sinon';
|
||||
import { container } from '../../core/container';
|
||||
import { Store } from '../store/store';
|
||||
import { StoreMock } from '../store/store.mock';
|
||||
import { IWebCrawler } from './i-web-crawler';
|
||||
|
||||
describe('Web Crawler', function () {
|
||||
this.timeout(2000);
|
||||
|
||||
before(() => {
|
||||
rewiremock.enable();
|
||||
|
||||
container.unbind('store');
|
||||
container.bind('store').to(StoreMock);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
if (!nock.isActive()) {
|
||||
nock.activate();
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
nock.cleanAll();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
rewiremock.disable();
|
||||
|
||||
container.unbind('store');
|
||||
container.bind('store').to(Store);
|
||||
});
|
||||
|
||||
it('fetches websites', async () => {
|
||||
const callback = sinon.spy();
|
||||
|
||||
const testUrl = 'https://example.com';
|
||||
nock(testUrl)
|
||||
.get(/.*/)
|
||||
.reply(
|
||||
HttpCode.OK,
|
||||
() => {
|
||||
callback();
|
||||
return JSON.stringify([{ id: 12, comment: 'Hey there' }]);
|
||||
},
|
||||
{ 'Content-Type': 'application/json' }
|
||||
)
|
||||
.persist();
|
||||
|
||||
const webCrawler: IWebCrawler = container.get('web-crawler');
|
||||
|
||||
const res: Response = await webCrawler.fetch(testUrl);
|
||||
expect(callback.callCount).to.equal(1, 'multiple requests (or none) are sent when only one should be');
|
||||
const json = (await res.json()) as unknown;
|
||||
expect(json).to.deep.equal([{ id: 12, comment: 'Hey there' }], 'response body is incorrect');
|
||||
});
|
||||
});
|
@ -1,69 +0,0 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { CookieJar } from 'jsdom';
|
||||
import nodeFetch, { RequestInit, Response } from 'node-fetch';
|
||||
import { inject } from '../../core/inject';
|
||||
import { CookieSaveError } from '../error/cookie-save-error';
|
||||
import { IStore } from '../store/i-store';
|
||||
import { IWebCrawler } from './i-web-crawler';
|
||||
|
||||
@injectable()
|
||||
export class WebCrawler implements IWebCrawler {
|
||||
public cookieJar: CookieJar;
|
||||
|
||||
private initialized: boolean;
|
||||
|
||||
private store: IStore;
|
||||
|
||||
public constructor(@inject('store') store: IStore) {
|
||||
this.initialized = false;
|
||||
this.cookieJar = new CookieJar();
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
public fetch(url: string, requestInit: RequestInit = {}): Promise<Response> {
|
||||
return this.init().then(() => {
|
||||
const cookiedInit = {
|
||||
...requestInit,
|
||||
...{
|
||||
headers: {
|
||||
...requestInit.headers,
|
||||
...{
|
||||
Cookie: this.cookieJar.getCookieStringSync(url),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return nodeFetch(url, cookiedInit).then((res: Response) => {
|
||||
this.setCookies(res.headers.raw()['set-cookie'], url).catch((reason: Error) => {
|
||||
throw new CookieSaveError(reason.message);
|
||||
});
|
||||
return res;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private init(): Promise<void> {
|
||||
if (!this.initialized) {
|
||||
return this.store.load(StoreKey.COOKIES).then((cookies: unknown) => {
|
||||
if (cookies !== undefined) {
|
||||
this.cookieJar = CookieJar.deserializeSync(cookies as string);
|
||||
}
|
||||
this.initialized = true;
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
private setCookies(header: string[], url: string): Promise<void> {
|
||||
if (header) {
|
||||
header.forEach((cookie: string) => {
|
||||
this.cookieJar.setCookieSync(cookie, url);
|
||||
});
|
||||
return this.store.save(StoreKey.COOKIES, this.cookieJar.serializeSync()).catch((reason: Error) => {
|
||||
throw new CookieSaveError(reason.message);
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
@ -1,43 +1,15 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte/internal';
|
||||
import { t } from '../../services/utils';
|
||||
import Bttn from '../1-atoms/Bttn.svelte';
|
||||
import { loggedIn } from '../../services/store';
|
||||
|
||||
let form = {
|
||||
name: '',
|
||||
password: '',
|
||||
};
|
||||
import { nhentaiSaveFavorites } from '../../services/api';
|
||||
|
||||
function handleClick() {
|
||||
loggedIn.fetchLogin(form).catch((reason) => {
|
||||
console.log(reason);
|
||||
});
|
||||
nhentaiSaveFavorites();
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
loggedIn.fetchIsLoggedIn();
|
||||
|
||||
return () => {};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
||||
<div class="nhentai-login">
|
||||
{#if $loggedIn}
|
||||
<div>{ t('logged in!') }</div>
|
||||
{:else}
|
||||
<form class="nhentai-login">
|
||||
<label>
|
||||
<span>{ t('Username/Email') }</span>
|
||||
<input bind:value="{form.name}" />
|
||||
</label>
|
||||
<label>
|
||||
<span>{ t('Password') }</span>
|
||||
<input bind:value="{form.password}" type="password" />
|
||||
</label>
|
||||
<Bttn on:click="{handleClick}">{ t('submit') }</Bttn>
|
||||
</form>
|
||||
{/if}
|
||||
<Bttn on:click="{handleClick}">{ t('Save nhentai Favorites') }</Bttn>
|
||||
</div>
|
||||
|
@ -27,10 +27,6 @@ const ipcClient: IIpcClient = {
|
||||
},
|
||||
};
|
||||
|
||||
export function login(credentials: ICredentials): Promise<void> {
|
||||
return ipcClient.ask(IpcChannel.LOGIN, credentials) as Promise<void>;
|
||||
}
|
||||
|
||||
export function isLoggedIn(): Promise<boolean> {
|
||||
return ipcClient.ask(IpcChannel.LOGGED_IN) as Promise<boolean>;
|
||||
export function nhentaiSaveFavorites(): Promise<void> {
|
||||
return ipcClient.ask(IpcChannel.NHENTAI_SAVE_FAVORITES) as Promise<void>;
|
||||
}
|
||||
|
@ -1,21 +0,0 @@
|
||||
import { writable, Readable } from 'svelte/store';
|
||||
import * as api from './api';
|
||||
|
||||
const { subscribe, set } = writable<boolean>(false);
|
||||
|
||||
interface ILoggedIn extends Readable<boolean> {
|
||||
fetchIsLoggedIn(): Promise<void>;
|
||||
fetchLogin(credentials: ICredentials): Promise<void>;
|
||||
}
|
||||
|
||||
export const loggedIn: ILoggedIn = {
|
||||
subscribe,
|
||||
fetchIsLoggedIn(): Promise<void> {
|
||||
return api.isLoggedIn().then((isLoggedIn: boolean) => {
|
||||
set(isLoggedIn);
|
||||
});
|
||||
},
|
||||
fetchLogin(this: ILoggedIn, credentials: ICredentials): Promise<void> {
|
||||
return api.login(credentials).then(this.fetchIsLoggedIn);
|
||||
},
|
||||
};
|
8
types/ipc.d.ts
vendored
8
types/ipc.d.ts
vendored
@ -1,6 +1,5 @@
|
||||
declare const enum IpcChannel {
|
||||
LOGIN = 'LOGIN',
|
||||
LOGGED_IN = 'LOGGED_IN',
|
||||
NHENTAI_SAVE_FAVORITES = 'NHENTAI_SAVE_FAVORITES',
|
||||
}
|
||||
|
||||
interface IIpcPayload {
|
||||
@ -16,11 +15,6 @@ interface IIpcResponse {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface ICredentials {
|
||||
name: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface IIpcClient {
|
||||
ask: (channel: IpcChannel, data?: unknown) => Promise<unknown>;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user