├── app ├── img │ ├── fb.png │ ├── heart.png │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ ├── icon_16.png │ ├── icon_32.png │ ├── patreon.png │ ├── splash.png │ ├── twitter.png │ ├── icon_noshadow_48.png │ ├── tech_tac_alpha_127.png │ ├── ob_text_logo_titlebar.png │ ├── ui-icons_444444_256x240.png │ ├── import.svg │ ├── update_check.svg │ ├── ball.svg │ ├── dots.svg │ ├── load.svg │ └── update_trcks.svg ├── fonts │ ├── woff │ │ ├── XRXV3I6Li01BKofINeaB.woff2 │ │ ├── XRXW3I6Li01BKofAjsOUYevI.woff2 │ │ └── XRXW3I6Li01BKofAksCUYevI.woff2 │ └── font.css ├── data │ ├── themes │ │ ├── assets │ │ │ ├── katdark │ │ │ │ └── titlebar.png │ │ │ ├── tpblight │ │ │ │ ├── titlebar.png │ │ │ │ └── custom.css │ │ │ └── carbondark │ │ │ │ └── custom.css │ │ └── themes.db │ └── config.db ├── renderers │ └── RndSplash.js ├── css │ ├── clusterize.css │ ├── styles.css │ ├── jquery-ui.min.css │ ├── theme.css │ ├── comp-override.css │ └── WndMain.css ├── reset-db.js ├── js │ ├── jquery.dragbetter.js │ ├── WndMain.js │ ├── clusterize.min.js │ └── bootstrap-notify.min.js ├── main-functions │ ├── scrape.js │ ├── upd-dump.js │ ├── deprecated │ │ ├── import-dumo-method-2.js │ │ └── import-dump-method-1.js │ ├── search.js │ └── import-dump.js ├── WndSplash.html ├── WndMain.html └── main.js ├── changelog.txt ├── LICENSE ├── .gitignore ├── package.json └── README.md /app/img/fb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/fb.png -------------------------------------------------------------------------------- /app/img/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/heart.png -------------------------------------------------------------------------------- /app/img/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/icon.icns -------------------------------------------------------------------------------- /app/img/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/icon.ico -------------------------------------------------------------------------------- /app/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/icon.png -------------------------------------------------------------------------------- /app/img/icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/icon_16.png -------------------------------------------------------------------------------- /app/img/icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/icon_32.png -------------------------------------------------------------------------------- /app/img/patreon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/patreon.png -------------------------------------------------------------------------------- /app/img/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/splash.png -------------------------------------------------------------------------------- /app/img/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/twitter.png -------------------------------------------------------------------------------- /app/img/icon_noshadow_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/icon_noshadow_48.png -------------------------------------------------------------------------------- /app/img/tech_tac_alpha_127.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/tech_tac_alpha_127.png -------------------------------------------------------------------------------- /app/img/ob_text_logo_titlebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/ob_text_logo_titlebar.png -------------------------------------------------------------------------------- /app/img/ui-icons_444444_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/ui-icons_444444_256x240.png -------------------------------------------------------------------------------- /app/fonts/woff/XRXV3I6Li01BKofINeaB.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/fonts/woff/XRXV3I6Li01BKofINeaB.woff2 -------------------------------------------------------------------------------- /app/data/themes/assets/katdark/titlebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/data/themes/assets/katdark/titlebar.png -------------------------------------------------------------------------------- /app/data/themes/assets/tpblight/titlebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/data/themes/assets/tpblight/titlebar.png -------------------------------------------------------------------------------- /app/fonts/woff/XRXW3I6Li01BKofAjsOUYevI.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/fonts/woff/XRXW3I6Li01BKofAjsOUYevI.woff2 -------------------------------------------------------------------------------- /app/fonts/woff/XRXW3I6Li01BKofAksCUYevI.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/fonts/woff/XRXW3I6Li01BKofAksCUYevI.woff2 -------------------------------------------------------------------------------- /app/data/themes/assets/tpblight/custom.css: -------------------------------------------------------------------------------- 1 | .tbl-grid, .trackers-tbl { 2 | background: #f6f1ee; 3 | } 4 | 5 | .table-striped tbody tr:nth-of-type(odd) { 6 | background-color: #f2ece7; 7 | } 8 | 9 | thead { 10 | background: #d2b9a6; 11 | } 12 | 13 | .table > thead > tr > th { 14 | border-top: none; 15 | } -------------------------------------------------------------------------------- /app/renderers/RndSplash.js: -------------------------------------------------------------------------------- 1 | const {ipcRenderer} = require('electron'); 2 | 3 | window.onerror = function (msg, url, lineNo, columnNo, error) { 4 | ipcRenderer.send('logger', '[SPLASH]' + error.stack); 5 | }; // Send window errors to Main process 6 | 7 | ipcRenderer.on('fade', function () { 8 | document.body.style.opacity = '1'; 9 | }); -------------------------------------------------------------------------------- /changelog.txt: -------------------------------------------------------------------------------- 1 | v 2.0.0: 2 | - Programming language switched to Javascript (NodeJS) from Java 3 | - Functionality stabilized on all platforms 4 | - GUI Built on Electron framework (HTML + CSS + Js) 5 | - Splash screen 6 | - Smoother animations 7 | - DB switched to NoSQL (NeDB) 8 | - Made Open-source 9 | - Gzip dump import support 10 | - 85% Faster importing 11 | - Drag and Drop importing 12 | - Remote dump update support 13 | - Auto dump update support 14 | - System notifications support 15 | - System tray support 16 | - 15% Faster searching 17 | - Instant search feature 18 | - Search autocomplete support 19 | - Fixed table sorting 20 | - Faster filtering 21 | - Faster Seeds/Peers discovery 22 | - DHT support for scraping 23 | - Customizable tracker fetching URL 24 | - Best trackers selection for magnet links 25 | - Theming support 26 | - Customizable configurations (Preferences window) 27 | - Configurations import/export support 28 | - Google search button 29 | - Windows shortcut helper 30 | - Logging to file support 31 | 32 | v 1.0.2: 33 | - Initial Release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Mahesh Bandara Wijerathna (m4heshd) 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 | -------------------------------------------------------------------------------- /app/css/clusterize.css: -------------------------------------------------------------------------------- 1 | /* max-height - the only parameter in this file that needs to be edited. 2 | * Change it to suit your needs. The rest is recommended to leave as is. 3 | */ 4 | .clusterize-scroll{ 5 | max-height: 2160px; 6 | /*overflow: auto;*/ 7 | } 8 | 9 | /** 10 | * Avoid vertical margins for extra tags 11 | * Necessary for correct calculations when rows have nonzero vertical margins 12 | */ 13 | .clusterize-extra-row{ 14 | margin-top: 0 !important; 15 | margin-bottom: 0 !important; 16 | } 17 | 18 | /* By default extra tag .clusterize-keep-parity added to keep parity of rows. 19 | * Useful when used :nth-child(even/odd) 20 | */ 21 | .clusterize-extra-row.clusterize-keep-parity{ 22 | display: none; 23 | } 24 | 25 | /* During initialization clusterize adds tabindex to force the browser to keep focus 26 | * on the scrolling list, see issue #11 27 | * Outline removes default browser's borders for focused elements. 28 | */ 29 | .clusterize-content{ 30 | outline: 0; 31 | counter-reset: clusterize-counter; 32 | } 33 | 34 | /* Centering message that appears when no data provided 35 | */ 36 | .clusterize-no-data td{ 37 | text-align: center; 38 | } -------------------------------------------------------------------------------- /app/fonts/font.css: -------------------------------------------------------------------------------- 1 | /* latin */ 2 | @font-face { 3 | font-family: 'Nunito'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: local('Nunito Regular'), local('Nunito-Regular'), url(woff/XRXV3I6Li01BKofINeaB.woff2) format('woff2'); 7 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 8 | } 9 | @font-face { 10 | font-family: 'Nunito'; 11 | font-style: normal; 12 | font-weight: 700; 13 | src: local('Nunito Bold'), local('Nunito-Bold'), url(woff/XRXW3I6Li01BKofAjsOUYevI.woff2) format('woff2'); 14 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 15 | } 16 | @font-face { 17 | font-family: 'Nunito'; 18 | font-style: normal; 19 | font-weight: 800; 20 | src: local('Nunito ExtraBold'), local('Nunito-ExtraBold'), url(woff/XRXW3I6Li01BKofAksCUYevI.woff2) format('woff2'); 21 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 22 | } -------------------------------------------------------------------------------- /app/img/import.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/img/update_check.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dump files 2 | *.csv 3 | app/data/downloads/ 4 | 5 | # Distribution 6 | dist 7 | 8 | # Logs 9 | logs 10 | *.log 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | 15 | # Runtime data 16 | pids 17 | *.pid 18 | *.seed 19 | *.pid.lock 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # Typescript v1 declaration files 47 | typings/ 48 | 49 | # Optional npm cache directory 50 | .npm 51 | 52 | # Optional eslint cache 53 | .eslintcache 54 | 55 | # Optional REPL history 56 | .node_repl_history 57 | 58 | # Output of 'npm pack' 59 | *.tgz 60 | 61 | # Yarn Integrity file 62 | .yarn-integrity 63 | 64 | # dotenv environment variables file 65 | .env 66 | 67 | # next.js build output 68 | .next 69 | 70 | # Editor directories and files 71 | .idea 72 | .vscode 73 | *.suo 74 | *.ntvs* 75 | *.njsproj 76 | *.sln 77 | *.sw? 78 | -------------------------------------------------------------------------------- /app/css/styles.css: -------------------------------------------------------------------------------- 1 | /* Global 2 | --------------*/ 3 | :root{ 4 | --txtclr : #8a9ba1; 5 | } 6 | body { 7 | font-family: Roboto,sans-serif; 8 | font-size: 13px; 9 | line-height: 1.42857143; 10 | color: #8a9ba1; 11 | background-color: #1e2a31; 12 | } 13 | 14 | /* Global Components 15 | ----------------------*/ 16 | 17 | .txtbox { 18 | border: 0; 19 | height: inherit; 20 | padding: 10px 20px 10px 50px; 21 | border-radius: 2px; 22 | width: 50%; 23 | background-color: #2b3942; 24 | font-size: 15px; 25 | color: var(--txtclr); 26 | } 27 | 28 | .btn-ui{ 29 | border: 0; 30 | height: inherit; 31 | padding: 10px; 32 | border-radius: 4px; 33 | background-color: #2b3942; 34 | font-size: 15px; 35 | color: var(--txtclr); 36 | } 37 | 38 | .txtbox:focus { 39 | background: #edeff0; 40 | color: #22313a; 41 | } 42 | 43 | ::placeholder { 44 | color: var(--txtclr); 45 | opacity: 1; 46 | } 47 | 48 | *,.txtbox, .btn-ui { 49 | -webkit-transition: all; 50 | -o-transition: all; 51 | transition: all; 52 | -webkit-transition-duration: .3s; 53 | transition-duration: .3s; 54 | } 55 | 56 | .top-search__reset { 57 | left: 0; 58 | font-size: 20px; 59 | color: #b4bfc3; 60 | top: 0; 61 | position: absolute; 62 | cursor: pointer; 63 | width: 50px; 64 | height: 100%; 65 | } 66 | 67 | .top-search__reset:hover { 68 | opacity: .75; 69 | filter: alpha(opacity=75); 70 | } -------------------------------------------------------------------------------- /app/img/ball.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/data/config.db: -------------------------------------------------------------------------------- 1 | {"type":"win-state","maxed":false,"position":[null,null],"size":[1280,800],"_id":"0"} 2 | {"type":"trackers","url":"https://newtrackon.com/api/stable","trackers":["http://tracker.tfile.me:80/announce","udp://tracker.torrent.eu.org:451/announce","udp://thetracker.org:80/announce","http://torrent.nwps.ws:80/announce","http://alpha.torrenttracker.nl:443/announce","http://0d.kebhana.mx:443/announce","udp://bt.xxx-tracker.com:2710/announce","udp://inferno.demonoid.pw:3418/announce","udp://tracker.justseed.it:1337/announce","udp://tracker.vanitycore.co:6969/announce","udp://open.stealth.si:80/announce","udp://tracker.coppersurfer.tk:6969/announce","udp://tracker.opentrackr.org:1337/announce","udp://tracker.cypherpunks.ru:6969/announce","udp://tracker.tiny-vps.com:6969/announce","http://retracker.mgts.by:80/announce","udp://retracker.lanta-net.ru:2710/announce","udp://public.popcorn-tracker.org:6969/announce","http://tracker.skyts.net:6969/announce","udp://ipv4.tracker.harry.lu:80/announce","http://tracker.city9x.com:2710/announce","udp://tracker.martlet.tk:6969/announce","http://retracker.telecom.by:80/announce","udp://9.rarbg.com:2710/announce"],"_id":"1"} 3 | {"type":"search","rs_count":100,"smart":true,"inst":false,"useAC":true,"history":[],"_id":"2"} 4 | {"type":"dump","updURL":"https://thepiratebay.org/static/dump/csv/torrent_dump_full.csv.gz","updLast":"2003-01-01T00:00:00.000Z","updType":"notify","updInt":10,"keepDL":true,"updStat":["complete","",""],"_id":"3"} 5 | {"type":"gen","sysTray":true,"logToFile":true,"useDHT":true,"supMsg":"2018-06-01T05:21:44.285Z","confVer":"1.0","_id":"4"} 6 | {"type":"etc","thmURL":"https://github.com/m4heshd/offlinebay-themes","appUpdURL":"https://raw.githubusercontent.com/m4heshd/ob_release/master/release.json","_id":"5"} 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "offlinebay", 3 | "version": "2.0.0", 4 | "description": "Offline version of The Pirate Bay", 5 | "main": "app/main.js", 6 | "scripts": { 7 | "start": "electron .", 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "clean": "node app/reset-db.js", 10 | "dist-mac": "electron-packager . OfflineBay --overwrite --platform=darwin --arch=x64 --icon=app/img/icon.icns --app-bundle-id=com.m4heshd.offlinebay --prune=true --out=dist --app-copyright=\"Copyright (C) 2018 m4heshd\"", 11 | "dist-win": "electron-packager . OfflineBay --overwrite --platform=win32 --arch=ia32 --icon=app/img/icon.ico --prune=true --out=dist --win32metadata.FileDescription=\"OfflineBay\" --win32metadata.ProductName=\"OfflineBay by m4heshd\" --app-copyright=\"Copyright (C) 2018 m4heshd\"", 12 | "dist-linux-64": "electron-packager . OfflineBay --overwrite --platform=linux --arch=x64 --icon=app/img/icon.png --prune=true --out=dist --app-copyright=\"Copyright (C) 2018 m4heshd\"", 13 | "dist-linux-32": "electron-packager . OfflineBay --overwrite --platform=linux --arch=ia32 --icon=app/img/icon.png --prune=true --out=dist --app-copyright=\"Copyright (C) 2018 m4heshd\"" 14 | }, 15 | "author": "m4heshd", 16 | "license": "MIT", 17 | "dependencies": { 18 | "adm-zip": "0.4.9", 19 | "bittorrent-dht": "8.2.0", 20 | "bittorrent-tracker": "9.7.0", 21 | "bootstrap": "4.0.0", 22 | "jquery": "3.3.1", 23 | "material-design-iconic-font": "2.2.0", 24 | "moment": "2.22.1", 25 | "nedb": "1.8.0", 26 | "papaparse": "4.3.7", 27 | "popper.js": "1.14.1", 28 | "request": "2.85.0", 29 | "rimraf": "2.6.2" 30 | }, 31 | "devDependencies": { 32 | "electron": "1.8.4", 33 | "electron-packager": "12.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/data/themes/assets/carbondark/custom.css: -------------------------------------------------------------------------------- 1 | body, .modal-content { 2 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAABGCAAAAABURb1YAAAFnklEQVR4AWWXCY4cwXbE6h4JMhCvMPc/ov+3Ox9QsLCGpKlFHILd+UQ559AGDkBSFXWmomonqkL/XuUcIPUcONjHCJyE36KaqNiaRnVCG9TGpgIU9hIfohCBVGj24ammo04lU3Fy0NZDAyZi5AElHjgEGlRbG1WjraIt7TmADQkAaoEHsOXAAaeqat9RVTqjosyEH5W+DQCQt8BzIJMfXJI3e5tGRefSlRn3NrO3scnvNl26FN5RITGNfjm3GM/+FhQgnlQeImC+dEm8rzHVTNGL9EADVsTAIfGJBziRy7RlmcZU1IYpHIj+4JrWym8/XSwzompmFNVOFdTOAAfs61KZ6eXcp57fykQUOxNBmTesz2HpCnCg3UV/bA7VRpWGNqKtrYLTs0ilCjgxiZxjeLC5dDPBFn4Pn2ja6BRUL10DTgET4Focq8rn8casxV4E5tIlZvytqtfivCOKZrqc38G7egnkXecyPZdzubc57VtUnbfL+a0u3b1N3d9Mci7cKocHoh3xihLQ6fXUlgTOOfVkXSUFINoWHhqxE23RTmCqtoEJaITkIrUCkU9fnqpqm+40VSXS3CLaSzfGBb4rPuOSfN0ylLve6/jpcrbL2dTlnOdv5AamKjLvLN1XruMNS/cul/hx8twKpKYRaE2DOp8+c+lW7uVXVQ7Vx0x1G3HXjNocsHCac7NbWc6egwJVHu2kRTVNC2pqv2U4XNsv5/yWJlV9+NJ1C7xluInlcr7rK+Kg+qy7onQqgnbi7UHcS3qXjdw3bO5tumycYlVssMI5EXXpRnatPK362KodtVU60cm+eQT10sVLFwJYlVaf7UGSoi5dQF3DvnTX3UvXTPO8quKH7jsXhm/PxfKWZXqBM5sS33kmKtC31Q05fI095i5IPpyzRXqSy1Sr17p1F7NE8vMtnNbLpVVtk8c0OrfF0sKxHnIpYgHW4puMomTz8mgmrSjpvrxhX97l7LqLpl7bW9BH9/MNzF8XwRqL8/m7c30eFYV5VX3QTquK06XbLt16F4ks+8Z9m6gPtpcuk3OvibCekng4lzMHi9MoqZmKj9tdbQCbc7bcEA+oEJduRNNL1waf1tuI9MDBUK8t3k9uFz3rrqadO9Ln9TL/ezlwDvky3TW7ZmXfpc77TN0yhOXnP939LvVDV9s+tgLTm0TqYd1lea3PRFuVjLa562HdPbCfascGrvO3DMeIATVqVRO8vfHrbupdfo1dzlXUdkVLZsS1mLPG3rWcl+4B1l1537h0VXzU1tyLG7cCu+LSrSqo06hocy3WCcil65qHDbuEYzQN2HpT3jFt9LEFvHTP1nxbXOEkbhkKmd+6BTfVZ/I143z/Fyz6Za92o0I7I6gzfeYAHPwy/Rb43L9TLtMt8CtwW1xvQvbR1g9n7qJRNN1kdxcdH7cM58Szxq679MdLOhE72t6VNEpHHkzwRyjBcGth+dE99pIkVbWaEWmvnQ8Yl7Mp59zpRWXdNo6odrO754Zzvxf/010nP7onb1TR+bgbVPB9PcADJvH/043Kvk0UzLRxQ6UiaeTAcyxYl2lyrrvZv8v2wE7U1uVcST0821jJeIDKWuyxiqnuko7q3+zJYXJ46nV3D67EyL+lQ9F2qkr+3kUafQpwYE8T329p6y7OX1TF97fQv/c+l87zj7Hsgpig4qRLd9fMPs74rLGyCzRbhmVqpy7nqvPmTD1gvKe7b2U8u1QzmtuDuEtbgT3c8QBJveo6cueFkUwv59nVmfFw1jl4/unuOT9qERV76ers8n23z/c8/Xy7a4lwwLplmGusbRp33Up13+ZDN3KPxNVMhY62vUQyRTse6+cKHtZYjIfDh260Cjbb3Vy6R8H8SMBzqSwrsB1UTacoknlnOb8V4JD9Njfpwz/fHjpBUeevcAtcl65sqXPX9D87RUTLc/dfOgAAAABJRU5ErkJggg==); 3 | } -------------------------------------------------------------------------------- /app/css/jquery-ui.min.css: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.12.1 - 2018-04-21 2 | * http://jqueryui.com 3 | * Includes: draggable.css, core.css, resizable.css, selectable.css, sortable.css 4 | * Copyright jQuery Foundation and other contributors; Licensed MIT */ 5 | 6 | .ui-draggable-handle{-ms-touch-action:none;touch-action:none}.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important;pointer-events:none}.ui-icon{display:inline-block;vertical-align:middle;margin-top:-.25em;position:relative;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-icon-block{left:50%;margin-left:-8px;display:block}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable{-ms-touch-action:none;touch-action:none}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-sortable-handle{-ms-touch-action:none;touch-action:none} -------------------------------------------------------------------------------- /app/reset-db.js: -------------------------------------------------------------------------------- 1 | console.log('Resetting config.db to initial state......'); 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | let records = '{"type":"win-state","maxed":false,"position":[null,null],"size":[1280,800],"_id":"0"}\n' + 7 | '{"type":"trackers","url":"https://newtrackon.com/api/stable","trackers":["http://tracker.tfile.me:80/announce","udp://tracker.torrent.eu.org:451/announce","udp://thetracker.org:80/announce","http://torrent.nwps.ws:80/announce","http://alpha.torrenttracker.nl:443/announce","http://0d.kebhana.mx:443/announce","udp://bt.xxx-tracker.com:2710/announce","udp://inferno.demonoid.pw:3418/announce","udp://tracker.justseed.it:1337/announce","udp://tracker.vanitycore.co:6969/announce","udp://open.stealth.si:80/announce","udp://tracker.coppersurfer.tk:6969/announce","udp://tracker.opentrackr.org:1337/announce","udp://tracker.cypherpunks.ru:6969/announce","udp://tracker.tiny-vps.com:6969/announce","http://retracker.mgts.by:80/announce","udp://retracker.lanta-net.ru:2710/announce","udp://public.popcorn-tracker.org:6969/announce","http://tracker.skyts.net:6969/announce","udp://ipv4.tracker.harry.lu:80/announce","http://tracker.city9x.com:2710/announce","udp://tracker.martlet.tk:6969/announce","http://retracker.telecom.by:80/announce","udp://9.rarbg.com:2710/announce"],"_id":"1"}\n' + 8 | '{"type":"search","rs_count":100,"smart":true,"inst":false,"useAC":true,"history":[],"_id":"2"}\n' + 9 | '{"type":"dump","updURL":"https://thepiratebay.org/static/dump/csv/torrent_dump_full.csv.gz","updLast":"2003-01-01T00:00:00.000Z","updType":"notify","updInt":10,"keepDL":true,"updStat":["complete","",""],"_id":"3"}\n' + 10 | '{"type":"gen","sysTray":true,"logToFile":true,"useDHT":true,"supMsg":"' + new Date().toISOString() +'","confVer":"1.0","_id":"4"}\n' + 11 | '{"type":"etc","thmURL":"https://github.com/m4heshd/offlinebay-themes","appUpdURL":"https://raw.githubusercontent.com/m4heshd/ob_release/master/release.json","_id":"5"}\n'; 12 | 13 | fs.writeFile(path.join(__dirname, 'data', 'config.db'), records, function (err) { 14 | err ? console.error(err + '\nFailed to reset the DB\n') : console.log('Task completed successfully'); 15 | }); 16 | -------------------------------------------------------------------------------- /app/css/theme.css: -------------------------------------------------------------------------------- 1 | /* Theme color variables 2 | -------------------------*/ 3 | :root{ 4 | --bodyTxt : #b4bfc3; 5 | --bodyBg : #1e2a31; 6 | --compClr : #2b3942; 7 | --compShadow1 : rgba(0,0,0,0.16); 8 | --compShadow2 : rgba(0,0,0,0.08); 9 | --txtFocusBg : #edeff0; 10 | --txtFocustxt : #22313a; 11 | --txtFocusPH : #354c5a; 12 | --btnTxtClr : #b4bfc3; 13 | --btnTxtHover : #fff; 14 | --btnBgHover : #138496; 15 | --btnFocusGlow : rgba(23, 162, 184, 0.5); 16 | --btnActive : #117a8b; 17 | --btnTxtDisable : #b4bfc3; 18 | --btnBgDisable : #4e4e4e; 19 | --btnBorderDisable : #17a2b8; 20 | --chkChecked : #edeff0; 21 | --chkUnchecked : rgba(177, 188, 192, 0.47); 22 | --tblHeadBottomBorder : #b4bfc3; 23 | --tblHeadHover: rgba(255, 255, 255, 0.01); 24 | --tblCellBorder : #30414a; 25 | --tblActiveRow : #22313a; 26 | --tblActiveRowHover: #19232a; 27 | --scrollBg : #24303a; 28 | --scrollBorder : #252525; 29 | --scrollThumb : #35464c; 30 | --mnuBtnBg: rgba(0, 0, 0, 0); 31 | --mnuBg: #edeff0; 32 | --mnuTxt: #4a606d; 33 | --mnuItemHover: rgba(0,0,0,.04); 34 | --mnuGrade1: rgba(255,0,0,0); 35 | --mnuGrade2: rgb(43, 57, 66); 36 | --mnuGlow: rgba(0,0,0,.175); 37 | --modalGlow: rgba(0,0,0,.5); 38 | --overlay: rgba(0,0,0,0.6); 39 | --olEffect: blur(2px); 40 | --olTxt: #b4bfc3; 41 | } -------------------------------------------------------------------------------- /app/js/jquery.dragbetter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jquery.dragbetter 0.1.3 3 | * 4 | * Author: Andrey Mikhaylov aka lolmaus 5 | * Email: lolmaus@gmail.com 6 | * 7 | * Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 8 | * 9 | * Based on jquery.draghover.js by William Meleyal (william.meleyal@gmail.com): https://gist.github.com/meleyal/3794126. 10 | * 11 | * Inspired by jquery.event.dragout by Dan Cork ([Firstname].[Lastname]@kickinteractive.net): https://github.com/dancork/jquery.event.dragout . 12 | * 13 | * Thanks to Ian Bytchek for support. 14 | * 15 | */ 16 | 17 | ;(function($){ 18 | 19 | "use strict"; 20 | 21 | $.event.special.dragbetterenter = { 22 | 23 | setup: function() { 24 | 25 | var 26 | self = this, 27 | $self = $(self); 28 | 29 | self.dragbetterCollection = []; 30 | 31 | $self.on('dragenter.dragbetterenter', function (event) { 32 | 33 | if (self.dragbetterCollection.length === 0) { 34 | $self.triggerHandler('dragbetterenter'); 35 | } 36 | 37 | self.dragbetterCollection.push(event.target); 38 | }); 39 | 40 | $self.on('drop.dragbetterenter dragend.dragbetterenter', function () { 41 | self.dragbetterCollection = []; 42 | $self.triggerHandler('dragbetterleave'); 43 | }); 44 | }, 45 | 46 | teardown: function() { 47 | $(this).off('.dragbetterenter'); 48 | } 49 | 50 | }; 51 | 52 | $.event.special.dragbetterleave = { 53 | 54 | setup: function() { 55 | 56 | if (!this.dragbetterCollection) 57 | throw "Triggered dragbetterleave without dragbetterenter. Do you listen to dragbetterenter?"; 58 | 59 | var 60 | self = this, 61 | $self = $(self); 62 | 63 | 64 | $self.on('dragleave.dragbetterleave', function (event) { 65 | 66 | // Timeout is needed to ensure that the leave event on the previous element 67 | // fires AFTER the enter event on the next element. 68 | setTimeout(function() { 69 | 70 | var currentElementIndex = self.dragbetterCollection.indexOf(event.target); 71 | if (currentElementIndex > -1) 72 | self.dragbetterCollection.splice(currentElementIndex, 1); 73 | 74 | if (self.dragbetterCollection.length === 0) { 75 | $self.triggerHandler('dragbetterleave'); 76 | } 77 | }, 1); 78 | }); 79 | }, 80 | 81 | teardown: function() { 82 | $(this).off('.dragbetterleave'); 83 | } 84 | 85 | }; 86 | 87 | })(window.jQuery); 88 | -------------------------------------------------------------------------------- /app/img/dots.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/main-functions/scrape.js: -------------------------------------------------------------------------------- 1 | const Tracker = require('bittorrent-tracker'); 2 | const DHT = require('bittorrent-dht'); 3 | const path = require('path'); 4 | const Datastore = require('nedb'); 5 | 6 | process.on('uncaughtException', function (error) { 7 | console.log(error); 8 | process.send(['scrape-failed', 'general']); //mainWindow.webContents.send('scrape-failed', 'general'); 9 | }); 10 | 11 | let args = process.argv.slice(2); 12 | let hash = args[0]; 13 | let isDHT = (args[1] === 'true'); 14 | let trackers = []; 15 | 16 | let config = new Datastore({ 17 | filename: path.join(process.cwd(), 'data', 'config.db'), 18 | autoload: true 19 | }); 20 | 21 | getTrackers().then(function (trcks) { 22 | if (!trcks || !trcks.length) { 23 | process.send(['scrape-warn', 'empty']); //mainWindow.webContents.send('scrape-warn', 'empty'); 24 | } else { 25 | trackers = trcks; 26 | scrapeTrackers(); 27 | } 28 | if (isDHT) { 29 | scrapeDHT(); 30 | } 31 | }).catch(function (err) { 32 | process.send(['scrape-warn', 'db']); //mainWindow.webContents.send('scrape-warn', 'db'); 33 | console.log(err); 34 | if (isDHT) { 35 | scrapeDHT(); 36 | } 37 | }); 38 | 39 | // Get trackers list from DB 40 | function getTrackers() { 41 | return new Promise((resolve, reject) => { 42 | config.findOne({type: 'trackers'}, function (err, trck) { 43 | if (!err && trck) { 44 | resolve(trck.trackers); 45 | } else { 46 | console.log(err); 47 | reject(); 48 | } 49 | }) 50 | }); 51 | } 52 | 53 | function scrapeDHT() { 54 | let dht = new DHT(); 55 | let timer; 56 | 57 | dht.listen(20000); 58 | 59 | dht.on('peer', function () { 60 | if (timer){ 61 | process.send(['scrape-update-DHT', 0]); //mainWindow.webContents.send('scrape-update-DHT'); 62 | clearTimeout(timer); 63 | timer = setTimeout(function () { 64 | dht.destroy(); 65 | }, 5000); 66 | } 67 | }); 68 | 69 | dht.lookup(hash); 70 | 71 | timer = setTimeout(function () { 72 | dht.destroy(); 73 | }, 5000); 74 | 75 | } 76 | 77 | function scrapeTrackers() { 78 | let opts = { 79 | infoHash: hash, 80 | announce: trackers, 81 | peerId: new Buffer('01234567890123456789'), 82 | port: 6881 83 | }; 84 | 85 | let client = new Tracker(opts); 86 | 87 | client.scrape(); 88 | 89 | client.on('scrape', function (data) { 90 | process.send(['scrape-update', data]); //mainWindow.webContents.send('scrape-update', data); 91 | }).on('error', function (err) { 92 | console.log(err); 93 | }); 94 | } -------------------------------------------------------------------------------- /app/js/WndMain.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | 3 | /* Search bar animations 4 | ----------------------------*/ 5 | $(".top-search__input").on("focus", function () { 6 | $(".top-search").addClass("top-search--focused"); 7 | }).on("blur", function () { 8 | let a = $(this).val(); 9 | !a.length > 0 && $(".top-search").removeClass("top-search--focused"); 10 | }); 11 | $(".top-search").on("click", ".top-search__reset", function () { 12 | $(".top-search").removeClass("top-search--focused"); 13 | $(".top-search__input").val(""); 14 | }); 15 | 16 | /* Menu dropdown animations 17 | ----------------------------*/ 18 | $('.dropdown').on('show.bs.dropdown', function () { 19 | $(this).find('.dropdown-menu').first().stop(true, true).css({ 20 | opacity: 1, 21 | transition: 'opacity 0.2s' 22 | }).slideDown(200); 23 | }).on('hide.bs.dropdown', function () { 24 | $(this).find('.dropdown-menu').first().stop(true, true).css({ 25 | opacity: 0, 26 | transition: 'opacity 0.2s' 27 | }).slideUp(200); 28 | }); 29 | 30 | /* Modals 31 | --------------------*/ 32 | /* Blur effect */ 33 | let mdls = $('.modal'); 34 | mdls.on('show.bs.modal', function () { 35 | $('.body-container').css('filter', 'var(--olEffect)'); 36 | }); 37 | mdls.on('hide.bs.modal', function () { 38 | $('.body-container').css('filter', ''); 39 | }); 40 | /* Draggable Modal */ 41 | $('.mdl-drag .modal-content').resizable({ 42 | //alsoResize: ".modal-dialog", 43 | minHeight: 300, 44 | minWidth: 300 45 | }); 46 | $('.mdl-drag').draggable({ 47 | handle: '.modal-titlebar' 48 | }); 49 | 50 | /* Data table functions 51 | ----------------------------*/ 52 | /* tblMain */ 53 | $("#tblMainBody").on('click', 'tr', function () { 54 | if (!$(this).hasClass('active')) { 55 | $('#tblMain .active').removeClass('active'); 56 | $(this).addClass('active'); 57 | $('.imgDots').css({ 58 | visibility: 'hidden' 59 | }); 60 | $('#pnlSeeds').css({ 61 | visibility: 'hidden' 62 | }); 63 | } 64 | }); 65 | function resizeHeader() { 66 | $('th[data-type="date"]').width($('#hdrDate').width()); 67 | $('th[data-type="name"]').width($('#hdrName').width()); 68 | $('th[data-type="size"]').width($('#hdrSize').width()); 69 | $('#tblMain').css('margin-top', '-' + $('#hdrDate').css('height')); 70 | } 71 | new ResizeObserver(resizeHeader).observe(hdrDate); 72 | 73 | /* tblTrackers */ 74 | $("#tblTrackers").on('click', 'tr', function () { 75 | if (!$(this).hasClass('active')) { 76 | $('#tblTrackers .active').removeClass('active'); 77 | $(this).addClass('active'); 78 | } 79 | }); 80 | 81 | }); -------------------------------------------------------------------------------- /app/img/load.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/main-functions/upd-dump.js: -------------------------------------------------------------------------------- 1 | const request = require('request'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | // Keep the application from crashing on unexpected errors 6 | process.on('uncaughtException', function (error) { 7 | console.log(error); 8 | process.send(['upd-dump-failed', ['general', type]]); //mainWindow.webContents.send('upd-dump-failed', ['general', type]); 9 | }); 10 | 11 | let args = process.argv.slice(2); 12 | let type = args[0]; 13 | let down_url = args[1]; 14 | let targetPath = path.join(process.cwd(), 'data', 'downloads', 'torrent_dump_full.csv.gz'); 15 | let tstamp = new Date().toString(); 16 | 17 | // Download the dump file from provided URL 18 | function downloadDump(down_url, targetPath) { 19 | let received_bytes = 0; 20 | let total_bytes = 0; 21 | let goodFile = true; 22 | 23 | ensureDlDir(targetPath); 24 | 25 | let out = fs.createWriteStream(targetPath) 26 | .on('error', function (err) { 27 | console.log(err); 28 | process.send(['upd-dump-failed', ['file', type]]); //mainWindow.webContents.send('upd-dump-failed', ['file', type]); 29 | }) 30 | .on('open', function () { 31 | 32 | let req = request({ 33 | method: 'GET', 34 | uri: down_url 35 | }); 36 | 37 | req.pipe(out); 38 | 39 | req.on('response', function (data) { 40 | if((data.headers['content-type'].split('/')[0]) !== 'application'){ 41 | process.send(['upd-dump-failed', ['content', type]]); //mainWindow.webContents.send('upd-dump-failed', ['content', type]); 42 | goodFile = false; 43 | req.abort(); 44 | } 45 | tstamp = data.headers['last-modified']; 46 | total_bytes = parseInt(data.headers['content-length']); 47 | }); 48 | req.on('data', function (chunk) { 49 | received_bytes += chunk.length; 50 | let progress = Math.round((received_bytes * 100) / total_bytes); 51 | process.send(['upd-dump-update', [progress, type]]); //mainWindow.webContents.send('upd-dump-update', [progress, type]); 52 | }); 53 | req.on('error', function (err) { 54 | console.log(err); 55 | process.send(['upd-dump-failed', ['download', type]]); //mainWindow.webContents.send('upd-dump-failed', ['download', type]); 56 | }); 57 | 58 | }) 59 | .on('finish', function () { 60 | out.close(); 61 | if (goodFile) { 62 | let final = fs.openSync(targetPath, 'r+'); 63 | let dTstamp = new Date(tstamp); 64 | fs.futimesSync(final, dTstamp, dTstamp); // Set modified time of the downloaded file to the time from remote file 65 | fs.fsyncSync(final); // This part is essential to ensure that file is completely written to the disk before extracting 66 | fs.closeSync(final); 67 | process.send(['upd-dump-success', [targetPath, tstamp]]); //mainWindow.webContents.send('upd-dump-success', [targetPath, tstamp]); 68 | process.exit(0); 69 | } 70 | }); 71 | } 72 | 73 | // Make sure the download directory is available before downloading 74 | function ensureDlDir(filePath) { 75 | let dirname = path.dirname(filePath); 76 | if (fs.existsSync(dirname)) { 77 | return true; 78 | } 79 | ensureDlDir(dirname); 80 | fs.mkdirSync(dirname); 81 | } 82 | 83 | downloadDump(down_url, targetPath); -------------------------------------------------------------------------------- /app/WndSplash.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 83 | 84 | 85 | 88 |
89 |
90 | 91 | 92 | 93 | 94 | 95 | 96 |
97 |
98 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /app/data/themes/themes.db: -------------------------------------------------------------------------------- 1 | {"name":"default","title":"Default","applied":true,"palette":{"bodyTxt":"#b4bfc3","bodyBg":"#1e2a31","compClr":"#2b3942","compShadow1":"rgba(0,0,0,0.16)","compShadow2":"rgba(0,0,0,0.08)","txtFocusBg":"#edeff0","txtFocustxt":"#22313a","txtFocusPH":"#354c5a","btnTxtClr":"#b4bfc3","btnTxtHover":"#fff","btnBgHover":"#138496","btnFocusGlow":"rgba(23, 162, 184, 0.5)","btnActive":"#117a8b","btnTxtDisable":"#b4bfc3","btnBgDisable":"#4e4e4e","btnBorderDisable":"#17a2b8","chkChecked":"#edeff0","chkUnchecked":"rgba(177, 188, 192, 0.47)","tblHeadBottomBorder":"#b4bfc3","tblHeadHover":"rgba(255, 255, 255, 0.01)","tblCellBorder":"#30414a","tblActiveRow":"#22313a","tblActiveRowHover":"#19232a","scrollBg":"#24303a","scrollBorder":"#252525","scrollThumb":"#35464c","mnuBtnBg":"rgba(0, 0, 0, 0)","mnuBg":"#edeff0","mnuTxt":"#4a606d","mnuItemHover":"rgba(0,0,0,.04)","mnuGrade1":"rgba(255,0,0,0)","mnuGrade2":"rgb(43, 57, 66)","mnuGlow":"rgba(0,0,0,.175)","modalGlow":"rgba(0,0,0,.5)","overlay":"rgba(0,0,0,0.6)","olEffect":"blur(2px)","olTxt":"#b4bfc3"},"_id":"0"} 2 | {"name":"katdark","title":"KAT Dark","applied":false,"palette":{"bodyTxt":"#ffdd7d","bodyBg":"#594c2d","compClr":"#252525","compShadow1":"rgba(0,0,0,0.16)","compShadow2":"rgba(0,0,0,0.08)","txtFocusBg":"#ffdd7d","txtFocustxt":"#252525","txtFocusPH":"#354c5a","btnTxtClr":"#ffdd7d","btnTxtHover":"#252525","btnBgHover":"#ffdd7d","btnFocusGlow":"rgba(255, 221, 125, 0.41)","btnActive":"#c1a556","btnTxtDisable":"#ffffff","btnBgDisable":"#4e4e4e","btnBorderDisable":"#17a2b8","chkChecked":"#ffdd7d","chkUnchecked":"#ffdd7d","tblHeadBottomBorder":"#ffdd7d","tblHeadHover":"rgba(255, 255, 255, 0.01)","tblCellBorder":"#594c2d","tblActiveRow":"#332d1f","tblActiveRowHover":"#403723","scrollBg":"#252525","scrollBorder":"#252525","scrollThumb":"#8c7a48","mnuBtnBg":"rgba(0, 0, 0, 0)","mnuBg":"#ffdd7d","mnuTxt":"#252525","mnuItemHover":"rgba(0,0,0,.04)","mnuGrade1":"rgba(255,0,0,0)","mnuGrade2":"#252525","mnuGlow":"rgba(0,0,0,.175)","modalGlow":"rgba(0,0,0,.5)","overlay":"rgba(0,0,0,0.6)","olEffect":"blur(2px)","olTxt":"#ffdd7d"},"_id":"0ahqLqhTDi7k0dWe"} 3 | {"name":"carbondark","title":"Carbon Dark","applied":false,"palette":{"bodyTxt":"#b4bfc3","bodyBg":"#1b1b1b","compClr":"#333333","compShadow1":"rgba(0,0,0,0.16)","compShadow2":"rgba(0,0,0,0.08)","txtFocusBg":"#171717","txtFocustxt":"#b4bfc3","txtFocusPH":"#9c5511","btnTxtClr":"#b4bfc3","btnTxtHover":"#fff","btnBgHover":"#9c5511","btnFocusGlow":"rgba(227, 124, 26, 0.12)","btnActive":"#633408","btnTxtDisable":"#b4bfc3","btnBgDisable":"#420e0e","btnBorderDisable":"#420e0e","chkChecked":"#edeff0","chkUnchecked":"rgba(177, 188, 192, 0.47)","tblHeadBottomBorder":"#b4bfc3","tblHeadHover":"rgba(255, 255, 255, 0.08)","tblCellBorder":"#252525","tblActiveRow":"#292929","tblActiveRowHover":"#1e1e1e","scrollBg":"#333333","scrollBorder":"#252525","scrollThumb":"#252525","mnuBtnBg":"rgba(0, 0, 0, 0)","mnuBg":"#edeff0","mnuTxt":"#1e1e1e","mnuItemHover":"rgba(0, 0, 0, 0.1)","mnuGrade1":"rgba(255,0,0,0)","mnuGrade2":"rgba(0, 0, 0, 0)","mnuGlow":"rgba(0,0,0,.175)","modalGlow":"rgba(0,0,0,.5)","overlay":"rgba(0,0,0,0.6)","olEffect":"blur(2px)","olTxt":"#b4bfc3"},"_id":"Yf7czmuw5UoiUuKD"} 4 | {"name":"tpblight","title":"TPB Light","applied":false,"palette":{"bodyTxt":"#000000","bodyBg":"#e8e8e8","compClr":"#9e6943","compShadow1":"rgba(0,0,0,0.16)","compShadow2":"rgba(0,0,0,0.08)","txtFocusBg":"#ffffff","txtFocustxt":"#000000","txtFocusPH":"#354c5a","btnTxtClr":"#000000","btnTxtHover":"#fff","btnBgHover":"#b97f55","btnFocusGlow":"rgba(132, 57, 4, 0.39)","btnActive":"#865838","btnTxtDisable":"#b4bfc3","btnBgDisable":"#4e4e4e","btnBorderDisable":"#17a2b8","chkChecked":"#9e6943","chkUnchecked":"#9e6943","tblHeadBottomBorder":"#000000","tblHeadHover":"#b97f55","tblCellBorder":"#ffffff","tblActiveRow":"#d2b9a6","tblActiveRowHover":"#b97f55","scrollBg":"#b9a495","scrollBorder":"#252525","scrollThumb":"#9e6943","mnuBtnBg":"rgba(0, 0, 0, 0)","mnuBg":"#b97f55","mnuTxt":"#e8e8e8","mnuItemHover":"rgba(0,0,0,.04)","mnuGrade1":"rgba(255,0,0,0)","mnuGrade2":"#9e6943","mnuGlow":"rgba(0,0,0,.175)","modalGlow":"rgba(0,0,0,.5)","overlay":"rgba(0,0,0,0.6)","olEffect":"blur(2px)","olTxt":"#e8e8e8"},"_id":"vT9ETZpivwi9hvmw"} 5 | -------------------------------------------------------------------------------- /app/main-functions/deprecated/import-dumo-method-2.js: -------------------------------------------------------------------------------- 1 | /* A bit faster than method 1. Too much garbage collection. Child process will be alive for too long. 2 | Line by line method is only needed for fast-csv */ 3 | 4 | const fs = require('fs'); 5 | const es = require('event-stream'); 6 | const csv = require('fast-csv'); 7 | const path = require('path'); 8 | require('v8').setFlagsFromString('--harmony'); //To fix the regex Lookbehind issue 9 | 10 | process.on('uncaughtException', function (error) { 11 | console.log(error); 12 | process.send(['import-failed', 'general']); //mainWindow.webContents.send('import-failed', 'general'); 13 | }); 14 | 15 | let args = process.argv.slice(2); 16 | let filePath = args[0]; 17 | 18 | let lineNr = 0; 19 | 20 | importDump(filePath); 21 | 22 | function importDump(filePath) { 23 | let totalLines = 0; 24 | 25 | countFileLines(filePath).then(function (count) { 26 | console.log('Total line count : ', count); 27 | totalLines = count; 28 | startImport(); 29 | }).catch(function (err) { 30 | process.send(['import-failed', 'read']);// mainWindow.webContents.send('import-failed', 'read'); 31 | console.log(err); 32 | }); 33 | 34 | function startImport() { 35 | 36 | fs.truncate(path.join(process.cwd(), 'data', 'stage.csv'), 0, function (err) { 37 | let stage = fs.createWriteStream(path.join(process.cwd(), 'data', 'stage.csv'), { 38 | flags: 'a' 39 | }).on('error', function (err) { 40 | stage.close(); 41 | console.log(err); 42 | process.send(['import-failed', 'temp']);// mainWindow.webContents.send('import-failed', 'temp'); 43 | }).on('open', function () { 44 | let s = fs.createReadStream(filePath) 45 | .pipe(es.split()) 46 | .pipe(es.mapSync(function (line) { 47 | 48 | s.pause(); 49 | 50 | lineNr += 1; 51 | 52 | let formattedLine = line.replace(/(?!";)(? { 93 | let lineCount = 1; 94 | fs.createReadStream(filePath) 95 | .on("data", (buffer) => { 96 | let idx = -1; 97 | lineCount--; 98 | do { 99 | idx = buffer.indexOf(10, idx + 1); 100 | lineCount++; 101 | } while (idx !== -1); 102 | }).on("end", () => { 103 | resolve(lineCount); 104 | }).on("error", reject); 105 | }); 106 | } -------------------------------------------------------------------------------- /app/main-functions/search.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const papa = require('papaparse'); 4 | 5 | process.on('uncaughtException', function (error) { 6 | console.log(error); 7 | process.send(['search-failed', 'general']); //mainWindow.webContents.send('search-failed', 'general'); 8 | }); 9 | 10 | let args = process.argv.slice(2); 11 | let query = args[0]; 12 | let count = parseInt(args[1]); 13 | let smart = (args[2] === 'true'); 14 | let inst = (args[3] === 'true'); 15 | count = count > 0 ? count : 100; 16 | count = count > 10000 ? 10000 : count; 17 | 18 | let i = 1; 19 | let reg; 20 | let stream; 21 | let result = []; 22 | 23 | // Takes a database record and returns a search result object to be displayed 24 | function resultObjectTemplate(record) { 25 | let size = formatBytes(record['SIZE(BYTES)'], 1); 26 | let row = '' + record['#ADDED'] + 27 | '' + record['HASH(B64)'] + 28 | '' + record['NAME'] + 29 | '' + size + ''; 30 | return { 31 | added: record['#ADDED'], 32 | name: record['NAME'], 33 | size: size, 34 | markup: row 35 | }; 36 | } 37 | 38 | // Returns true of false for DB record based on regular search terms 39 | function regularCondition(record) { 40 | return record['NAME'] && record['NAME'].toUpperCase().indexOf(query.toUpperCase()) > -1; 41 | } 42 | 43 | // Returns true of false for DB record based on smart search terms 44 | function smartCondition(record) { 45 | return reg.test(record['NAME']); 46 | } 47 | 48 | // Processes and searches each chunk of DB as it is read into memory 49 | function procData(results, parser) { 50 | var condition = smart ? smartCondition : regularCondition; 51 | 52 | for (let c = 0; c < results.data.length; c++) { 53 | let record = results.data[c]; 54 | 55 | if (condition(record)) { 56 | if (i > count) { 57 | parser.abort(); 58 | stream.close(); 59 | break; 60 | } else { 61 | result.push(resultObjectTemplate(record)); 62 | i++; 63 | } 64 | } 65 | } 66 | 67 | if (inst && result.length > 0) { 68 | process.send(['search-update', result]); //mainWindow.webContents.send('search-update', result); 69 | result = []; 70 | } 71 | } 72 | 73 | 74 | let finSearch = function () { 75 | if (inst) { 76 | process.send(['search-success-inst', { 77 | resCount: --i 78 | }]); //mainWindow.webContents.send('search-success-inst'); 79 | } else { 80 | process.send(['search-success', { 81 | resCount: --i, 82 | results: result 83 | }]); //mainWindow.webContents.send('search-success'); 84 | } 85 | 86 | process.exit(0); 87 | }; 88 | 89 | 90 | function search() { 91 | 92 | stream = fs.createReadStream(path.join(process.cwd(), 'data', 'processed.csv')) 93 | .once('open', function () { 94 | papa.parse(stream, { 95 | delimiter: ';', 96 | escapeChar: '\\', 97 | header: true, 98 | chunk: procData, 99 | complete: finSearch, 100 | error: function (error) { 101 | process.send(['search-failed', 'process']); //mainWindow.webContents.send('search-failed', 'process'); 102 | console.log(error); 103 | } 104 | }); 105 | }) 106 | .on('error', function (err) { 107 | process.send(['search-failed', 'read']); //mainWindow.webContents.send('search-failed', 'read'); 108 | console.log(err); 109 | }); 110 | 111 | } 112 | 113 | 114 | function startSearch(text) { 115 | reg = new RegExp(regexify(text), 'i'); 116 | search(); 117 | } 118 | 119 | function escapeRegExp(text) { 120 | return text.replace(/[-[\]{}()*+?.,\\/^$|#\s]/g, '\\$&'); 121 | } 122 | 123 | function regexify(text) { 124 | text = text.trim().replace(/(\s+)/g, ' '); 125 | let words = text.split(' '); 126 | let final = ''; 127 | words.forEach(function (item) { 128 | final += '(?=.*' + escapeRegExp(item) + ')'; 129 | }); 130 | return final; 131 | } 132 | 133 | function formatBytes(bytes, decimals) { 134 | if (bytes === 0) return '0 Bytes'; 135 | let k = 1024, 136 | dm = decimals || 2, 137 | sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], 138 | i = Math.floor(Math.log(bytes) / Math.log(k)); 139 | return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; 140 | } 141 | 142 | startSearch(query); -------------------------------------------------------------------------------- /app/main-functions/deprecated/import-dump-method-1.js: -------------------------------------------------------------------------------- 1 | /* Seems to be extremely slow. Too much garbage collection. Line by line method is only needed for fast-csv */ 2 | 3 | const fs = require('fs-extra'); 4 | const path = require('path'); 5 | const csv = require('fast-csv'); 6 | const readline = require('readline'); 7 | require('v8').setFlagsFromString('--harmony'); //To fix the regex Lookbehind issue 8 | 9 | process.on('uncaughtException', function (error) { 10 | console.log(error); 11 | process.send(['import-failed', 'general']); //mainWindow.webContents.send('import-failed', 'general'); 12 | }); 13 | 14 | let args = process.argv.slice(2); 15 | let filePath = args[0]; 16 | 17 | console.log(filePath); 18 | 19 | importDump(filePath); 20 | 21 | function importDump(filePath) { 22 | let totalLines = 0; 23 | 24 | countFileLines(filePath).then(function (count) { 25 | console.log(count); 26 | totalLines = count; 27 | startImport(); 28 | }).catch(function (err) { 29 | process.send(['import-failed', 'read']);// mainWindow.webContents.send('import-failed', 'read'); 30 | console.log(err); 31 | }); 32 | 33 | function startImport() { 34 | try { 35 | try { 36 | fs.truncateSync(path.join(process.cwd(), 'data', 'stage.csv'), 0); 37 | } catch (error) { 38 | console.log('Staging file not found'); 39 | } 40 | 41 | let stage = fs.createWriteStream(path.join(process.cwd(), 'data', 'stage.csv'), { 42 | flags: 'a' 43 | }).on('error', function (err) { 44 | stage.close(); 45 | console.log(err); 46 | process.send(['import-failed', 'temp']);// mainWindow.webContents.send('import-failed', 'temp'); 47 | }).on('open', function () { 48 | // console.log('OPENED'); 49 | let curLine = 0; 50 | 51 | let lineReader = readline.createInterface({ 52 | input: fs.createReadStream(filePath) 53 | }); 54 | 55 | lineReader.on('line', function (line) { 56 | lineReader.pause(); 57 | // console.log('READ'); 58 | let formattedLine = line.replace(/(?!";)(? { 107 | let lineCount = 1; 108 | fs.createReadStream(filePath) 109 | .on("data", (buffer) => { 110 | let idx = -1; 111 | lineCount--; // Because the loop will run once for idx=-1 112 | do { 113 | idx = buffer.indexOf(10, idx + 1); 114 | lineCount++; 115 | } while (idx !== -1); 116 | }).on("end", () => { 117 | resolve(lineCount); 118 | }).on("error", reject); 119 | }); 120 | } -------------------------------------------------------------------------------- /app/img/update_trcks.svg: -------------------------------------------------------------------------------- 1 | 2 | download 3 | cloud,arrow,save,clone,sync,get,retrieve,download,form 4 | cc-by 5 | n8fyph 6 | -------------------------------------------------------------------------------- /app/js/clusterize.min.js: -------------------------------------------------------------------------------- 1 | /* Clusterize.js - v0.18.1 - 2018-01-02 2 | http://NeXTs.github.com/Clusterize.js/ 3 | Copyright (c) 2015 Denis Lukov; Licensed GPLv3 */ 4 | 5 | ;(function(q,n){"undefined"!=typeof module?module.exports=n():"function"==typeof define&&"object"==typeof define.amd?define(n):this[q]=n()})("Clusterize",function(){function q(b,a,c){return a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)}function n(b,a,c){return a.removeEventListener?a.removeEventListener(b,c,!1):a.detachEvent("on"+b,c)}function r(b){return"[object Array]"===Object.prototype.toString.call(b)}function m(b,a){return window.getComputedStyle?window.getComputedStyle(a)[b]: 6 | a.currentStyle[b]}var l=function(){for(var b=3,a=document.createElement("b"),c=a.all||[];a.innerHTML="\x3c!--[if gt IE "+ ++b+"]>=l&&!c.tag&&(c.tag=b[0].match(/<([^>\s/]*)/)[1].toLowerCase()),1>=this.content_elem.children.length&&(a.data=this.html(b[0]+b[0]+b[0])),c.tag||(c.tag=this.content_elem.children[0].tagName.toLowerCase()), 11 | this.getRowsHeight(b))},getRowsHeight:function(b){var a=this.options,c=a.item_height;a.cluster_height=0;if(b.length&&(b=this.content_elem.children,b.length)){var d=b[Math.floor(b.length/2)];a.item_height=d.offsetHeight;"tr"==a.tag&&"collapse"!=m("borderCollapse",this.content_elem)&&(a.item_height+=parseInt(m("borderSpacing",this.content_elem),10)||0);"tr"!=a.tag&&(b=parseInt(m("marginTop",d),10)||0,d=parseInt(m("marginBottom",d),10)||0,a.item_height+=Math.max(b,d));a.block_height=a.item_height*a.rows_in_block; 12 | a.rows_in_cluster=a.blocks_in_cluster*a.rows_in_block;a.cluster_height=a.blocks_in_cluster*a.block_height;return c!=a.item_height}},getClusterNum:function(){this.options.scroll_top=this.scroll_elem.scrollTop;return Math.floor(this.options.scroll_top/(this.options.cluster_height-this.options.block_height))||0},generateEmptyRow:function(){var b=this.options;if(!b.tag||!b.show_no_data_row)return[];var a=document.createElement(b.tag),c=document.createTextNode(b.no_data_text);a.className=b.no_data_class; 13 | if("tr"==b.tag){var d=document.createElement("td");d.colSpan=100;d.appendChild(c)}a.appendChild(d||c);return[a.outerHTML]},generate:function(b,a){var c=this.options,d=b.length;if(de&&g++;f=l&&"tr"==this.options.tag){var c=document.createElement("div");for(c.innerHTML=""+b+"
";b=a.lastChild;)a.removeChild(b);for(c=this.getChildNodes(c.firstChild.firstChild);c.length;)a.appendChild(c.shift())}else a.innerHTML=b},getChildNodes:function(b){b=b.children;for(var a=[],c=0,d=b.length;c 2 |
3 | OfflineBay 4 |
5 | OfflineBay by m4heshd 6 | 7 | 8 | Stable version: [**`2.0.0`**](https://github.com/m4heshd/offlinebay/releases/tag/2.0.0) 9 | 10 | OfflineBay is a free and open-source tool created to hold and parse copies of torrent archive dumps. So it allows you to search and download torrents offline. OfflineBay can parse CSV files with many torrenting related added features. The parsing is programmed to match the structure of dump files created by The Pirate Bay. But any group or organization can follow that same structure to create dump files to be used with OfflineBay. 11 | 12 | **[PLEASE FOLLOW THIS VIDEO TUTORIAL BEFORE YOU START USING OFFLINEBAY](https://www.youtube.com/watch?v=nNjW_OUv-hE)** 13 | 14 | OfflineBay Video 15 | 16 | ### DISCLAIMER: 17 | > **OfflineBay is Free and Open-source software licensed under [MIT License](LICENSE). This software is created to parse CSV dump files created by a third party and any of the data other than this software is not provided by m4heshd. Use the data at your own risk. m4heshd will not be responsible for any of the data acquired using dump files. It is strongly advised not to use OfflineBay for any copyright infringing activities. OfflineBay will not hide or protect you from authorities if you engage in such activities.** 18 | 19 | 20 | # Downloads 21 | 22 | OfflineBay is distributed as a portable application in favor of the majority. Download the corresponding package and extract the **ZIP** file. All of the packages should have an executable (double-clickable) along with some other files. File named `OfflineBay.exe` or `OfflineBay.app` or `OfflineBay` would be the executable file. 23 | 24 | **NOTE:** Linux users may need to declare the file as an executable before running - `chmod +x OfflineBay` 25 | 26 | **Why downloads are larger than the previous version?**
27 | Well, OfflineBay 2 is created using [Electron](https://electronjs.org) and NodeJS. So the disk footprint could be a [little concerning](https://github.com/electron/electron/issues/2003). There is no way around this issue. This is one small compromise for a lot of improvements. 28 | 29 | - [All releases (with source)](https://github.com/m4heshd/offlinebay/releases) 30 | - Windows (x86 & x64) - [Download](https://github.com/m4heshd/offlinebay/releases/download/2.0.0/OfflineBay-2.0.0-Windows.zip) 31 | - MacOS - [Download](https://github.com/m4heshd/offlinebay/releases/download/2.0.0/OfflineBay-2.0.0-MacOS.zip) 32 | - Linux (x64) - [Download](https://github.com/m4heshd/offlinebay/releases/download/2.0.0/OfflineBay-2.0.0-Linux-x64.zip) 33 | - Linux (x86) - [Download](https://github.com/m4heshd/offlinebay/releases/download/2.0.0/OfflineBay-2.0.0-Linux-x86.zip) 34 | 35 | **WARNING:** Since OfflineBay is now open-source you could stumble upon some modified versions of OfflineBay. If you get infected with malware by going off the proper channels, i won't take any blame for them. 36 | 37 | If you're looking for the older version of OfflineBay, Downloads are available [here](https://pirates-forum.org/Thread-Release-OfflineBay-1-0-2-Download-torrents-from-thePirateBay-offline). 38 | 39 | **How to remove OfflineBay?**
40 | I personally believe that none of the applications should leave any residue in the removal process. Unlike other portable apps OfflineBay will not leave anything behind once you hit delete on the application folder. It's valid for all platforms. Everything will be removed including Dump files, configurations and error logs. 41 | 42 | # Build OfflineBay yourself 43 | 44 | You will need [NodeJS](https://nodejs.org) and [NPM](https://www.npmjs.com/) (Usually packed with NodeJS) installed on your computer to build OfflineBay yourself. 45 | 46 | **NOTE:** When creating OfflineBay i almost managed to completely avoid native modules. But there's a single module named `bufferutil` that's required by another module. You may run into a few errors when building if you don't have platform build tools for `node-gyp`. You can simply ignore those errors since the module is an optional dependency. Also since OfflineBay practically has no native dependencies, it's possible to cross-build without any complications. But i cannot guarantee on the outcome. 47 | 48 | Clone this repository, `cd` to that directory and run `npm run clean` before everything. Then run any of the following commands to build OfflineBay. 49 | 50 | - Windows (x86 & x64) - `npm run dist-win` 51 | - MacOS - `npm run dist-mac` 52 | - Linux (x64) - `npm run dist-linux-64` 53 | - Linux (x86) - `npm run dist-linux-32` 54 | 55 | # Support OfflineBay project 56 | 57 | Involvement as a contributor by adding a few lines of code, fixing a bug, respond to issues, testing etc.. would be one of the most helpful methods you could support the project. If you're not a programmer but a creative person, you could definitely get involved in the GUI part of OfflineBay. [Follow this link to learn about theming for OfflineBay](https://github.com/m4heshd/offlinebay-themes). 58 | If you have some spare coins laying around, you could throw some this way to buy me a coffee.. 59 | 60 | - **BTC:** 1KpjcftYs8CnG7jVgmzSoKAVT7QGqisYBC 61 | - **ETH:** 0x1EE4130876B4f0aD2303a98787fd3746DD0C84F4 62 | - **BCH:** 1LsY4Ppvrfx21pzs7YaAC3ciLbdwUYj92q 63 | 64 | [![Donate to m4heshd](https://i.ibb.co/3vQTMts/paypal-donate-icon-7.png)](https://www.paypal.me/mpwk?locale.x=en_US) 65 | 66 | Or you can buy me a "ko-fi" by clicking this button 67 | 68 | [![ko-fi](https://i.ibb.co/QmQknmc/ko-fi.png)](https://ko-fi.com/m4heshd) 69 | 70 | # Got an Issue? 71 | 72 | [Follow this link](https://github.com/m4heshd/offlinebay/issues) to submit your issues. Remember to be descriptive when submitting issues. Also remember that issues area is not meant to ask for help. If you need help or you have any other questions regarding OfflineBay you can simply fire up a conversation on the [Suprbay thread](https://pirates-forum.org/Thread-Release-OfflineBay-v2-Open-source-and-No-more-Java-dependency). 73 | # Known issues 74 | 75 | **Linux:** 76 | 77 | - Splash window may have a [white background instead of transparency](https://github.com/electron/electron/issues/2170) [`unfixable/SW-HW dependent`] 78 | - DHT scraping may not work sometimes [`unable to trace the issue`] 79 | 80 | # Themes you say? 81 | 82 | You can create themes for OfflineBay just using CSS. [This repository](https://github.com/m4heshd/offlinebay-themes) is dedicated to OfflineBay themes. Visit there to learn more about creating themes. 83 | 84 | If you're a regular user, you can also follow the [same repository](https://github.com/m4heshd/offlinebay-themes) to download themes. 85 | 86 | # Changelog 87 | 88 | Complete Changelog is available [here](changelog.txt). 89 | 90 | # License 91 | OfflineBay is licensed under [MIT License](LICENSE). So you are allowed to use freely and modify the application. I will not be responsible for any outcome. Proceed with any action at your own risk. 92 | -------------------------------------------------------------------------------- /app/js/bootstrap-notify.min.js: -------------------------------------------------------------------------------- 1 | /* Project: Bootstrap Growl = v3.1.3 | Description: Turns standard Bootstrap alerts into "Growl-like" notifications. | Author: Mouse0270 aka Robert McIntosh | License: MIT License | Website: https://github.com/mouse0270/bootstrap-growl */ 2 | !function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t("object"==typeof exports?require("jquery"):jQuery)}(function(t){function e(e,i,n){var i={content:{message:"object"==typeof i?i.message:i,title:i.title?i.title:"",icon:i.icon?i.icon:"",url:i.url?i.url:"#",target:i.target?i.target:"-"}};n=t.extend(!0,{},i,n),this.settings=t.extend(!0,{},s,n),this._defaults=s,"-"==this.settings.content.target&&(this.settings.content.target=this.settings.url_target),this.animations={start:"webkitAnimationStart oanimationstart MSAnimationStart animationstart",end:"webkitAnimationEnd oanimationend MSAnimationEnd animationend"},"number"==typeof this.settings.offset&&(this.settings.offset={x:this.settings.offset,y:this.settings.offset}),this.init()}var s={element:"body",position:null,type:"info",allow_dismiss:!0,newest_on_top:!1,showProgressbar:!1,placement:{from:"top",align:"right"},offset:20,spacing:10,z_index:1031,delay:5e3,timer:1e3,url_target:"_blank",mouse_over:null,animate:{enter:"animated fadeInDown",exit:"animated fadeOutUp"},onShow:null,onShown:null,onClose:null,onClosed:null,icon_type:"class",template:''};String.format=function(){for(var t=arguments[0],e=1;e .progress-bar').removeClass("progress-bar-"+t.settings.type),t.settings.type=i[e],this.$ele.addClass("alert-"+i[e]).find('[data-notify="progressbar"] > .progress-bar').addClass("progress-bar-"+i[e]);break;case"icon":var n=this.$ele.find('[data-notify="icon"]');"class"==t.settings.icon_type.toLowerCase()?n.removeClass(t.settings.content.icon).addClass(i[e]):(n.is("img")||n.find("img"),n.attr("src",i[e]));break;case"progress":var a=t.settings.delay-t.settings.delay*(i[e]/100);this.$ele.data("notify-delay",a),this.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",i[e]).css("width",i[e]+"%");break;case"url":this.$ele.find('[data-notify="url"]').attr("href",i[e]);break;case"target":this.$ele.find('[data-notify="url"]').attr("target",i[e]);break;default:this.$ele.find('[data-notify="'+e+'"]').html(i[e])}var o=this.$ele.outerHeight()+parseInt(t.settings.spacing)+parseInt(t.settings.offset.y);t.reposition(o)},close:function(){t.close()}}},buildNotify:function(){var e=this.settings.content;this.$ele=t(String.format(this.settings.template,this.settings.type,e.title,e.message,e.url,e.target)),this.$ele.attr("data-notify-position",this.settings.placement.from+"-"+this.settings.placement.align),this.settings.allow_dismiss||this.$ele.find('[data-notify="dismiss"]').css("display","none"),(this.settings.delay<=0&&!this.settings.showProgressbar||!this.settings.showProgressbar)&&this.$ele.find('[data-notify="progressbar"]').remove()},setIcon:function(){"class"==this.settings.icon_type.toLowerCase()?this.$ele.find('[data-notify="icon"]').addClass(this.settings.content.icon):this.$ele.find('[data-notify="icon"]').is("img")?this.$ele.find('[data-notify="icon"]').attr("src",this.settings.content.icon):this.$ele.find('[data-notify="icon"]').append('Notify Icon')},styleURL:function(){this.$ele.find('[data-notify="url"]').css({backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)",height:"100%",left:"0px",position:"absolute",top:"0px",width:"100%",zIndex:this.settings.z_index+1}),this.$ele.find('[data-notify="dismiss"]').css({position:"absolute",right:"10px",top:"5px",zIndex:this.settings.z_index+2})},placement:function(){var e=this,s=this.settings.offset.y,i={display:"inline-block",margin:"0px auto",position:this.settings.position?this.settings.position:"body"===this.settings.element?"fixed":"absolute",transition:"all .5s ease-in-out",zIndex:this.settings.z_index},n=!1,a=this.settings;switch(t('[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])').each(function(){return s=Math.max(s,parseInt(t(this).css(a.placement.from))+parseInt(t(this).outerHeight())+parseInt(a.spacing))}),1==this.settings.newest_on_top&&(s=this.settings.offset.y),i[this.settings.placement.from]=s+"px",this.settings.placement.align){case"left":case"right":i[this.settings.placement.align]=this.settings.offset.x+"px";break;case"center":i.left=0,i.right=0}this.$ele.css(i).addClass(this.settings.animate.enter),t.each(Array("webkit","moz","o","ms",""),function(t,s){e.$ele[0].style[s+"AnimationIterationCount"]=1}),t(this.settings.element).append(this.$ele),1==this.settings.newest_on_top&&(s=parseInt(s)+parseInt(this.settings.spacing)+this.$ele.outerHeight(),this.reposition(s)),t.isFunction(e.settings.onShow)&&e.settings.onShow.call(this.$ele),this.$ele.one(this.animations.start,function(){n=!0}).one(this.animations.end,function(){t.isFunction(e.settings.onShown)&&e.settings.onShown.call(this)}),setTimeout(function(){n||t.isFunction(e.settings.onShown)&&e.settings.onShown.call(this)},600)},bind:function(){var e=this;if(this.$ele.find('[data-notify="dismiss"]').on("click",function(){e.close()}),this.$ele.mouseover(function(){t(this).data("data-hover","true")}).mouseout(function(){t(this).data("data-hover","false")}),this.$ele.data("data-hover","false"),this.settings.delay>0){e.$ele.data("notify-delay",e.settings.delay);var s=setInterval(function(){var t=parseInt(e.$ele.data("notify-delay"))-e.settings.timer;if("false"===e.$ele.data("data-hover")&&"pause"==e.settings.mouse_over||"pause"!=e.settings.mouse_over){var i=(e.settings.delay-t)/e.settings.delay*100;e.$ele.data("notify-delay",t),e.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",i).css("width",i+"%")}t<=-e.settings.timer&&(clearInterval(s),e.close())},e.settings.timer)}},close:function(){var e=this,s=parseInt(this.$ele.css(this.settings.placement.from)),i=!1;this.$ele.data("closing","true").addClass(this.settings.animate.exit),e.reposition(s),t.isFunction(e.settings.onClose)&&e.settings.onClose.call(this.$ele),this.$ele.one(this.animations.start,function(){i=!0}).one(this.animations.end,function(){t(this).remove(),t.isFunction(e.settings.onClosed)&&e.settings.onClosed.call(this)}),setTimeout(function(){i||(e.$ele.remove(),e.settings.onClosed&&e.settings.onClosed(e.$ele))},600)},reposition:function(e){var s=this,i='[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])',n=this.$ele.nextAll(i);1==this.settings.newest_on_top&&(n=this.$ele.prevAll(i)),n.each(function(){t(this).css(s.settings.placement.from,e),e=parseInt(e)+parseInt(s.settings.spacing)+t(this).outerHeight()})}}),t.notify=function(t,s){var i=new e(this,t,s);return i.notify},t.notifyDefaults=function(e){return s=t.extend(!0,{},s,e)},t.notifyClose=function(e){"undefined"==typeof e||"all"==e?t("[data-notify]").find('[data-notify="dismiss"]').trigger("click"):t('[data-notify-position="'+e+'"]').find('[data-notify="dismiss"]').trigger("click")}}); -------------------------------------------------------------------------------- /app/css/comp-override.css: -------------------------------------------------------------------------------- 1 | .radio, 2 | .checkbox { 3 | position: relative; 4 | display: block; 5 | } 6 | .radio label, 7 | .checkbox label { 8 | min-height: 18px; 9 | padding-left: 20px; 10 | margin-bottom: 0; 11 | font-weight: normal; 12 | cursor: pointer; 13 | } 14 | .radio input[type="radio"], 15 | .radio-inline input[type="radio"], 16 | .checkbox input[type="checkbox"], 17 | .checkbox-inline input[type="checkbox"] { 18 | position: absolute; 19 | margin-left: -20px; 20 | margin-top: 4px \9; 21 | } 22 | .radio-inline, 23 | .checkbox-inline { 24 | position: relative; 25 | display: inline-block; 26 | padding-left: 20px; 27 | margin-bottom: 0; 28 | vertical-align: middle; 29 | font-weight: normal; 30 | cursor: pointer; 31 | } 32 | .radio-inline + .radio-inline, 33 | .checkbox-inline + .checkbox-inline { 34 | margin-top: 0; 35 | margin-left: 10px; 36 | } 37 | input[type="radio"][disabled], 38 | input[type="checkbox"][disabled], 39 | input[type="radio"].disabled, 40 | input[type="checkbox"].disabled, 41 | fieldset[disabled] input[type="radio"], 42 | fieldset[disabled] input[type="checkbox"] { 43 | cursor: not-allowed; 44 | } 45 | .radio-inline.disabled, 46 | .checkbox-inline.disabled, 47 | fieldset[disabled] .radio-inline, 48 | fieldset[disabled] .checkbox-inline { 49 | cursor: not-allowed; 50 | } 51 | .radio.disabled label, 52 | .checkbox.disabled label, 53 | fieldset[disabled] .radio label, 54 | fieldset[disabled] .checkbox label { 55 | cursor: not-allowed; 56 | } 57 | 58 | .checkbox label, 59 | .radio label { 60 | display: block; 61 | padding-left: 30px; 62 | position: relative; 63 | } 64 | .checkbox, 65 | .radio, 66 | .checkbox-inline, 67 | .radio-inline { 68 | padding-top: 0; 69 | } 70 | .checkbox input[type=checkbox], 71 | .radio input[type=checkbox], 72 | .checkbox-inline input[type=checkbox], 73 | .radio-inline input[type=checkbox], 74 | .checkbox input[type=radio], 75 | .radio input[type=radio], 76 | .checkbox-inline input[type=radio], 77 | .radio-inline input[type=radio] { 78 | top: 0; 79 | left: 0; 80 | margin-left: 0; 81 | z-index: 1; 82 | cursor: pointer; 83 | opacity: 0; 84 | filter: alpha(opacity=0); 85 | margin-top: 0; 86 | transform: translate(0, -1.7px); 87 | } 88 | .checkbox.disabled, 89 | .radio.disabled, 90 | .checkbox-inline.disabled, 91 | .radio-inline.disabled { 92 | opacity: 0.6; 93 | filter: alpha(opacity=60); 94 | } 95 | .checkbox .input-helper:before, 96 | .radio .input-helper:before, 97 | .checkbox-inline .input-helper:before, 98 | .radio-inline .input-helper:before, 99 | .checkbox .input-helper:after, 100 | .radio .input-helper:after, 101 | .checkbox-inline .input-helper:after, 102 | .radio-inline .input-helper:after { 103 | position: absolute; 104 | content: ""; 105 | transition: all; 106 | transition-duration: 150ms; 107 | } 108 | .checkbox .input-helper:before, 109 | .radio .input-helper:before, 110 | .checkbox-inline .input-helper:before, 111 | .radio-inline .input-helper:before { 112 | left: 0; 113 | border-width: 2px; 114 | border-style: solid; 115 | box-shadow: 0 2px 2px 0 var(--compShadow1), 0 0 0 1px var(--compShadow2); 116 | } 117 | .checkbox input, 118 | .checkbox-inline input { 119 | width: 17px; 120 | height: 17px; 121 | } 122 | .checkbox input:checked + .input-helper:after, 123 | .checkbox-inline input:checked + .input-helper:after { 124 | opacity: 1; 125 | filter: alpha(opacity=100); 126 | } 127 | .checkbox .input-helper:before, 128 | .checkbox-inline .input-helper:before { 129 | top: 1px; 130 | width: 17px; 131 | height: 17px; 132 | border-radius: 2px; 133 | transform: translate(0, -1.7px); 134 | } 135 | .checkbox .input-helper:after, 136 | .checkbox-inline .input-helper:after { 137 | opacity: 0; 138 | filter: alpha(opacity=0); 139 | content: '\f26b'; 140 | font-family: 'Material-Design-Iconic-Font'; 141 | position: absolute; 142 | font-size: 12px; 143 | left: 3px; 144 | top: 1px; 145 | font-weight: bold; 146 | color: var(--bodyBg); 147 | transform: translate(0, -1.7px); 148 | } 149 | .checkbox .input-helper:before, 150 | .checkbox-inline .input-helper:before { 151 | border-color: var(--chkUnchecked); 152 | } 153 | .checkbox input:checked + .input-helper:before, 154 | .checkbox-inline input:checked + .input-helper:before { 155 | background-color: var(--chkChecked); 156 | border-color: var(--chkChecked); 157 | } 158 | .radio input, 159 | .radio-inline input { 160 | width: 19px; 161 | height: 19px; 162 | } 163 | .radio input:checked + .input-helper:after, 164 | .radio-inline input:checked + .input-helper:after { 165 | transform: scale(1); 166 | background: var(--chkChecked); 167 | } 168 | .radio .input-helper:before, 169 | .radio-inline .input-helper:before { 170 | top: -1px; 171 | width: 19px; 172 | height: 19px; 173 | border-radius: 50%; 174 | border-color: var(--chkUnchecked); 175 | } 176 | .radio .input-helper:after, 177 | .radio-inline .input-helper:after { 178 | background: var(--chkUnchecked); 179 | width: 9px; 180 | height: 9px; 181 | border-radius: 50%; 182 | top: 4px; 183 | left: 5px; 184 | transform: scale(0); 185 | } 186 | .radio input:checked + .input-helper:before, 187 | .radio-inline input:checked + .input-helper:before { 188 | border-color: var(--chkChecked); 189 | } 190 | .checkbox-inline, 191 | .radio-inline { 192 | padding-left: 30px; 193 | } 194 | /* Dropdown */ 195 | .dropdown-menu { 196 | position: absolute; 197 | left: 0; 198 | z-index: 9; 199 | display: none; 200 | min-width: 180px; 201 | list-style: none; 202 | font-size: 13px; 203 | text-align: left; 204 | background-color: var(--mnuBg); 205 | box-shadow: 0 6px 12px var(--mnuGlow); 206 | background-clip: padding-box; 207 | padding: 10px 0; 208 | border-radius: 3px; 209 | top: -1px; 210 | margin: 0; 211 | border: 0; 212 | } 213 | 214 | .dropdown-menu > li > a { 215 | display: block; 216 | font-weight: 400; 217 | color: var(--mnuTxt); 218 | padding: 10px 20px; 219 | text-decoration: none; 220 | clear: both; 221 | transition: background-color; 222 | transition-duration: 300ms; 223 | } 224 | .dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover { 225 | text-decoration: none; 226 | color: var(--mnuTxt); 227 | background-color: var(--mnuItemHover); 228 | } 229 | .dropup .dropdown-menu { 230 | bottom: 0; 231 | margin-bottom: 0; 232 | } 233 | /* Tables */ 234 | .table > thead > tr > td.active, 235 | .table > tbody > tr > td.active, 236 | .table > tfoot > tr > td.active, 237 | .table > thead > tr > th.active, 238 | .table > tbody > tr > th.active, 239 | .table > tfoot > tr > th.active, 240 | .table > thead > tr.active > td, 241 | .table > tbody > tr.active > td, 242 | .table > tfoot > tr.active > td, 243 | .table > thead > tr.active > th, 244 | .table > tbody > tr.active > th, 245 | .table > tfoot > tr.active > th { 246 | background-color: var(--tblActiveRow); 247 | } 248 | .table-hover > tbody > tr > td.active:hover, 249 | .table-hover > tbody > tr > th.active:hover, 250 | .table-hover > tbody > tr.active:hover > td, 251 | .table-hover > tbody > tr:hover > .active, 252 | .table-hover > tbody > tr.active:hover > th { 253 | transition: all; 254 | transition-duration: .3s; 255 | background-color: var(--tblActiveRowHover); 256 | } 257 | .table > thead > tr > th, .table > tbody > tr > th, .table > tfoot > tr > th, .table > thead > tr > td, .table > tbody > tr > td, .table > tfoot > tr > td { 258 | padding: 15px; 259 | line-height: 1.42857143; 260 | vertical-align: top; 261 | border-top: 1px solid var(--tblCellBorder); 262 | } 263 | .table > thead > tr > th { 264 | border-bottom-width: 1px; 265 | border-bottom-color: var(--tblHeadBottomBorder); 266 | color: var(--bodyTxt); 267 | font-weight: 700; 268 | } 269 | .table--vmiddle th, 270 | .table--vmiddle td { 271 | vertical-align: middle; 272 | } 273 | @media (min-width: 768px) { 274 | .table-striped:not(.table-bordered) > thead > tr > th, 275 | .table-striped:not(.table-bordered) > tbody > tr > td { 276 | vertical-align: middle; 277 | } 278 | } -------------------------------------------------------------------------------- /app/main-functions/import-dump.js: -------------------------------------------------------------------------------- 1 | /* Tested to be working with the TPB dump at best. Extremely fast, zero Memory leaks and no garbage collection needed. 2 | * Process exits immediately. Put together to work with papaparse */ 3 | 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const zlib = require('zlib'); 7 | require('v8').setFlagsFromString('--harmony'); //To fix the regex Lookbehind issue 8 | 9 | // Keep the application from crashing on unexpected errors 10 | process.on('uncaughtException', function (error) { 11 | console.log(error); 12 | process.send(['import-failed', 'general']); //mainWindow.webContents.send('import-failed', 'general'); 13 | }); 14 | 15 | let args = process.argv.slice(2); 16 | let isUpd = (args[0] === 'true'); 17 | let filePath = args[1]; 18 | let timestamp = args[2]; 19 | let keepDL = (args[3] === 'true'); 20 | let stagePath = path.join(process.cwd(), 'data', 'stage.csv'); 21 | let extract = path.join(process.cwd(), 'data', 'downloads', 'torrent_dump_full.csv'); 22 | let totalLines = 0; 23 | 24 | // Determine if the file is a compressed dump or an extracted CSV and proceed to the corresponding function 25 | let ext = path.extname(filePath).toLowerCase(); 26 | if (ext === '.gz') { 27 | decompressDump(); 28 | } else if (ext === '.csv') { 29 | extract = filePath; 30 | startCSV(); 31 | } else { 32 | process.send(['import-failed', 'invalid']);// mainWindow.webContents.send('import-failed', 'invalid'); 33 | } 34 | 35 | // Validate and start the importing process 36 | function startCSV(){ 37 | validate().then(function () { 38 | countFileLines(extract).then(function (count) { 39 | totalLines = count; 40 | startImport(); 41 | }).catch(function (err) { 42 | process.send(['import-failed', 'read']);// mainWindow.webContents.send('import-failed', 'read'); 43 | console.log(err); 44 | }); 45 | }).catch(function (err) { 46 | switch (err) { 47 | case 0: 48 | process.send(['import-failed', 'invalid']);// mainWindow.webContents.send('import-failed', 'invalid'); 49 | break; 50 | default: 51 | process.send(['import-failed', 'read']);// mainWindow.webContents.send('import-failed', 'read'); 52 | break; 53 | } 54 | }); 55 | } 56 | 57 | // Validate the header of the CSV file to identify if it's a TPB dump 58 | function validate() { 59 | return new Promise((resolve, reject) => { 60 | process.send(['import-validate']);// mainWindow.webContents.send('import-validate'); 61 | let reader = fs.createReadStream(extract) 62 | .on("data", function (buffer) { 63 | let header = buffer.toString().split('\n')[0]; 64 | reader.close(); 65 | if (header === '#ADDED;HASH(B64);NAME;SIZE(BYTES)') { 66 | resolve(); 67 | } else { 68 | reject(0); 69 | } 70 | }) 71 | .on("error", function (err) { 72 | console.log(err); 73 | reject(1); 74 | }) 75 | }); 76 | } 77 | 78 | // Truncate the staging file if exist or create, Process the data and import. 79 | function startImport() { 80 | let lineCount = 1; 81 | let stage = fs.createWriteStream(stagePath).on('error', function (err) { 82 | stage.close(); 83 | console.log(err); 84 | process.send(['import-failed', 'temp']);// mainWindow.webContents.send('import-failed', 'temp'); 85 | }).on('open', function () { 86 | fs.createReadStream(extract) 87 | .on("data", function (buffer) { 88 | let idx = -1; 89 | lineCount--; 90 | let formattedLine = buffer.toString().replace(/(?!";)(? { 222 | let lineCount = 1; 223 | fs.createReadStream(filePath) 224 | .on("data", (buffer) => { 225 | let idx = -1; 226 | lineCount--; 227 | do { 228 | idx = buffer.indexOf(10, idx + 1); 229 | lineCount++; 230 | } while (idx !== -1); 231 | }).on("end", () => { 232 | resolve(lineCount); 233 | }).on("error", reject); 234 | }); 235 | } -------------------------------------------------------------------------------- /app/css/WndMain.css: -------------------------------------------------------------------------------- 1 | /* Global 2 | --------------*/ 3 | * { 4 | -webkit-user-drag: none; 5 | } 6 | body { 7 | font-family: 'Nunito', sans-serif; 8 | font-size: 13px; 9 | line-height: 1.42857143; 10 | color: var(--bodyTxt); 11 | background-color: var(--bodyBg); 12 | -webkit-user-select: none; 13 | } 14 | .body-container { 15 | display: grid; 16 | grid-template-rows: 30px 30px minmax(0px, auto) 40px; 17 | height: 100vh; 18 | } 19 | button:focus, input:focus { 20 | outline: none; !important; 21 | } 22 | /* Table */ 23 | table { 24 | cursor: default; 25 | /*transform: rotateX(0deg);*/ 26 | } 27 | thead > tr > th:hover { 28 | background: var(--tblHeadHover); 29 | } 30 | thead { 31 | background: var(--compClr); 32 | } 33 | th[data-role=columnheader] { 34 | cursor: pointer; 35 | } 36 | 37 | th[data-role=columnheader]:after { 38 | font: normal normal normal 14px/1 'Material-Design-Iconic-Font'; 39 | font-size: 18px; 40 | content: '\f1cd'; 41 | float: right; 42 | visibility: hidden; 43 | opacity: 0; 44 | } 45 | th[data-sort=desc]:after { 46 | content: '\f1ce'; 47 | } 48 | th[data-sort=asc]:after { 49 | content: '\f1cd'; 50 | } 51 | th[data-sort]:after { 52 | visibility: visible; 53 | opacity: 0.4; 54 | } 55 | th[data-role=columnheader]:hover:after { 56 | visibility: visible; 57 | opacity: 1; 58 | } 59 | #tblHead{ 60 | margin-bottom: 0; 61 | } 62 | #tblMain > thead > tr > th { 63 | border-bottom: none; 64 | } 65 | #tblMain thead{ 66 | visibility: hidden; 67 | } 68 | .sticky-top{ 69 | z-index: 1; 70 | } 71 | 72 | span, strong { 73 | cursor: default; 74 | } 75 | /* Notifications */ 76 | .alert-or { 77 | box-shadow: 0 0 8px 3px rgba(0,0,0,0.16); 78 | } 79 | .close-or { 80 | transform: translate(0, -3px); 81 | } 82 | /* Grid Layout 83 | --------------------*/ 84 | .item-v-center{ 85 | display: flex; 86 | align-items: center; 87 | flex: 1; 88 | } 89 | /* Title bar */ 90 | .grid-title { 91 | display: grid; 92 | grid-template-columns: 80px auto repeat(3, 48px); 93 | grid-template-rows: 30px; 94 | grid-template-areas: 95 | "appTitle glue-title btnMin btnMax btnExit"; 96 | background-color: var(--compClr); 97 | -webkit-app-region: drag; 98 | } 99 | .app-icon { 100 | margin-left: 10px; 101 | } 102 | .app-title { 103 | margin-left: 10px; 104 | } 105 | .btn-ctl { 106 | border: 0; 107 | height: 100%; 108 | width: 100%; 109 | background-color: var(--compClr); 110 | color: var(--bodyTxt); 111 | font-family: fantasy; 112 | -webkit-app-region: no-drag; 113 | } 114 | .btn-ctl i { 115 | font-size: 17px; 116 | /*font-weight: bold;*/ 117 | } 118 | .btn-ctl:focus { 119 | border: 1px; 120 | outline-style: none; 121 | box-shadow: none !important; 122 | } 123 | /* Menu bar */ 124 | .grid-menu { 125 | display: grid; 126 | grid-template-columns: 60px 70px 60px; 127 | padding-top: 3px; 128 | padding-left: 10px; 129 | background: linear-gradient(to top, var(--mnuGrade1), var(--mnuGrade2)); 130 | /*-webkit-app-region: drag;*/ 131 | } 132 | .mnu-itm { 133 | border: 0; 134 | height: 30px; 135 | width: 100%; 136 | border-radius: 3px; 137 | background-color: var(--mnuBtnBg); 138 | color: var(--bodyTxt); 139 | -webkit-app-region: no-drag; 140 | } 141 | .mnu-itm:focus, .mnu-itm:active { 142 | outline-style: none; 143 | box-shadow: none; 144 | } 145 | .mnu-itm:active { 146 | background: var(--bodyBg); 147 | } 148 | .mnu-itm[aria-expanded="true"] { 149 | background: linear-gradient(to bottom, var(--mnuGrade1), var(--mnuGrade2)); 150 | } 151 | .mnu-ico { 152 | font-size: 18px; 153 | font-weight: bold; 154 | vertical-align: middle; 155 | padding-right: 10px; 156 | } 157 | .mnu-txt { 158 | vertical-align: middle; 159 | cursor: pointer; 160 | } 161 | /* Body */ 162 | .grid-body { 163 | display: grid; 164 | grid-template-columns: auto 180px; 165 | grid-template-rows: 49px 47px 65px 55px 55px 55px 144px auto minmax(50px, 163px); 166 | grid-template-areas: 167 | "txtSearch btnSearch" 168 | "searchTools ." 169 | "tbl btnOpenMag" 170 | "tbl btnCopyMag" 171 | "tbl btnHash" 172 | "tbl btnGoogle" 173 | "tbl seedPanel" 174 | "tbl glue-1" 175 | "tbl TTLogo"; 176 | grid-column-gap: 20px; 177 | /*height: 1fr;*/ 178 | padding: 23px; 179 | padding-top: 5px; 180 | } 181 | 182 | .txtSearch { 183 | grid-area: txtSearch; 184 | } 185 | .btnSearch { 186 | grid-area: btnSearch; 187 | } 188 | .grid-search-tools { 189 | grid-area: searchTools; 190 | display: grid; 191 | grid-template-columns: 130px 95px 80px minmax(auto, 125px) 60px auto; 192 | padding-left: 15px; 193 | padding-right: 15px; 194 | padding-top: 15px; 195 | } 196 | .tbl-grid { 197 | grid-area: tbl; 198 | overflow-y: auto; 199 | margin-top: 20px; 200 | border-radius: 4px; 201 | background: var(--compClr); 202 | box-shadow: 0 2px 2px 0 var(--compShadow1), 0 0 0 1px var(--compShadow2); 203 | } 204 | .btnOpenMag { 205 | grid-area: btnOpenMag; 206 | margin-top: 20px; 207 | } 208 | .btnCopyMag { 209 | grid-area: btnCopyMag; 210 | margin-top: 10px; 211 | } 212 | .btnHash { 213 | grid-area: btnHash; 214 | margin-top: 10px; 215 | } 216 | .btnGoogle { 217 | grid-area: btnGoogle; 218 | margin-top: 10px; 219 | } 220 | .seedPanel { 221 | grid-area: seedPanel; 222 | margin-top: 30px; 223 | visibility: hidden; 224 | } 225 | .glue-1 { 226 | grid-area: glue-1; 227 | } 228 | .ttLogo { 229 | grid-area: TTLogo; 230 | } 231 | 232 | /* Status bar */ 233 | .grid-stat-bar { 234 | display: grid; 235 | grid-template-columns: auto auto; 236 | border-top: 1px solid var(--compClr); 237 | margin: 0 12px; 238 | } 239 | #txtStat { 240 | margin: 10px 0 10px 10px; 241 | } 242 | #txtStatRight { 243 | visibility: hidden; 244 | width: 100%; 245 | text-align: right; 246 | margin: 10px 10px 10px 0; 247 | } 248 | 249 | /* About Modal */ 250 | .abt-modal-content { 251 | display: grid; 252 | grid-template-rows: auto auto auto auto; 253 | padding: 30px; 254 | padding-top: 0; 255 | } 256 | .abt-title { 257 | margin-bottom: 15px; 258 | text-align: center; 259 | } 260 | .abt-title span { 261 | font-size: 25px; 262 | font-weight: bold; 263 | } 264 | .social-btns { 265 | display: flex; 266 | justify-content: center; 267 | margin: 20px 0; 268 | } 269 | .social-btns img { 270 | margin: 0 10px; 271 | cursor: pointer; 272 | } 273 | .social-btns img:hover { 274 | -webkit-transition: transform 0.3s ease; 275 | transform: scale(1.1); 276 | } 277 | 278 | /* Trackers Modal */ 279 | .mdl-trackers { 280 | height: 500px; 281 | } 282 | .trackers-modal-content { 283 | display: grid; 284 | grid-template-columns: auto 120px; 285 | grid-template-rows: 55px 55px minmax(0px, auto); 286 | grid-template-areas: 287 | "tbl btnCopyTrck" 288 | "tbl btnCopyAllTrck" 289 | "tbl ."; 290 | grid-column-gap: 20px; 291 | padding: 20px 10px 10px; 292 | } 293 | .trackers-tbl { 294 | grid-area: tbl; 295 | overflow-y: auto; 296 | border-radius: 4px; 297 | background: var(--compClr); 298 | box-shadow: 0 2px 2px 0 var(--compShadow1), 0 0 0 1px var(--compShadow2); 299 | } 300 | .btnCopyTrck { 301 | grid-area: btnCopyTrck; 302 | } 303 | .btnCopyAllTrck { 304 | grid-area: btnCopyAllTrck; 305 | } 306 | #tblTrackers td{ 307 | padding: 10px; 308 | } 309 | 310 | /* Preferences Modal */ 311 | .mdl-prefs { 312 | height: 500px; 313 | } 314 | .prefs-modal-content { 315 | display: grid; 316 | grid-template-columns: auto; 317 | grid-template-rows: auto 55px; 318 | grid-row-gap: 20px; 319 | padding: 20px 10px 10px; 320 | } 321 | .prefs-opts { 322 | overflow-y: auto; 323 | } 324 | .prefs-opts hr { 325 | border-top: 1px solid var(--compClr); 326 | } 327 | .prefs-section{ 328 | margin: 30px 0; 329 | } 330 | .prefs-itm { 331 | margin-top: 20px; 332 | } 333 | .prefs-span-txt { 334 | display: grid; 335 | grid-template-columns: 90px auto; 336 | margin-right: 10px; 337 | } 338 | .prefs-upd-int { 339 | display: grid; 340 | grid-template-columns: 178px 60px 50px; 341 | grid-column-gap: 5px; 342 | } 343 | .prefs-btns { 344 | display: grid; 345 | grid-template-columns: auto 120px 120px; 346 | grid-column-gap: 10px; 347 | } 348 | 349 | /* Themes Modal */ 350 | .mdl-themes { 351 | height: 500px; 352 | } 353 | .themes-modal-content { 354 | display: grid; 355 | grid-template-columns: auto; 356 | grid-template-rows: auto 55px; 357 | grid-row-gap: 20px; 358 | padding: 20px 10px 10px; 359 | } 360 | .themes-tiles { 361 | display: grid; 362 | grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); 363 | grid-gap: 20px; 364 | padding: 20px 10px 10px; 365 | overflow-y: auto; 366 | } 367 | .theme-prev-bg { 368 | height: 130px; 369 | width: 200px; 370 | margin: 0 auto; 371 | border-radius: 7px; 372 | border: solid 1px var(--bodyTxt); 373 | text-align: center; 374 | justify-content: center; 375 | } 376 | .theme-prev-content { 377 | text-align: center; 378 | } 379 | .btn-theme-apply { 380 | margin: 15px 0; 381 | width: 120px !important; 382 | display: block !important; 383 | box-shadow: 0 0 14px 1px var(--btnFocusGlow); 384 | } 385 | .btn-theme-del { 386 | width: 30px !important; 387 | height: 30px !important; 388 | box-shadow: 0 2px 2px 0 var(--compShadow1), 0 0 0 1px var(--compShadow2) !important; 389 | } 390 | .btn-theme-del.thm-default-btn { 391 | visibility: hidden; 392 | } 393 | .btn-theme-del > i { 394 | padding: 0 !important; 395 | margin-top: -13px !important; 396 | font-size: 16px; 397 | } 398 | .themes-btns { 399 | display: grid; 400 | grid-template-columns: auto 120px 120px 120px; 401 | grid-column-gap: 10px; 402 | } 403 | 404 | /* Overlay */ 405 | #overlay { 406 | display: grid; 407 | grid-template-rows: 33% 180px 70px auto; 408 | text-align: center; 409 | position: fixed; 410 | visibility: hidden; 411 | opacity: 0; 412 | width: 100%; 413 | height: 100%; 414 | top: 0; 415 | left: 0; 416 | right: 0; 417 | bottom: 0; 418 | background-color: var(--overlay); 419 | z-index: 1002; 420 | cursor: wait; 421 | transition: all .2s ease; 422 | } 423 | 424 | #olText{ 425 | color: var(--olTxt); 426 | font-size: 40px; 427 | } 428 | 429 | /* Global Elements 430 | --------------------*/ 431 | /*Button*/ 432 | .btn-ui { 433 | border: 0; 434 | height: 45px; 435 | width: 100%; 436 | padding: 10px; 437 | border-radius: 4px; 438 | background-color: var(--compClr); 439 | font-size: 15px; 440 | color: var(--btnTxtClr); 441 | box-shadow: 0 2px 2px 0 var(--compShadow1), 0 0 0 1px var(--compShadow2); 442 | } 443 | .btn-ico { 444 | font-size: 20px; 445 | vertical-align: middle; 446 | padding-right: 5px; 447 | } 448 | .btn-themed:hover { 449 | color: var(--btnTxtHover); 450 | background-color: var(--btnBgHover); 451 | border-color: var(--btnActive); 452 | } 453 | .btn-themed:focus, .btn-themed.focus { 454 | box-shadow: 0 0 0 0.2rem var(--btnFocusGlow); 455 | } 456 | .btn-themed.disabled, .btn-themed:disabled { 457 | color: var(--btnTxtDisable); 458 | background-color: var(--btnBgDisable); 459 | border-color: var(--btnBorderDisable); 460 | } 461 | .btn-themed:not(:disabled):not(.disabled):active, .btn-themed:not(:disabled):not(.disabled).active, 462 | .show > .btn-themed.dropdown-toggle { 463 | color: var(--btnTxtHover); 464 | background-color: var(--btnActive); 465 | } 466 | .btn-themed:not(:disabled):not(.disabled):active:focus, .btn-themed:not(:disabled):not(.disabled).active:focus, 467 | .show > .btn-themed.dropdown-toggle:focus { 468 | box-shadow: 0 0 0 0.2rem var(--btnFocusGlow); 469 | } 470 | 471 | /*Text box*/ 472 | .txtbox { 473 | border: 0; 474 | height: inherit; 475 | padding: 10px 10px 10px 10px; 476 | border-radius: 4px; 477 | width: 100%; 478 | background-color: var(--compClr); 479 | font-size: 15px; 480 | color: var(--bodyTxt); 481 | box-shadow: 0 2px 2px 0 var(--compShadow1), 0 0 0 1px var(--compShadow2); 482 | } 483 | .txtbox:focus { 484 | background: var(--txtFocusBg); 485 | color: var(--txtFocustxt); 486 | } 487 | .txtbox::-webkit-input-placeholder { 488 | color: var(--bodyTxt) 489 | } 490 | .txtbox:focus::-webkit-input-placeholder { 491 | color: var(--txtFocusPH) 492 | } 493 | .txtboxsm { 494 | height: 32px; 495 | } 496 | .txtinvalid, .txtinvalid:focus{ 497 | color: red; 498 | } 499 | /*Modal*/ 500 | .modal { 501 | text-align: center; 502 | } 503 | .modal:before { 504 | display: inline-block; 505 | vertical-align: middle; 506 | content: " "; 507 | height: 100%; 508 | } 509 | .modal-close { 510 | display: flex; 511 | justify-content: flex-end; 512 | } 513 | .mdl-ctl { 514 | width: 30px; 515 | height: 30px; 516 | border-radius: 50%; 517 | background: none; 518 | } 519 | .modal-dialog { 520 | display: inline-block; 521 | min-width: 600px; 522 | text-align: left; 523 | vertical-align: middle; 524 | } 525 | .modal-body { 526 | display: grid; 527 | padding: 10px; 528 | } 529 | .modal-window { 530 | height: 100%; 531 | grid-template-rows: 30px minmax(0px, auto); 532 | } 533 | .modal-content { 534 | background: var(--bodyBg); 535 | box-shadow: 0 3px 15px var(--modalGlow); 536 | } 537 | .modal-titlebar { 538 | display: grid; 539 | grid-template-columns: auto 30px; 540 | grid-template-rows: 30px; 541 | border-bottom: var(--compClr) 1px solid; 542 | min-height: 35px; 543 | cursor: -webkit-grab; 544 | -webkit-app-region: no-drag; 545 | } 546 | .modal-titlebar:active, .modal-titlebar-txt span:active{ 547 | cursor: -webkit-grabbing; 548 | } 549 | .modal-titlebar-txt { 550 | display: flex; 551 | align-items: center; 552 | } 553 | .mdl-ico { 554 | vertical-align: middle; 555 | font-size: 18px; 556 | padding-left: 5px; 557 | } 558 | .modal-titlebar-txt span{ 559 | font-weight: bold; 560 | font-size: 15px; 561 | padding-left: 10px; 562 | cursor: -webkit-grab; 563 | } 564 | .ui-icon, .ui-widget-content .ui-icon { 565 | background-image: url(../img/ui-icons_444444_256x240.png); 566 | } 567 | .ui-icon-gripsmall-diagonal-se { 568 | background-position: -80px -224px; 569 | } 570 | .ui-resizable-se { 571 | width: 16px; 572 | height: 16px; 573 | } 574 | /* Scrollbars */ 575 | *::-webkit-scrollbar { 576 | width: 10px !important; 577 | height: 10px !important; 578 | background: var(--scrollBg) !important; 579 | border: 1px solid var(--scrollBorder) !important; 580 | } 581 | *::-webkit-scrollbar-button { 582 | display: none !important; 583 | } 584 | *::-webkit-scrollbar-thumb, 585 | *::-webkit-scrollbar-track { 586 | border: 0 !important; 587 | box-shadow: none !important; 588 | } 589 | *::-webkit-scrollbar-thumb { 590 | min-height: 28px !important; 591 | background: var(--scrollThumb) !important; 592 | } 593 | *::-webkit-scrollbar-corner, 594 | *::-webkit-scrollbar-track { 595 | background: var(--scrollBg) !important; 596 | } 597 | /* Tooltip */ 598 | .tooltiptext { 599 | visibility: hidden; 600 | position: absolute; 601 | width: 120px; 602 | background-color: var(--mnuBg); 603 | color: var(--mnuTxt); 604 | text-align: center; 605 | padding: 5px 0; 606 | border-radius: 6px; 607 | z-index: 1; 608 | opacity: 0; 609 | transition: opacity .6s; 610 | bottom: 165%; 611 | left: 50%; 612 | margin-left: -60px; 613 | } 614 | .tooltiptext::after { 615 | content: ""; 616 | position: absolute; 617 | top: 100%; 618 | left: 50%; 619 | margin-left: -5px; 620 | border: 5px solid transparent; 621 | border-top-color: var(--mnuBg); 622 | } 623 | .show-tooltip:hover .tooltiptext { 624 | visibility: visible; 625 | opacity: 1; 626 | } 627 | 628 | /*------END GLOBAL------*/ 629 | 630 | /* Search Input 631 | --------------------*/ 632 | .top-search, .top-search__input, 633 | .top-search__reset:after, 634 | .top-search__reset:before, 635 | .btn-ui, .txtbox, 636 | .table-hover tbody tr:hover, 637 | thead > tr > th:hover, 638 | .btn-ctl, 639 | .mnu-itm{ 640 | -webkit-transition: all; 641 | -o-transition: all; 642 | transition: all; 643 | -webkit-transition-duration: .3s; 644 | transition-duration: .3s 645 | } 646 | 647 | .top-search { 648 | position: relative; 649 | margin: 2px 0; 650 | overflow: visible; 651 | box-shadow: 0 2px 2px 0 var(--compShadow1), 0 0 0 1px var(--compShadow2); 652 | /*display: inline;*/ 653 | display: inline-block; 654 | } 655 | 656 | .top-search__input { 657 | border: 0; 658 | height: 45px; 659 | padding: 10px 20px 10px 50px; 660 | border-radius: 4px; 661 | width: 100%; 662 | background-color: var(--compClr); 663 | font-size: 15px; 664 | color: var(--txtFocustxt) 665 | } 666 | 667 | .top-search__input::-webkit-input-placeholder { 668 | color: var(--bodyTxt) 669 | } 670 | 671 | .top-search--focused .top-search__input { 672 | background-color: var(--txtFocusBg) 673 | } 674 | 675 | .top-search--focused .top-search__input + .top-search__reset { 676 | color: var(--txtFocusBg) 677 | } 678 | 679 | .top-search--focused .top-search__input + .top-search__reset:before { 680 | -webkit-transform: scale(0) rotate(180deg); 681 | -ms-transform: scale(0) rotate(180deg); 682 | -o-transform: scale(0) rotate(180deg); 683 | transform: scale(0) rotate(180deg) 684 | } 685 | 686 | .top-search--focused .top-search__input + .top-search__reset:after { 687 | -webkit-transform: scale(1) rotate(0); 688 | -ms-transform: scale(1) rotate(0); 689 | -o-transform: scale(1) rotate(0); 690 | transform: scale(1) rotate(0) 691 | } 692 | 693 | .top-search--focused .top-search__input::-webkit-input-placeholder { 694 | color: var(--txtFocusPH) 695 | } 696 | 697 | 698 | .top-search__reset { 699 | left: 0; 700 | font-size: 20px; 701 | color: var(--bodyTxt); 702 | top: 0; 703 | position: absolute; 704 | cursor: pointer; 705 | width: 50px; 706 | height: 100% 707 | } 708 | 709 | .top-search__reset:hover { 710 | opacity: .75; 711 | filter: alpha(opacity=75) 712 | } 713 | 714 | .top-search__reset:after, .top-search__reset:before { 715 | position: absolute; 716 | left: 20px; 717 | top: 14px 718 | } 719 | 720 | .top-search__reset:after { 721 | content: '\f2ff'; 722 | -webkit-transform: scale(0) rotate(-180deg); 723 | -ms-transform: scale(0) rotate(-180deg); 724 | -o-transform: scale(0) rotate(-180deg); 725 | transform: scale(0) rotate(-180deg); 726 | color: var(--txtFocustxt) 727 | } 728 | 729 | /* Seeds 730 | --------------------*/ 731 | .seeds { 732 | margin-top: 20px; 733 | } 734 | .seeds i { 735 | display:inline-block; 736 | font-size: 30px; 737 | } 738 | .seeds span{ 739 | display:inline-block; 740 | vertical-align:middle; 741 | font-size: 20px; 742 | font-weight: bold; 743 | padding-left: 10px; 744 | } 745 | .seeds img{ 746 | display: inline-block; 747 | vertical-align: middle; 748 | margin-left: 5px; 749 | width: 25px; 750 | } 751 | 752 | /* TechTac Logo 753 | --------------------*/ 754 | .img-helper { 755 | display: inline-block; 756 | height: 100%; 757 | vertical-align: middle; 758 | } 759 | .tt-img{ 760 | height: 130px; 761 | max-height: 80%; 762 | text-align: center; 763 | } 764 | .tt-img img{ 765 | /*width: 127px;*/ 766 | vertical-align:middle; 767 | max-height: 100%; 768 | } 769 | .tt-img img:hover { 770 | -webkit-transition: transform 0.3s ease; 771 | transform: scale(1.04); 772 | cursor: pointer; 773 | } 774 | 775 | /* Support notification 776 | ------------------------*/ 777 | .support-alert { 778 | min-width: 470px; 779 | max-width: 524px; 780 | } 781 | .support-body { 782 | display: grid; 783 | grid-template-columns: 90px auto; 784 | grid-column-gap: 7px; 785 | } 786 | .support-alert h2, .support-alert h5 { 787 | font-weight: 700; 788 | } 789 | .support-body span { 790 | font-size: 14px; 791 | display: block; 792 | } 793 | .support-body span a:hover { 794 | color: darkred !important; 795 | cursor: pointer; 796 | } 797 | 798 | /* Autocomplete 799 | ----------------*/ 800 | .autocomplete-items { 801 | position: absolute; 802 | display: table; 803 | z-index: 8; 804 | top: 100%; 805 | left: 40px; 806 | right: 0; 807 | background-color: var(--txtFocusBg); 808 | border-radius: 4px; 809 | box-shadow: 0 6px 12px var(--mnuGlow); 810 | } 811 | .autocomplete-items div { 812 | cursor: pointer; 813 | color: var(--txtFocustxt); 814 | border-bottom: 1px solid rgba(0, 0, 0, 0.1); 815 | transition: all; 816 | transition-duration: 300ms; 817 | padding: 10px 50px 10px 10px; 818 | } 819 | .autocomplete-items div strong { 820 | font-weight: 800; 821 | } 822 | .autocomplete-items div:hover { 823 | background-color: rgba(0, 0, 0, 0.1); 824 | } 825 | .autocomplete-active { 826 | background-color: rgba(0, 0, 0, 0.1); 827 | } -------------------------------------------------------------------------------- /app/WndMain.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | OfflineBay by m4heshd 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 | 23 |
24 |
25 |
26 | 29 |
30 |
31 | 34 |
35 |
36 | 39 |
40 |
41 | 42 |
43 | 78 | 113 | 136 |
137 | 138 |
139 | 144 |
145 | 149 |
150 |
151 |
152 | 157 |
158 |
159 | 165 |
166 |
167 | Max Results: 168 |
169 |
170 | 172 |
173 |
174 | Filter: 175 |
176 |
177 | 179 |
180 |
181 |
182 |
183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 |
Date AddedHashTorrent NameTorrent Size
193 |
194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 |
Date AddedHashTorrent NameTorrent Size
206 |
207 |
208 | 212 |
213 |
214 | 218 |
219 |
220 | 224 |
225 |
226 | 230 |
231 |
232 |
233 | Seeds/Peers 234 |
235 |
236 | 237 | 5623 238 | 239 |
240 |
241 | 242 | 1200 243 | 244 |
245 |
246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 |
258 | 259 |
260 |
261 | Ready... 262 |
263 |
264 | Ready... 265 |
266 |
267 |
268 | 269 | 270 | 311 | 312 | 352 | 353 | 518 | 519 | 577 | 578 | 579 |
580 |
581 |
582 | 583 | 584 |
585 |
586 | Loading... 587 |
588 |
589 |
590 | 591 | 592 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | -------------------------------------------------------------------------------- /app/main.js: -------------------------------------------------------------------------------- 1 | /* QR9SkRpC1piyc/5wR87+vRl4oKxTFO28fjaeGeiafGCE1A== ;) */ 2 | 3 | const cp = require('child_process'); 4 | const electron = require('electron'); 5 | const url = require('url'); 6 | const path = require('path'); 7 | const Datastore = require('nedb'); 8 | const request = require('request'); 9 | const fs = require('fs'); 10 | const util = require('util'); 11 | const moment = require('moment'); 12 | 13 | const {app, BrowserWindow, ipcMain, dialog, Menu, Tray} = electron; 14 | 15 | let prefs = { 16 | maxed: false, 17 | position: [0, 0], 18 | size: [1200, 800], 19 | rs_count: 100, 20 | smart: true, 21 | inst: false, 22 | logToFile: true, 23 | confVer: "1.0" 24 | }; // Set initial Preferences 25 | let procImport; 26 | let procSearch; 27 | let procScrape; 28 | let procUpd; 29 | let scrapeOpts = []; 30 | let finalPrefs = false; 31 | let awaitingQuit = false; 32 | let awaitingScrape = false; 33 | let splashWindow; 34 | let mainWindow; 35 | let obTray; 36 | let appIcon = path.join(__dirname, 'img', 'icon.png'); 37 | let trayIcon = path.join(__dirname, 'img', 'icon_32.png'); 38 | let version = 'N/A'; 39 | 40 | /* System handles 41 | ------------------*/ 42 | let isMac = process.platform === 'darwin'; 43 | 44 | // Allow only a single instance of OfflineBay 45 | let shouldQuit = app.makeSingleInstance(function() { 46 | if (mainWindow) { 47 | showWindow(); 48 | } 49 | }); 50 | if (shouldQuit) { 51 | app.quit(); 52 | return; 53 | } 54 | 55 | app.setAppUserModelId(process.execPath); // To support Windows notifications 56 | 57 | // Log to file 58 | let logger = fs.createWriteStream(path.join(__dirname, 'data', 'logger.log')); 59 | logger.on('error', function (err) { 60 | console.log(err); 61 | logger = null; 62 | }); 63 | console.log = function () { 64 | let data = util.format.apply(this, arguments) + '\n'; 65 | if (prefs.logToFile) { 66 | if (logger) { 67 | logger.write(`[${moment().format('YYYY-MM-DD hh:mm:ss')}] : ${data}`); 68 | } 69 | process.stdout.write(data); 70 | } else { 71 | process.stdout.write(data); 72 | } 73 | }; 74 | 75 | // Get current version from package.json 76 | try { 77 | const packageJSON = require(path.join(__dirname, '..', 'package.json')); 78 | if (packageJSON) { 79 | version = packageJSON.version; 80 | } 81 | } catch (error) { 82 | console.log(error); 83 | } 84 | 85 | // Set app & tray icons 86 | switch (process.platform) { 87 | case 'win32': 88 | appIcon = path.join(__dirname, 'img', 'icon.ico'); 89 | trayIcon = appIcon; 90 | break; 91 | case 'darwin': 92 | appIcon = path.join(__dirname, 'img', 'icon.icns'); 93 | trayIcon = path.join(__dirname, 'img', 'icon_16.png'); 94 | app.dock.bounce('critical'); 95 | break; 96 | } 97 | 98 | /* Process handles 99 | --------------------*/ 100 | // Emitted if the window is waiting for child processes to exit 101 | process.on('cont-quit', function () { 102 | if (!procSearch && !procImport && !procScrape && !procUpd) { 103 | app.quit(); 104 | } 105 | }); 106 | // Emitted if the window is waiting for one scrape process to finish to start the next 107 | process.on('cont-scrape', function () { 108 | if (!procScrape) { 109 | initScrape(scrapeOpts[0], scrapeOpts[1]); 110 | } 111 | }); 112 | 113 | process.on('uncaughtException', function (error) { 114 | console.log(error); 115 | if (mainWindow){ 116 | popErr('An unknown error occurred'); 117 | } else { 118 | app.quit(); 119 | } 120 | }); 121 | 122 | /* DB functions 123 | ----------------*/ 124 | // Loads the configurations DB 125 | let config = new Datastore({ 126 | filename: path.join(__dirname, 'data', 'config.db'), 127 | autoload: true 128 | }); 129 | 130 | // Update the prefs object 131 | function updatePrefs() { 132 | prefs.maxed = mainWindow.isMaximized(); 133 | if (!prefs.maxed) { 134 | prefs.position = mainWindow.getPosition(); 135 | prefs.size = mainWindow.getSize(); 136 | } 137 | } 138 | 139 | // Save the current prefs to config DB 140 | function saveSession() { 141 | return new Promise((resolve, reject) => { 142 | config.update({type: 'win-state'}, { 143 | $set: { 144 | maxed: prefs.maxed, 145 | position: prefs.position, 146 | size: prefs.size 147 | } 148 | }, {}, function (err, numReplaced) { 149 | if (err || numReplaced < 1) { 150 | console.log(err); 151 | } 152 | config.update({type: 'search'}, { 153 | $set: { 154 | rs_count: prefs.rs_count, 155 | smart: prefs.smart, 156 | inst: prefs.inst 157 | } 158 | }, {}, function (err, numReplaced) { 159 | if (err || numReplaced < 1) { 160 | console.log(err); 161 | } 162 | finalPrefs = true; 163 | resolve(); 164 | }); 165 | }); 166 | }); 167 | } 168 | 169 | // Update updLast on DB 170 | function saveUpdLast(updLast) { 171 | return new Promise((resolve, reject) => { 172 | config.update({type: 'dump'}, { $set: { updLast: updLast } }, function (err, numReplaced) { 173 | if (err || numReplaced < 1) { 174 | console.log(err); 175 | reject(); 176 | } else { 177 | resolve(); 178 | } 179 | }) 180 | }); 181 | } 182 | 183 | // Add new search history item to the DB 184 | function addHistoryItm(txt) { 185 | config.update({type: 'search'}, {$push: {history: txt}}, function (err, numReplaced) { 186 | if (err || numReplaced < 1) { 187 | console.log(err); 188 | } 189 | }) 190 | } 191 | 192 | // Clear search history on DB 193 | function clearHistory() { 194 | return new Promise((resolve, reject) => { 195 | config.update({type: 'search'}, { $set: { history: [] } }, function (err, numReplaced) { 196 | if (err || numReplaced < 1) { 197 | console.log(err); 198 | reject(); 199 | } else { 200 | resolve(); 201 | } 202 | }) 203 | }); 204 | } 205 | 206 | // Update last support message shown timestamp on DB 207 | function updSupMsgDate() { 208 | config.update({type: 'gen'}, {$set: {supMsg: new Date().toISOString()}}, function (err, numReplaced) { 209 | if (err || numReplaced < 1) { 210 | console.log(err); 211 | console.log('An error occurred trying to update Support message timestamp'); 212 | } 213 | }) 214 | } 215 | 216 | // Load prefs from config DB and start OfflineBay 217 | function loadSession() { 218 | showSplash(); 219 | config.findOne({type: 'win-state'}, function (err, dbPref) { 220 | if (!err && dbPref) { 221 | prefs.maxed = dbPref.maxed; 222 | prefs.position = dbPref.position; 223 | prefs.size = dbPref.size; 224 | 225 | config.findOne({type: 'gen'}, function (err, dbPref) { 226 | if (!err && dbPref) { 227 | prefs.logToFile = dbPref.logToFile; 228 | startOB(); 229 | } else { 230 | setTimeout(popDbErr, 1500); 231 | } 232 | }) 233 | } else { 234 | console.log(err); 235 | popDbErr(); 236 | } 237 | }) 238 | } 239 | 240 | // Get the endpoint URL to update trackers 241 | function getTrackerEP() { 242 | return new Promise((resolve, reject) => { 243 | config.findOne({type: 'trackers'}, function (err, trck) { 244 | if (!err && trck) { 245 | resolve(trck.url); 246 | } else { 247 | console.log(err); 248 | reject(); 249 | } 250 | }) 251 | }); 252 | } 253 | 254 | // Update trackers list on DB 255 | function setTrackers(trcks) { 256 | return new Promise((resolve, reject) => { 257 | config.update({type: 'trackers'}, { $set: { trackers: trcks } }, function (err, numReplaced) { 258 | if (err || numReplaced < 1) { 259 | console.log(err); 260 | reject(); 261 | } else { 262 | resolve(); 263 | } 264 | }) 265 | }); 266 | } 267 | 268 | // Update preferences from renderer process 269 | function saveRndPrefs(rndPrefs) { 270 | return new Promise((resolve, reject) => { 271 | config.update({type: 'gen'}, { 272 | $set: { 273 | sysTray: rndPrefs.sysTray, 274 | useDHT: rndPrefs.useDHT, 275 | logToFile: prefs.logToFile 276 | } 277 | }, function (err, numReplaced) { 278 | if (err || numReplaced < 1) { 279 | console.log(err); 280 | reject(); 281 | } else { 282 | config.update({type: 'trackers'}, {$set: {url: rndPrefs.trckURL}}, function (err, numReplaced) { 283 | if (err || numReplaced < 1) { 284 | console.log(err); 285 | reject(); 286 | } else { 287 | config.update({type: 'dump'}, { 288 | $set: { 289 | updURL: rndPrefs.updURL, 290 | updType: rndPrefs.updType, 291 | updInt: rndPrefs.updInt, 292 | keepDL: rndPrefs.keepDL 293 | } 294 | }, function (err, numReplaced) { 295 | if (err || numReplaced < 1) { 296 | console.log(err); 297 | reject(); 298 | } else { 299 | config.update({type: 'search'}, { 300 | $set: { 301 | useAC: rndPrefs.useAC 302 | } 303 | }, function (err, numReplaced) { 304 | if (err || numReplaced < 1) { 305 | console.log(err); 306 | reject(); 307 | } else { 308 | resolve(); 309 | } 310 | }); 311 | } 312 | }); 313 | } 314 | }); 315 | } 316 | }); 317 | }); 318 | } 319 | 320 | function popDbErr() { 321 | dialog.showErrorBox('Critical error', 'DB error occurred. Please re-install OfflineBay'); 322 | } 323 | 324 | /* Main App handles 325 | --------------------*/ 326 | app.on('ready', loadSession); 327 | 328 | // Save the instance data to config DB before quitting 329 | app.on('will-quit', async function (event) { 330 | if (!finalPrefs) { 331 | event.preventDefault(); 332 | await saveSession(); 333 | app.quit(); 334 | } 335 | }); 336 | 337 | /* IPC Event handling 338 | ----------------------*/ 339 | /* Logger */ 340 | ipcMain.on('logger', function (event, data) { 341 | console.log(data); 342 | }); // Handle console.logs from Renderer 343 | ipcMain.on('get-logger-type', function (event) { 344 | event.returnValue = prefs.logToFile; 345 | }); // Handle Get logger type request 346 | 347 | /* Window controls */ 348 | ipcMain.on('app-close', function (event, data) { 349 | if (data) { 350 | mainWindow.hide(); 351 | if (isMac) { 352 | app.dock.hide(); 353 | } 354 | } else { 355 | app.quit(); 356 | } 357 | }); // Close button control 358 | ipcMain.on('app-min', function () { 359 | mainWindow.minimize(); 360 | }); // Minimize button control 361 | ipcMain.on('app-max', function () { 362 | mainWindow.isMaximized() ? mainWindow.restore() : mainWindow.maximize(); 363 | }); // Maximize/Restore button control 364 | ipcMain.on('show-win', function () { 365 | showWindow(); 366 | }); // Show and focus mainWindow 367 | ipcMain.on('drag-enter', function () { 368 | if (!procSearch && !procUpd && !procImport) { 369 | mainWindow.webContents.send('show-drag-ol'); 370 | } 371 | }); // Validate 'dragenter' event on mainWindow 372 | ipcMain.on('drag-leave', function () { 373 | if (!procSearch && !procUpd && !procImport) { 374 | mainWindow.webContents.send('hide-ol'); 375 | } 376 | }); // Validate 'dragleave' event on mainWindow 377 | ipcMain.on('show-dev', function () { 378 | if (mainWindow) { 379 | mainWindow.openDevTools(); 380 | } 381 | }); // Hand show dev console event 382 | 383 | /* App Update */ 384 | ipcMain.on('app-upd-check', function (event, data) { 385 | checkAppUpd(data); 386 | }); // Handle check app updates event 387 | 388 | /* Import */ 389 | ipcMain.on('pop-import', function () { 390 | popImport(); 391 | }); // Import dump file open dialog 392 | ipcMain.on('drop-import', function (event, data) { 393 | if (!procSearch && !procUpd) { 394 | doImport(false, data, '', false); // (isUpd, filePath, timestamp, keepDL) 395 | } else { 396 | popErr('Cannot import in the middle of another process'); 397 | } 398 | }); // Import files dragged and dropped to the mainWindow 399 | ipcMain.on('upd-import', function (event, data) { 400 | if (!procSearch) { 401 | doImport(true, data[0], data[1], data[2]); 402 | } else { 403 | popWarn('Can\'t update the dump file in the middle of searching') 404 | } 405 | }); // Import dump file after update is downloaded 406 | 407 | /* Search */ 408 | ipcMain.on('search-start', function (event, data) { 409 | initSearch(data[0], data[1], data[2], data[3]); 410 | }); // Handle search event 411 | ipcMain.on('add-history-itm', function (event, data) { 412 | addHistoryItm(data); 413 | }); // Handle add new history item event 414 | ipcMain.on('clear-history', function () { 415 | clearHistory().then(function () { 416 | popSuccess('Search history was cleared successfully'); 417 | }).catch(function () { 418 | popErr('Failed to clear search history on DB') 419 | }); 420 | }); // Handle clear search history event 421 | 422 | /* Preferences */ 423 | ipcMain.on('pref-change', function (event, data) { 424 | prefs[data[0]] = data[1]; 425 | }); // Handle any preference change event 426 | ipcMain.on('save-rnd-prefs', function (event, data) { 427 | saveRndPrefs(data).then(function () { 428 | popSuccess('Settings were saved successfully'); 429 | }).catch(function () { 430 | popErr('Failed to save settings to the DB') 431 | }); 432 | }); // Handle saving of preferences from renderer process 433 | ipcMain.on('save-upd-last', function (event, data) { 434 | saveUpdLast(data[0]).then(function () { 435 | if (data[1] === 'reset') { 436 | popSuccess('Dump update was Reset successfully'); 437 | } 438 | }).catch(function () { 439 | switch (data[1]) { 440 | case 'import': 441 | popErr('Failed to update dump Timestamp on DB'); 442 | break; 443 | case 'reset': 444 | popErr('Failed to Reset update on DB'); 445 | break; 446 | } 447 | }); 448 | }); // Handle updLast update event 449 | 450 | /* Scrape */ 451 | ipcMain.on('scrape-start', function (event, data) { 452 | initScrape(data[0], data[1]); 453 | }); // Handle seed/peer count event 454 | 455 | /* Trackers */ 456 | ipcMain.on('upd-trackers', function () { 457 | updTrackers(); 458 | }); // Handle update trackers event 459 | 460 | /* Update dump */ 461 | ipcMain.on('upd-dump', function (event, data) { 462 | let type = data[1]; 463 | switch (type) { 464 | case 'auto': 465 | case 'check': 466 | case 'notify': 467 | // console.log(type); 468 | checkDumpUpd(type, data[0], data[2]); 469 | break; 470 | case 'user': 471 | initUpdDump(type, data[0]); 472 | break; 473 | case 'tray': 474 | if (mainWindow.isMinimized() || !mainWindow.isVisible()) { 475 | checkDumpUpd('notify', data[0], data[2]); 476 | } else { 477 | checkDumpUpd('check', data[0], data[2]); 478 | } 479 | break; 480 | } 481 | }); // Handle update dump event 482 | 483 | /* Themes */ 484 | ipcMain.on('theme-import', function () { 485 | popThemeImport(); 486 | }); // Handle import theme event 487 | 488 | /* Import & Export settings */ 489 | ipcMain.on('pop-exp-conf', function () { 490 | popExpConf(); 491 | }); // Handle export settings save dialog event 492 | ipcMain.on('pop-imp-conf', function () { 493 | popImpConf(); 494 | }); // Handle import settings open dialog event 495 | 496 | /* Misc */ 497 | ipcMain.on('update-sup-msg', function () { 498 | updSupMsgDate(); 499 | }); // Handle 'update support message timestamp' event 500 | 501 | /* Notification senders 502 | ------------------------*/ 503 | // Show blue background notification 504 | function popMsg(msg) { 505 | mainWindow.webContents.send('notify', [msg, 'info']); 506 | } 507 | // Show green background notification 508 | function popSuccess(msg) { 509 | mainWindow.webContents.send('notify', [msg, 'success']); 510 | } 511 | // Show red background notification 512 | function popErr(msg) { 513 | mainWindow.webContents.send('notify', [msg, 'danger']); 514 | } 515 | // Show yellow background notification 516 | function popWarn(msg) { 517 | mainWindow.webContents.send('notify', [msg, 'warning']); 518 | } 519 | 520 | /* Misc Functions 521 | ------------------*/ 522 | // Initiate the waiting boolean and kill the corresponding child process 523 | function waitProcess(event, _process, name) { 524 | event.preventDefault(); 525 | awaitingQuit = true; 526 | popWarn('Wait for background process ' + name + ' to finish'); 527 | _process.kill('SIGINT'); 528 | } 529 | 530 | // Show splash window 531 | function showSplash() { 532 | splashWindow = new BrowserWindow({ 533 | width: 300, 534 | height: 300, 535 | resizable: false, 536 | show: false, 537 | frame: false, 538 | transparent: true, 539 | alwaysOnTop: true, 540 | icon: appIcon, 541 | title: 'OfflineBay by m4heshd' 542 | }); 543 | 544 | splashWindow.loadURL(url.format({ 545 | pathname: path.join(__dirname, 'WndSplash.html'), 546 | protocol: 'file:', 547 | slashes: true 548 | })); 549 | 550 | splashWindow.webContents.once('dom-ready', function () { 551 | splashWindow.show(); 552 | }); 553 | 554 | splashWindow.once('show', function () { 555 | splashWindow.webContents.send('fade'); 556 | }); 557 | } 558 | 559 | // Create the main window and handle events 560 | function startOB() { 561 | let curScreen; 562 | 563 | if (prefs.position[0] !== null && prefs.position[1] !== null) { 564 | curScreen = electron.screen.getDisplayNearestPoint({x: prefs.position[0], y: prefs.position[1]}); 565 | } else { 566 | curScreen = electron.screen.getPrimaryDisplay(); 567 | } 568 | 569 | if (prefs.size[0] > curScreen.workAreaSize.width) { 570 | prefs.size[0] = curScreen.workAreaSize.width; 571 | prefs.position[0] = curScreen.bounds.x; 572 | } 573 | if (prefs.size[1] > curScreen.workAreaSize.height) { 574 | prefs.size[1] = curScreen.workAreaSize.height; 575 | prefs.position[1] = curScreen.bounds.y; 576 | } 577 | 578 | mainWindow = new BrowserWindow({ 579 | width: prefs.size[0], 580 | height: prefs.size[1], 581 | x: prefs.position[0], 582 | y: prefs.position[1], 583 | minWidth: 762, 584 | minHeight: 700, 585 | show: false, 586 | frame: false, 587 | backgroundColor: '#1e2a31', 588 | webPreferences: { 589 | experimentalFeatures: true 590 | }, 591 | icon: appIcon, 592 | title: 'OfflineBay by m4heshd' 593 | }); 594 | 595 | mainWindow.loadURL(url.format({ 596 | pathname: path.join(__dirname, 'WndMain.html'), 597 | protocol: 'file:', 598 | slashes: true 599 | })); 600 | 601 | mainWindow.webContents.once('dom-ready', function () { 602 | if (prefs.maxed) { 603 | mainWindow.maximize(); 604 | } 605 | mainWindow.webContents.send('set-version', version); 606 | mainWindow.show(); 607 | }); 608 | 609 | mainWindow.once('show', function () { 610 | if (splashWindow) { 611 | splashWindow.destroy(); 612 | } 613 | }); 614 | 615 | mainWindow.on('close', function (event) { 616 | if (procImport) { 617 | waitProcess(event, procImport, '\'IMPORT\''); 618 | } // Validation of any running child processes before closing (Import) 619 | if (procSearch) { 620 | waitProcess(event, procSearch, '\'SEARCH\''); 621 | } // Validation of any running child processes before closing (Search) 622 | if (procScrape) { 623 | waitProcess(event, procScrape, '\'SCRAPE\''); 624 | } // Validation of any running child processes before closing (Scrape) 625 | if (procUpd) { 626 | waitProcess(event, procUpd, '\'UPDATE\''); 627 | } // Validation of any running child processes before closing (Update) 628 | }); 629 | mainWindow.on('closed', function () { 630 | mainWindow = null; 631 | app.quit(); 632 | }); 633 | mainWindow.on('maximize', function () { 634 | mainWindow.webContents.send('maxed'); 635 | updatePrefs(); 636 | }); 637 | mainWindow.on('unmaximize', function () { 638 | mainWindow.webContents.send('restored'); 639 | updatePrefs(); 640 | }); 641 | // Using the following events will ensure that the prefs object is always updated. 642 | // Handling this in the window close event may record incorrect data. 643 | mainWindow.on('move', function () { 644 | updatePrefs(); 645 | }); 646 | mainWindow.on('resize', function () { 647 | updatePrefs(); 648 | }); 649 | 650 | setSysTray(); 651 | } 652 | 653 | // Show and Focus mainWindow 654 | function showWindow(){ 655 | mainWindow.show(); 656 | mainWindow.focus(); 657 | if (isMac) { 658 | app.dock.show(); 659 | } 660 | } 661 | 662 | // Create system tray icon and functions 663 | function setSysTray() { 664 | obTray = new Tray(trayIcon); 665 | const trayMnu = Menu.buildFromTemplate([ 666 | {label: 'OfflineBay ' + version, icon: path.join(__dirname, 'img', 'icon_16.png'), enabled: false}, 667 | {type: 'separator'}, 668 | {label: 'Show', click: showWindow}, 669 | {label: 'Center on screen', click: centerWindow}, 670 | {label: 'Check dump updates', click: updCheckRequest}, 671 | {type: 'separator'}, 672 | {label: 'Quit', click: app.quit} 673 | ]); 674 | obTray.setToolTip('OfflineBay'); 675 | obTray.setContextMenu(trayMnu); 676 | 677 | obTray.on('click', showWindow); 678 | obTray.on('double-click', showWindow); 679 | 680 | function centerWindow(){ 681 | mainWindow.center(); 682 | } 683 | 684 | function updCheckRequest() { 685 | mainWindow.webContents.send('upd-check-tray'); 686 | } 687 | 688 | if (isMac) { 689 | const dockMnu = Menu.buildFromTemplate([ 690 | {label: 'OfflineBay ' + version, icon: path.join(__dirname, 'img', 'icon_16.png'), enabled: false}, 691 | {type: 'separator'}, 692 | {label: 'Show', click: showWindow}, 693 | {label: 'Center on screen', click: centerWindow}, 694 | {label: 'Check dump updates', click: updCheckRequest} 695 | ]); 696 | app.dock.setMenu(dockMnu); 697 | } 698 | } 699 | 700 | // Show open dialog for dump imports 701 | function popImport() { 702 | let dlg = dialog.showOpenDialog( 703 | mainWindow, 704 | { 705 | properties: ['openFile'], 706 | title: 'Open dump file (CSV or GZ)', 707 | filters: [ 708 | {name: 'Dump Files', extensions: ['csv', 'gz']} 709 | ] 710 | }); 711 | 712 | if (typeof dlg !== "undefined") { 713 | doImport(false, dlg[0], '', false); 714 | } 715 | } 716 | 717 | // Perform dump import process (Update or manual) 718 | function doImport(isUpd, filePath, timestamp, keepDL) { 719 | if (!procImport) { 720 | mainWindow.webContents.send('import-start'); 721 | procImport = cp.fork(path.join(__dirname, 'main-functions', 'import-dump.js'), [isUpd, filePath, timestamp, keepDL], { 722 | cwd: __dirname, 723 | silent: true 724 | }); 725 | procImport.stdout.on('data', function (data) { 726 | console.log(data.toString().slice(0,-1)); 727 | }); 728 | procImport.on('exit', function () { 729 | console.log('Import process ended'); 730 | procImport = null; 731 | if (awaitingQuit) { 732 | process.emit('cont-quit'); 733 | } 734 | }); 735 | procImport.on('message', function (m) { 736 | mainWindow.webContents.send(m[0], m[1]); 737 | }); 738 | } else { 739 | popWarn('One Import process is already running'); 740 | } 741 | } 742 | 743 | // Initiate search child process 744 | function initSearch(query, count, smart, inst) { 745 | if (!procSearch) { 746 | procSearch = cp.fork(path.join(__dirname, 'main-functions', 'search.js'), [query, count, smart, inst], { 747 | cwd: __dirname, 748 | silent: true 749 | }); 750 | procSearch.stdout.on('data', function (data) { 751 | console.log(data.toString().slice(0,-1)); 752 | }); 753 | procSearch.on('exit', function () { 754 | console.log('Search process ended'); 755 | procSearch = null; 756 | if (awaitingQuit) { 757 | process.emit('cont-quit'); 758 | } 759 | }); 760 | procSearch.on('message', function (m) { 761 | mainWindow.webContents.send(m[0], m[1]); 762 | }); 763 | mainWindow.webContents.send('search-init'); 764 | } else { 765 | popWarn('One Search process is already running'); 766 | mainWindow.webContents.send('hide-ol'); 767 | } 768 | } 769 | 770 | // Initiate tracker scrape child process 771 | function initScrape(hash, isDHT) { 772 | if (!procScrape) { 773 | mainWindow.webContents.send('scrape-init'); 774 | procScrape = cp.fork(path.join(__dirname, 'main-functions', 'scrape.js'), [hash, isDHT], { 775 | cwd: __dirname, 776 | silent: true 777 | }); 778 | procScrape.stdout.on('data', function (data) { 779 | console.log(data.toString().slice(0,-1)); 780 | }); 781 | procScrape.on('exit', function () { 782 | console.log('Scraping process ended'); 783 | procScrape = null; 784 | if (awaitingQuit) { 785 | process.emit('cont-quit'); 786 | } 787 | if (awaitingScrape) { 788 | process.emit('cont-scrape'); 789 | awaitingScrape = false; 790 | } else { 791 | mainWindow.webContents.send('scrape-end'); 792 | } 793 | }); 794 | procScrape.on('message', function (m) { 795 | mainWindow.webContents.send(m[0], m[1]); 796 | }); 797 | } else { 798 | awaitingScrape = true; 799 | scrapeOpts = [hash, isDHT]; 800 | procScrape.kill('SIGINT'); 801 | } 802 | } 803 | 804 | // Start tracker updating process 805 | function updTrackers(){ 806 | getTrackerEP().then(function (url) { 807 | request.get(url, function (err, res, body) { 808 | if (err) { 809 | console.log(err); 810 | mainWindow.webContents.send('upd-trackers-failed', 'net'); 811 | } 812 | else { 813 | let trcks = body.trim().split('\n\n'); 814 | if (trcks.length > 0) { 815 | setTrackers(trcks).then(function () { 816 | mainWindow.webContents.send('upd-trackers-success', trcks); 817 | }).catch(function () { 818 | mainWindow.webContents.send('upd-trackers-failed', 'update'); 819 | }); 820 | } else { 821 | mainWindow.webContents.send('upd-trackers-failed', 'empty'); 822 | } 823 | } 824 | }); 825 | }).catch(function () { 826 | mainWindow.webContents.send('upd-trackers-failed', 'ep'); 827 | }); 828 | } 829 | 830 | /* Dump updates 831 | ----------------*/ 832 | // Initialize dump update 833 | function initUpdDump(type, dlURL) { 834 | if (!procSearch && !procImport) { 835 | if (!procUpd) { 836 | procUpd = cp.fork(path.join(__dirname, 'main-functions', 'upd-dump.js'), [type, dlURL], { 837 | cwd: __dirname, 838 | silent: true 839 | }); 840 | procUpd.stdout.on('data', function (data) { 841 | console.log(data.toString().slice(0,-1)); 842 | }); 843 | procUpd.on('exit', function () { 844 | console.log('Dump update process ended'); 845 | procUpd = null; 846 | if (awaitingQuit) { 847 | process.emit('cont-quit'); 848 | } 849 | }); 850 | procUpd.on('message', function (m) { 851 | mainWindow.webContents.send(m[0], m[1]); 852 | }); 853 | } else { 854 | if (type === 'user') { 855 | popWarn('One update process is already running'); 856 | } 857 | } 858 | } else { 859 | if (type === 'user') { 860 | popWarn('Can\'t update. Dump file is busy at the moment'); 861 | } 862 | } 863 | } 864 | 865 | // Check for dump file updates 866 | function checkDumpUpd(type, dlURL, updLast) { 867 | if (!procUpd && !procImport) { 868 | let req = request({ 869 | method: 'GET', 870 | uri: dlURL 871 | }); 872 | 873 | req.on('response', function (data) { 874 | if ((data.headers['content-type'].split('/')[0]) === 'application') { 875 | let update = new Date(data.headers['last-modified']) - new Date(updLast); 876 | if (update > 0) { 877 | if (type === 'check') { 878 | dialog.showMessageBox( 879 | mainWindow, 880 | { 881 | type: 'question', 882 | buttons: ['Yes', 'No'], 883 | title: 'Update confirmation', 884 | message: 'An update is available. Do you want to proceed with the download?', 885 | cancelId: 1 886 | }, function (res) { 887 | if (res === 0) { 888 | mainWindow.webContents.send('upd-dump-init', 'user'); 889 | initUpdDump('user', dlURL); 890 | } else { 891 | mainWindow.webContents.send('hide-ol'); 892 | mainWindow.webContents.send('hide-stat'); 893 | } 894 | }); 895 | } else if (type === 'notify') { 896 | mainWindow.webContents.send('upd-check-notify'); 897 | } else if (type === 'auto') { 898 | mainWindow.webContents.send('upd-dump-init', type); 899 | initUpdDump(type, dlURL); 900 | } 901 | } else { 902 | mainWindow.webContents.send('upd-check-unavail', type); 903 | } 904 | } else { 905 | mainWindow.webContents.send('upd-check-failed', ['content', type]); 906 | } 907 | req.abort(); 908 | }); 909 | req.on('error', function (err) { 910 | console.log(err); 911 | mainWindow.webContents.send('upd-check-failed', ['download', type]); 912 | }); 913 | } else { 914 | if (type === 'check') { 915 | popWarn('An update process or import process is already running'); 916 | } 917 | } 918 | } 919 | 920 | /* Themes 921 | -----------*/ 922 | // Show open dialog to import themes 923 | function popThemeImport() { 924 | let dlg = dialog.showOpenDialog( 925 | mainWindow, 926 | { 927 | properties: ['openFile'], 928 | title: 'Open OfflineBay Theme (ZIP)', 929 | filters: [ 930 | {name: 'Theme packages', extensions: ['zip']} 931 | ] 932 | }); 933 | 934 | if (typeof dlg !== "undefined") { 935 | mainWindow.webContents.send('init-theme-import', dlg[0]); 936 | } 937 | } 938 | 939 | /* App Updates 940 | ---------------*/ 941 | // Check for new OfflineBay versions 942 | function checkAppUpd(updURL) { 943 | request({ 944 | method: 'GET', 945 | uri: updURL 946 | }, function (error, response, body) { 947 | if (!error) { 948 | try { 949 | let stable = JSON.parse(body)["stable"]; 950 | if (stable > version) { 951 | let link = JSON.parse(body)["stable-dl"]; 952 | dialog.showMessageBox( 953 | mainWindow, 954 | { 955 | type: 'question', 956 | buttons: ['Yes', 'No'], 957 | title: 'OfflineBay update', 958 | message: 'You\'re using an old version of OfflineBay. New version ' + stable + ' is available.\nDo you want to visit the download page?', 959 | cancelId: 1 960 | }, function (res) { 961 | if (res === 0) { 962 | mainWindow.webContents.send('open-link', link); 963 | } 964 | }); 965 | } 966 | } catch (error) { 967 | console.log(error); 968 | } 969 | } else { 970 | console.log(error); 971 | } 972 | } 973 | ); 974 | } 975 | 976 | /* Import & Export settings 977 | ----------------------------*/ 978 | // Pop save dialog and export settings 979 | function popExpConf() { 980 | let dlg = dialog.showSaveDialog( 981 | mainWindow, 982 | { 983 | title: 'Export settings as', 984 | defaultPath: 'OBSettings.db', 985 | filters: [ 986 | {name: 'Settings dump', extensions: ['db']} 987 | ] 988 | }); 989 | 990 | if (typeof dlg !== "undefined") { 991 | try { 992 | fs.createReadStream(path.join(__dirname, 'data', 'config.db')).pipe(fs.createWriteStream(dlg)); 993 | popSuccess('Settings were exported successfully'); 994 | } catch (error) { 995 | console.log(error); 996 | popErr('Failed to export. An error occurred'); 997 | } 998 | } 999 | } 1000 | 1001 | // Pop open dialog, Validate and import settings 1002 | function popImpConf() { 1003 | let dlg = dialog.showOpenDialog( 1004 | mainWindow, 1005 | { 1006 | properties: ['openFile'], 1007 | title: 'Open exported settings (DB)', 1008 | filters: [ 1009 | {name: 'Settings dump', extensions: ['db']} 1010 | ] 1011 | }); 1012 | 1013 | if (typeof dlg !== "undefined") { 1014 | let expConf = new Datastore({ 1015 | filename: dlg[0], 1016 | autoload: true 1017 | }); 1018 | 1019 | expConf.findOne({type: 'gen'}, function (err, pref) { 1020 | if (!err && pref) { 1021 | if (prefs.confVer === pref.confVer) { 1022 | config.remove({}, {multi: true}, function (err, numRemoved) { 1023 | if (err || numRemoved < 1) { 1024 | popErr('Failed to import. An error occurred while clearing current settings'); 1025 | } else { 1026 | expConf.find({}, function (err, pref) { 1027 | if (!err && pref) { 1028 | config.insert(pref, function (err) { 1029 | if (err) { 1030 | popErr('Failed to import. An error occurred while replacing current settings'); 1031 | } else { 1032 | popSuccess('Settings were imported successfully'); 1033 | dialog.showMessageBox( 1034 | mainWindow, 1035 | { 1036 | type: 'info', 1037 | buttons: ['Ok'], 1038 | title: 'App restart needed', 1039 | message: 'Import is complete. OfflineBay will restart to apply new settings' 1040 | }, function () { 1041 | app.relaunch(); 1042 | app.quit(); 1043 | }); 1044 | } 1045 | }); 1046 | } else { 1047 | popErr('Failed to import. An error occurred while retrieving new settings'); 1048 | } 1049 | }); 1050 | } 1051 | }); 1052 | } else { 1053 | popErr('Unable to import. Exported settings version doesn\'t match the current version'); 1054 | } 1055 | } else { 1056 | console.log(err, pref); 1057 | popErr('Unable to import. An error occurred trying to open the file'); 1058 | } 1059 | }) 1060 | } 1061 | } 1062 | --------------------------------------------------------------------------------