├── .gitignore ├── Client ├── ElectronClient │ ├── .gitignore │ ├── assets │ │ ├── icon.ico │ │ ├── icon.png │ │ └── tray-icon.png │ ├── css │ │ ├── bootstrap-grid.css │ │ ├── bootstrap-grid.css.map │ │ ├── bootstrap-grid.min.css │ │ ├── bootstrap-grid.min.css.map │ │ ├── bootstrap-reboot.css │ │ ├── bootstrap-reboot.css.map │ │ ├── bootstrap-reboot.min.css │ │ ├── bootstrap-reboot.min.css.map │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ ├── dark-mode-res │ │ ├── dark-mode-switch.min.js │ │ └── dark-mode.css │ ├── main.js │ ├── mainWindow.html │ ├── package-lock.json │ └── package.json └── PWF_Desktop │ ├── .editorconfig │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitattributes │ ├── .gitignore │ ├── app │ ├── .testcafe-electron-rc │ ├── Routes.tsx │ ├── actions │ │ ├── APIConnectionActions.ts │ │ ├── appInitializedActions.ts │ │ ├── editorSelectorActions.ts │ │ ├── feedDateActions.ts │ │ ├── feedDownloadActions.ts │ │ ├── feedIllustActions.ts │ │ ├── feedThumbnailActions.ts │ │ └── feedTimerActions.ts │ ├── app.global.css │ ├── app.html │ ├── app.icns │ ├── components │ │ ├── DownloadManager │ │ │ └── DownloadManager.ts │ │ ├── Editor │ │ │ └── Editor.tsx │ │ ├── Feed │ │ │ ├── Feed.css │ │ │ └── Feed.tsx │ │ ├── Home.css │ │ ├── Home.tsx │ │ ├── Navbar │ │ │ ├── Navbar.css │ │ │ └── Navbar.tsx │ │ ├── Ranking │ │ │ └── Ranking.tsx │ │ ├── Recommender │ │ │ └── Recommender.tsx │ │ ├── Settings │ │ │ └── Settings.tsx │ │ ├── WallpaperSetter │ │ │ └── WallpaperSetter.ts │ │ └── css.d.ts │ ├── constants │ │ ├── defaultConfigs.json │ │ ├── routes.json │ │ ├── storeType.ts │ │ └── styles.json │ ├── containers │ │ ├── App.tsx │ │ └── Root.tsx │ ├── index.tsx │ ├── main.dev.ts │ ├── menu.ts │ ├── package-lock.json │ ├── package.json │ ├── reducers │ │ ├── APIConnectionReducer.ts │ │ ├── appInitializedReducer.ts │ │ ├── editorSelectionReducer.ts │ │ ├── feedDateReducer.ts │ │ ├── feedDownloadReducer.ts │ │ ├── feedIllustReducer.ts │ │ ├── feedThumbnailReducer.ts │ │ ├── feedTimerReducer.ts │ │ ├── recommenderDataReducer.ts │ │ └── settingsReducer.ts │ ├── res │ │ └── imgs │ │ │ └── empty_pattern_gray.png │ ├── rootReducer.ts │ ├── store.ts │ ├── utils │ │ └── .gitkeep │ └── yarn.lock │ ├── babel.config.js │ ├── configs │ ├── .eslintrc │ ├── webpack.config.base.js │ ├── webpack.config.eslint.js │ ├── webpack.config.main.prod.babel.js │ ├── webpack.config.renderer.dev.babel.js │ ├── webpack.config.renderer.dev.dll.babel.js │ └── webpack.config.renderer.prod.babel.js │ ├── internals │ ├── img │ │ ├── erb-banner.png │ │ ├── erb-logo.png │ │ ├── eslint-padded-90.png │ │ ├── eslint-padded.png │ │ ├── eslint.png │ │ ├── jest-padded-90.png │ │ ├── jest-padded.png │ │ ├── jest.png │ │ ├── js-padded.png │ │ ├── js.png │ │ ├── npm.png │ │ ├── react-padded-90.png │ │ ├── react-padded.png │ │ ├── react-router-padded-90.png │ │ ├── react-router-padded.png │ │ ├── react-router.png │ │ ├── react.png │ │ ├── redux-padded-90.png │ │ ├── redux-padded.png │ │ ├── redux.png │ │ ├── webpack-padded-90.png │ │ ├── webpack-padded.png │ │ ├── webpack.png │ │ ├── yarn-padded-90.png │ │ ├── yarn-padded.png │ │ └── yarn.png │ ├── mocks │ │ └── fileMock.js │ └── scripts │ │ ├── .eslintrc │ │ ├── BabelRegister.js │ │ ├── CheckBuildsExist.js │ │ ├── CheckNativeDep.js │ │ ├── CheckNodeEnv.js │ │ ├── CheckPortInUse.js │ │ ├── CheckYarn.js │ │ ├── DeleteSourceMaps.js │ │ └── ElectronRebuild.js │ ├── package-lock.json │ ├── package.json │ ├── resources │ ├── icon.icns │ ├── icon.ico │ └── icon.png │ ├── tsconfig.json │ └── yarn.lock ├── Demo ├── pick_wallpaper_demo.gif └── set_wallpaper_demo.gif ├── Docs └── PWF Architecture Diagram.png ├── LICENSE.md ├── README.cn.md ├── README.jp.md ├── README.md ├── Server ├── README.md ├── backend │ ├── Old │ │ ├── paper_user.php │ │ ├── pick_paper.php │ │ ├── pixiv_db_schema.sql │ │ ├── ranking_gallery.php │ │ └── select_paper.php │ └── pwf-api │ │ ├── app.js │ │ ├── index.js │ │ ├── package-lock.json │ │ └── package.json ├── frontend │ ├── .editorconfig │ ├── .gitignore │ ├── .htaccess │ ├── README.md │ ├── angular.json │ ├── e2e │ │ ├── protractor.conf.js │ │ ├── src │ │ │ ├── app.e2e-spec.ts │ │ │ └── app.po.ts │ │ └── tsconfig.e2e.json │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── app.component.css │ │ │ ├── app.component.html │ │ │ ├── app.component.spec.ts │ │ │ ├── app.component.ts │ │ │ ├── app.module.ts │ │ │ ├── demo.service.spec.ts │ │ │ ├── demo.service.ts │ │ │ ├── demo │ │ │ │ ├── demo.component.css │ │ │ │ ├── demo.component.html │ │ │ │ ├── demo.component.spec.ts │ │ │ │ └── demo.component.ts │ │ │ ├── faq │ │ │ │ ├── faq.component.css │ │ │ │ ├── faq.component.html │ │ │ │ ├── faq.component.spec.ts │ │ │ │ └── faq.component.ts │ │ │ ├── features │ │ │ │ ├── features.component.css │ │ │ │ ├── features.component.html │ │ │ │ ├── features.component.spec.ts │ │ │ │ └── features.component.ts │ │ │ ├── picker.service.spec.ts │ │ │ ├── picker.service.ts │ │ │ ├── ranking.service.spec.ts │ │ │ ├── ranking.service.ts │ │ │ └── ranking │ │ │ │ ├── ranking.component.css │ │ │ │ ├── ranking.component.html │ │ │ │ ├── ranking.component.spec.ts │ │ │ │ └── ranking.component.ts │ │ ├── assets │ │ │ ├── .gitkeep │ │ │ └── img │ │ │ │ ├── desktop_cool.jpg │ │ │ │ ├── desktop_cute.jpg │ │ │ │ └── desktop_default.jpg │ │ ├── browserslist │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── karma.conf.js │ │ ├── main.ts │ │ ├── polyfills.ts │ │ ├── styles.css │ │ ├── test.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ ├── tsconfig.json │ └── tslint.json └── pipeline │ ├── image-sr │ ├── .gitignore │ ├── configs │ │ ├── dev │ │ │ ├── Dockerfile │ │ │ ├── deploy_configs.sh │ │ │ └── script_configs.sh │ │ ├── prod │ │ │ ├── Dockerfile │ │ │ ├── deploy_configs.sh │ │ │ └── script_configs.sh │ │ └── qa │ │ │ ├── Dockerfile │ │ │ ├── deploy_configs.sh │ │ │ └── script_configs.sh │ ├── deploy.sh │ ├── k8s │ │ ├── dev │ │ │ └── deploy.yaml │ │ ├── prod │ │ │ └── deploy.yaml │ │ └── qa │ │ │ └── deploy.yaml │ └── src │ │ ├── downloader.py │ │ ├── error_handlers.py │ │ ├── exec.sh │ │ ├── model.py │ │ ├── requirements.txt │ │ ├── subscriber.py │ │ └── uploader.py │ ├── pixiv-scraper-old │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── config_template.yaml │ ├── cron │ ├── data_upload │ │ ├── .gitignore │ │ ├── index.js │ │ ├── package-lock.json │ │ └── package.json │ ├── download.py │ ├── geckodriver │ ├── processing.php │ ├── refresh.sh │ ├── requirements.txt │ ├── tests │ │ └── timestamp.py │ ├── update_detector │ │ ├── README.md │ │ ├── requirements.txt │ │ └── update_detector.py │ ├── upload.php │ └── upload_user.php │ └── pixiv-scraper │ ├── .gitignore │ ├── README.md │ ├── configs │ └── dev │ │ ├── Dockerfile │ │ ├── deploy_configs.sh │ │ └── script_configs.sh │ ├── deploy.sh │ ├── k8s │ └── dev │ │ └── deploy.yaml │ └── src │ ├── data_upload │ ├── .gitignore │ ├── index.js │ ├── package-lock.json │ └── package.json │ ├── download.py │ ├── error_handlers.py │ ├── exec.sh │ ├── geckodriver │ └── requirements.txt └── package-lock.json /.gitignore: -------------------------------------------------------------------------------- 1 | # MacOS 2 | .DS_STORE 3 | /downloads -------------------------------------------------------------------------------- /Client/ElectronClient/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | -------------------------------------------------------------------------------- /Client/ElectronClient/assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/ElectronClient/assets/icon.ico -------------------------------------------------------------------------------- /Client/ElectronClient/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/ElectronClient/assets/icon.png -------------------------------------------------------------------------------- /Client/ElectronClient/assets/tray-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/ElectronClient/assets/tray-icon.png -------------------------------------------------------------------------------- /Client/ElectronClient/css/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.3.1 (https://getbootstrap.com/) 3 | * Copyright 2011-2019 The Bootstrap Authors 4 | * Copyright 2011-2019 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important} 8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /Client/ElectronClient/dark-mode-res/dark-mode-switch.min.js: -------------------------------------------------------------------------------- 1 | !function(){var t,e=document.getElementById("darkSwitch");if(e){t=null!==localStorage.getItem("darkSwitch")&&"dark"===localStorage.getItem("darkSwitch"),(e.checked=t)?document.body.setAttribute("data-theme","dark"):document.body.removeAttribute("data-theme"),e.addEventListener("change",function(t){e.checked?(document.body.setAttribute("data-theme","dark"),localStorage.setItem("darkSwitch","dark")):(document.body.removeAttribute("data-theme"),localStorage.removeItem("darkSwitch"))})}}(); -------------------------------------------------------------------------------- /Client/ElectronClient/dark-mode-res/dark-mode.css: -------------------------------------------------------------------------------- 1 | [data-theme="dark"] { 2 | background-color: #111 !important; 3 | color: #eee; 4 | } 5 | 6 | [data-theme="dark"] .bg-light { 7 | background-color: #333 !important; 8 | } 9 | 10 | [data-theme="dark"] .bg-white { 11 | background-color: #000 !important; 12 | } 13 | 14 | [data-theme="dark"] .bg-black { 15 | background-color: #eee !important; 16 | } -------------------------------------------------------------------------------- /Client/ElectronClient/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pixivwallpaperclient", 3 | "version": "1.3.6", 4 | "author": "SingularityF", 5 | "description": "https://github.com/SingularityF/PixivWallpaper", 6 | "main": "main.js", 7 | "scripts": { 8 | "start": "electron .", 9 | "pack": "electron-builder --dir", 10 | "dist": "electron-builder" 11 | }, 12 | "build": { 13 | "appId": "pixiv.wallpaper", 14 | "win": { 15 | "icon": "./assets/icon.ico" 16 | }, 17 | "mac": { 18 | "target": "dmg", 19 | "icon": "./assets/icon.png" 20 | } 21 | }, 22 | "license": "ISC", 23 | "dependencies": { 24 | "appdata-path": "^1.0.0", 25 | "axios": "^0.19.0", 26 | "bootstrap": "^4.3.1", 27 | "form-data": "^2.5.1", 28 | "jquery": "^3.5.0", 29 | "macaddress": "^0.2.9", 30 | "mkdirp": "^0.5.1", 31 | "moment": "^2.24.0", 32 | "popper.js": "^1.16.0", 33 | "qs": "^6.9.0", 34 | "wallpaper": "^4.4.1" 35 | }, 36 | "devDependencies": { 37 | "electron": "^7.2.4", 38 | "electron-builder": "^22.1.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/.eslintignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | .eslintcache 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | 30 | # OSX 31 | .DS_Store 32 | 33 | # App packaged 34 | release 35 | app/*.main.prod.js 36 | app/main.prod.js 37 | app/main.prod.js.map 38 | app/renderer.prod.js 39 | app/renderer.prod.js.map 40 | app/style.css 41 | app/style.css.map 42 | dist 43 | dll 44 | main.js 45 | main.js.map 46 | 47 | .idea 48 | npm-debug.log.* 49 | __snapshots__ 50 | 51 | # Package.json 52 | package.json 53 | .travis.yml 54 | *.css.d.ts 55 | *.sass.d.ts 56 | *.scss.d.ts 57 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'erb/typescript', 3 | rules: { 4 | // A temporary hack related to IDE not resolving correct package.json 5 | 'import/no-extraneous-dependencies': 'off', 6 | }, 7 | parserOptions: { 8 | ecmaVersion: 2020, 9 | sourceType: 'module', 10 | project: './tsconfig.json', 11 | tsconfigRootDir: __dirname, 12 | createDefaultProgram: true, 13 | }, 14 | settings: { 15 | 'import/resolver': { 16 | // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below 17 | node: {}, 18 | webpack: { 19 | config: require.resolve('./configs/webpack.config.eslint.js'), 20 | }, 21 | }, 22 | 'import/parsers': { 23 | '@typescript-eslint/parser': ['.ts', '.tsx'], 24 | }, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.png binary 3 | *.jpg binary 4 | *.jpeg binary 5 | *.ico binary 6 | *.icns binary 7 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | .eslintcache 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | 30 | # OSX 31 | .DS_Store 32 | 33 | # App packaged 34 | release 35 | app/main.prod.js 36 | app/main.prod.js.map 37 | app/renderer.prod.js 38 | app/renderer.prod.js.map 39 | app/style.css 40 | app/style.css.map 41 | dist 42 | dll 43 | main.js 44 | main.js.map 45 | 46 | .idea 47 | npm-debug.log.* 48 | *.css.d.ts 49 | *.sass.d.ts 50 | *.scss.d.ts 51 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/.testcafe-electron-rc: -------------------------------------------------------------------------------- 1 | { 2 | "mainWindowUrl": "./app.html", 3 | "appPath": "." 4 | } 5 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/Routes.tsx: -------------------------------------------------------------------------------- 1 | /* eslint react/jsx-props-no-spreading: off */ 2 | import React from 'react'; 3 | import { Switch, Route } from 'react-router-dom'; 4 | import routes from './constants/routes.json'; 5 | import App from './containers/App'; 6 | import Home from './components/Home'; 7 | import Feed from './components/Feed/Feed'; 8 | import Editor from './components/Editor/Editor'; 9 | import Ranking from './components/Ranking/Ranking'; 10 | import Navbar from './components/Navbar/Navbar'; 11 | import Settings from './components/Settings/Settings'; 12 | import GlobalStyles from './constants/styles.json'; 13 | import Recommender from './components/Recommender/Recommender'; 14 | import { RouterSharp } from '@material-ui/icons'; 15 | 16 | // Lazily load routes and code split with webpacck 17 | // const LazyCounterPage = React.lazy(() => 18 | // import(/* webpackChunkName: "CounterPage" */ './containers/CounterPage') 19 | // ); 20 | 21 | // const CounterPage = (props: Record) => ( 22 | // Loading...}> 23 | // 24 | // 25 | // ); 26 | 27 | export default function Routes() { 28 | return ( 29 | 30 |
31 | 32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
48 |
49 | 50 | 51 |
52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/actions/APIConnectionActions.ts: -------------------------------------------------------------------------------- 1 | export function updateAPIURL(APIURL: string) { 2 | return { 3 | type: 'API_LOADED', 4 | data: APIURL 5 | }; 6 | } 7 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/actions/appInitializedActions.ts: -------------------------------------------------------------------------------- 1 | export function appInitialized() { 2 | return { 3 | type: 'INIT_DONE', 4 | }; 5 | } 6 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/actions/editorSelectorActions.ts: -------------------------------------------------------------------------------- 1 | import feedDateReducer from "../reducers/feedDateReducer"; 2 | 3 | export const editorSelectionUpdate = (illustID: number, path: string, feedDate: string) => { 4 | return { 5 | type: 'SELECTION_UPDATE', 6 | path: path, 7 | illustID: illustID, 8 | feedDate: feedDate, 9 | }; 10 | }; -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/actions/feedDateActions.ts: -------------------------------------------------------------------------------- 1 | export const feedDateUpdate = (data: string) => { 2 | return { 3 | type: 'DATE_UPDATE', 4 | data: data, 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/actions/feedDownloadActions.ts: -------------------------------------------------------------------------------- 1 | export const feedDownloadUpdate = (key: number, data: string) => { 2 | return { 3 | type: 'DOWNLOAD_UPDATE', 4 | data: data, 5 | key: key, 6 | }; 7 | }; 8 | 9 | export const feedDownloadReset = () => { 10 | return { 11 | type: 'DOWNLOAD_RESET', 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/actions/feedIllustActions.ts: -------------------------------------------------------------------------------- 1 | import {IllustData} from '../constants/storeType'; 2 | 3 | export const feedIllustUpdate = (data: Array) =>{ 4 | return { 5 | type: 'ILLUST_UPDATE', 6 | data: data 7 | }; 8 | }; -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/actions/feedThumbnailActions.ts: -------------------------------------------------------------------------------- 1 | export const feedThumbnailUpdate = (key: number, data: string) => { 2 | return { 3 | type: 'THUMBNAIL_UPDATE', 4 | data: data, 5 | key: key, 6 | }; 7 | }; 8 | 9 | export const feedThumbnailReset = () => { 10 | return { 11 | type: 'THUMBNAIL_RESET' 12 | }; 13 | }; -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/actions/feedTimerActions.ts: -------------------------------------------------------------------------------- 1 | export const tick = () =>{ 2 | return { 3 | type: 'TICK' 4 | }; 5 | }; 6 | 7 | export const reset = () =>{ 8 | return { 9 | type: 'RESET' 10 | }; 11 | }; -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/app.global.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @NOTE: Prepend a `~` to css file paths that are in your node_modules 3 | * See https://github.com/webpack-contrib/sass-loader#imports 4 | */ 5 | @import '~@fortawesome/fontawesome-free/css/all.css'; 6 | @import '~cropperjs/dist/cropper.css'; 7 | 8 | body { 9 | position: relative; 10 | height: 100vh; 11 | font-family: Arial, Helvetica, Helvetica Neue, serif; 12 | background-color: rgb(90, 90, 90); 13 | color: rgb(228, 228, 228); 14 | margin: 0; 15 | padding: 0; 16 | overflow: hidden; 17 | } 18 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Pixiv Wallpaper Feed 6 | 20 | 21 | 22 |
23 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/app.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/app/app.icns -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/components/DownloadManager/DownloadManager.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | import { remote } from 'electron'; 4 | import { downloadFolder } from '../../constants/defaultConfigs.json'; 5 | import axios from 'axios'; 6 | const mkdirp = require('mkdirp'); 7 | const getSize = require('get-folder-size'); 8 | 9 | export async function createFolder(folderPath: string) { 10 | await mkdirp(folderPath); 11 | return; 12 | } 13 | 14 | export async function getFolderSize(callback: any) { 15 | await createFolder(getDownloadFolder()); 16 | getSize(getDownloadFolder(), (err, size) => { 17 | if (err) { 18 | throw err; 19 | } 20 | let sizeString = (size / 1024 / 1024).toFixed(2) + ' MB'; 21 | callback(sizeString); 22 | }); 23 | } 24 | 25 | export function getDownloadFolder() { 26 | return path.resolve(remote.app.getPath('userData'), downloadFolder); 27 | } 28 | 29 | export async function showDownloadFolder() { 30 | await createFolder(getDownloadFolder()); 31 | remote.shell.showItemInFolder(getDownloadFolder()); 32 | } 33 | 34 | export async function clearDownloads() { 35 | fs.rmdirSync(getDownloadFolder(), { recursive: true }); 36 | } 37 | 38 | export function isDownloaded(illustID: number, feedDate: string) { 39 | let folderPath = path.resolve( 40 | remote.app.getPath('userData'), 41 | downloadFolder, 42 | feedDate, 43 | 'original' 44 | ); 45 | if (fs.existsSync(folderPath)) { 46 | let files = fs.readdirSync(folderPath); 47 | let fileBasenames = files.map((file) => file.split('.')[0]); 48 | if (fileBasenames.includes(illustID.toString())) return true; 49 | } 50 | return false; 51 | } 52 | 53 | export function downloadImage( 54 | url: string, 55 | illustID: number, 56 | feedDate: string, 57 | type: string, 58 | callback: any 59 | ) { 60 | axios 61 | .get(url, { 62 | responseType: 'arraybuffer', 63 | }) 64 | .then(async (res) => { 65 | let ext = res['headers']['content-type'].split('/')[1]; 66 | let filePath = makePath(illustID, feedDate, type, ext) 67 | saveImage(filePath, Buffer.from(res.data), callback); 68 | }); 69 | } 70 | 71 | export function makePath(illustID: number, feedDate: string, type:string, ext: string){ 72 | let folderPath = path.resolve( 73 | remote.app.getPath('userData'), 74 | `WallpaperDownloads/${feedDate}/${type}/` 75 | ); 76 | let filePath = path.resolve(folderPath, `${illustID}.${ext}`); 77 | return filePath; 78 | } 79 | 80 | export async function saveImage(filePath: string, imgData: Buffer, callback: any) { 81 | let folderPath = path.dirname(filePath); 82 | await createFolder(folderPath); 83 | fs.writeFileSync(filePath, imgData, 'binary'); 84 | callback(filePath); 85 | } 86 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/components/Editor/Editor.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react'; 2 | import { useSelector, useStore } from 'react-redux'; 3 | import { Button, Box, Grid } from '@material-ui/core'; 4 | import Cropper from 'react-cropper'; 5 | import GlobalStyles from '../../constants/styles.json'; 6 | import { saveImage, makePath } from '../DownloadManager/DownloadManager'; 7 | import StoreData from '../../constants/storeType'; 8 | import { setWallpaper } from '../WallpaperSetter/WallpaperSetter'; 9 | 10 | export default function Editor() { 11 | const editorSelection = useSelector( 12 | (state: StoreData) => state.editorSelection 13 | ); 14 | const cropperRef = useRef(null); 15 | let imageElement: any = null; 16 | let cropper: any = null; 17 | const onCrop = () => { 18 | imageElement = cropperRef?.current; 19 | cropper = imageElement?.cropper; 20 | }; 21 | 22 | const crop = () => { 23 | let imgData = cropper.getCroppedCanvas().toDataURL(); 24 | saveImage( 25 | makePath( 26 | editorSelection.illustID, 27 | editorSelection.feedDate, 28 | 'edited', 29 | 'png' 30 | ), 31 | Buffer.from(imgData.replace(/^data:image\/\w+;base64,/, ''), 'base64'), 32 | (filePath: string) => {setWallpaper(filePath);} 33 | ); 34 | }; 35 | 36 | return ( 37 |
47 |

48 | {editorSelection.path == '' 49 | ? 'No image selected. Please select an image to edit in the Ranking tab.' 50 | : 'Image loaded. Please specify crop region.'} 51 |

52 | 64 | 75 |
76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/components/Feed/Feed.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | bottom: 0; 3 | position: fixed; 4 | width: 100%; 5 | background-color: rgb(0, 150, 250); 6 | z-index: 10000; 7 | } -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/components/Feed/Feed.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Chip } from '@material-ui/core'; 3 | import styles from './Feed.css'; 4 | import { useSelector, useStore } from 'react-redux'; 5 | import { tick, reset } from '../../actions/feedTimerActions'; 6 | import { feedDateUpdate } from '../../actions/feedDateActions'; 7 | import { feedIllustUpdate } from '../../actions/feedIllustActions'; 8 | import StoreType, { IllustData } from '../../constants/storeType'; 9 | import moment from 'moment'; 10 | import axios from 'axios'; 11 | import { 12 | feedThumbnailUpdate, 13 | feedThumbnailReset, 14 | } from '../../actions/feedThumbnailActions'; 15 | import { 16 | feedDownloadUpdate, 17 | feedDownloadReset, 18 | } from '../../actions/feedDownloadActions'; 19 | import { appInitialized } from '../../actions/appInitializedActions'; 20 | import GlobalStyles from '../../constants/styles.json'; 21 | import { downloadImage } from '../DownloadManager/DownloadManager'; 22 | import { updateAPIURL } from '../../actions/APIConnectionActions'; 23 | 24 | let urlJoin = require('url-join'); 25 | let timer: any = null; 26 | let prevState: any = null; 27 | 28 | export default function Feed() { 29 | const store = useStore(); 30 | const timer_seconds = useSelector((state: StoreType) => state.feedTimer); 31 | 32 | let duration = moment.duration(timer_seconds, 'seconds'); 33 | 34 | function readLocalStorage() { 35 | try { 36 | let apiURLLS = 37 | localStorage.getItem('apiURL') == null 38 | ? '' 39 | : localStorage.getItem('apiURL'); 40 | let feedDateLS = 41 | localStorage.getItem('feedDate') == null 42 | ? '' 43 | : localStorage.getItem('feedDate'); 44 | 45 | let feedIllustLS = 46 | localStorage.getItem('feedIllust') == null 47 | ? [] 48 | : JSON.parse(localStorage.getItem('feedIllust')); 49 | let feedThumbnailLS = 50 | localStorage.getItem('feedThumbnail') == null 51 | ? {} 52 | : JSON.parse(localStorage.getItem('feedThumbnail')); 53 | let feedDownloadLS = 54 | localStorage.getItem('feedDownload') == null 55 | ? {} 56 | : JSON.parse(localStorage.getItem('feedDownload')); 57 | store.dispatch(updateAPIURL(apiURLLS)); 58 | store.dispatch(feedDateUpdate(feedDateLS)); 59 | store.dispatch(feedIllustUpdate(feedIllustLS)); 60 | for (const [key, value] of Object.entries(feedThumbnailLS)) { 61 | store.dispatch(feedThumbnailUpdate(parseInt(key), value)); 62 | } 63 | for (const [key, value] of Object.entries(feedDownloadLS)) { 64 | store.dispatch(feedDownloadUpdate(parseInt(key), value)); 65 | } 66 | } catch (e) { 67 | console.log(e); 68 | } 69 | } 70 | 71 | useEffect(() => { 72 | if ( 73 | store.getState().apiURL != prevState.apiURL || 74 | store.getState().appInitialized && 75 | prevState.feedTimer >= 0 && 76 | store.getState().feedTimer <= 0 77 | ) { 78 | store.dispatch(reset()); 79 | axios 80 | .get( 81 | urlJoin(store.getState().apiURL, 'pipeline', 'date', 'latest') 82 | ) 83 | .then((res) => { 84 | localStorage.setItem('feedDate', res.data.latest); 85 | store.dispatch(feedDateUpdate(res.data.latest)); 86 | }); 87 | } 88 | if ( 89 | store.getState().appInitialized && 90 | prevState.feedDate != store.getState().feedDate 91 | ) { 92 | axios 93 | .get( 94 | urlJoin(store.getState().apiURL, 'ranking', 'unique', store.getState().feedDate) 95 | ) 96 | .then((res) => { 97 | localStorage.setItem( 98 | 'feedIllust', 99 | JSON.stringify(res.data.illustData) 100 | ); 101 | store.dispatch(feedIllustUpdate(res.data.illustData)); 102 | }); 103 | } 104 | if ( 105 | store.getState().appInitialized && 106 | prevState.feedIllust != store.getState().feedIllust 107 | ) { 108 | store.dispatch(feedThumbnailReset()); 109 | localStorage.removeItem('feedThumbnail'); 110 | store.dispatch(feedDownloadReset()); 111 | localStorage.removeItem('feedDownload'); 112 | let currState: StoreType = store.getState(); 113 | currState.feedIllust.forEach((illust: IllustData) => { 114 | axios 115 | .get( 116 | urlJoin(store.getState().apiURL, 'image', 'thumbnail', illust.IllustID.toString()) 117 | ) 118 | .then((res) => { 119 | downloadImage( 120 | res.data.url, 121 | illust.IllustID, 122 | store.getState().feedDate, 123 | 'thumbnail', 124 | (filePath: string) => { 125 | store.dispatch(feedThumbnailUpdate(illust.IllustID, filePath)); 126 | localStorage.setItem( 127 | 'feedThumbnail', 128 | JSON.stringify(store.getState().feedThumbnail) 129 | ); 130 | } 131 | ); 132 | }); 133 | }); 134 | } 135 | prevState = store.getState(); 136 | }); 137 | 138 | useState(() => { 139 | if (timer === null) { 140 | timer = setInterval(() => { 141 | store.dispatch(tick()); 142 | }, 1000); 143 | } 144 | readLocalStorage(); 145 | store.dispatch(appInitialized()); 146 | prevState = store.getState(); 147 | }); 148 | 149 | return ( 150 |
154 | 164 |
165 | ); 166 | } 167 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/components/Home.css: -------------------------------------------------------------------------------- 1 | .container { 2 | text-align: center; 3 | } -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/components/Home.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import routes from '../constants/routes.json'; 4 | import { Button, Box, Grid } from '@material-ui/core'; 5 | import { makeStyles } from '@material-ui/core/styles'; 6 | import styles from './Home.css'; 7 | import { clearDownloads } from './DownloadManager/DownloadManager'; 8 | 9 | export default function Home() { 10 | return ( 11 |
12 |

Home

13 | {/* 14 | 15 | 24 | 25 | 26 | 27 | 30 | 31 | */} 32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/components/Navbar/Navbar.css: -------------------------------------------------------------------------------- 1 | .links { 2 | text-decoration: none; 3 | color: inherit; 4 | } 5 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/components/Navbar/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Drawer, 4 | IconButton, 5 | ListItem, 6 | List, 7 | Divider, 8 | ListItemIcon, 9 | ListItemText, 10 | } from '@material-ui/core'; 11 | import { Speed, Settings, FormatListNumbered, Palette } from '@material-ui/icons'; 12 | import { Link } from 'react-router-dom'; 13 | import { makeStyles } from '@material-ui/core/styles'; 14 | import styles from './Navbar.css'; 15 | import GlobalStyles from '../../constants/styles.json'; 16 | 17 | const drawerWidth = GlobalStyles.navWidth; 18 | 19 | const useStyles = makeStyles((theme) => ({ 20 | drawer: { 21 | width: drawerWidth, 22 | flexShrink: 0, 23 | }, 24 | drawerPaper: { 25 | width: drawerWidth, 26 | paddingTop: 25, 27 | }, 28 | })); 29 | 30 | export default function Navbar() { 31 | const classes = useStyles(); 32 | return ( 33 | 76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/components/Ranking/Ranking.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { 3 | Grid, 4 | Card, 5 | Box, 6 | CardActionArea, 7 | CardMedia, 8 | CardContent, 9 | CircularProgress, 10 | IconButton, 11 | } from '@material-ui/core'; 12 | import { useSelector, useStore } from 'react-redux'; 13 | import StoreData from '../../constants/storeType'; 14 | import { Chip, Switch, FormControlLabel } from '@material-ui/core'; 15 | import { makeStyles } from '@material-ui/core/styles'; 16 | import GlobalStyles from '../../constants/styles.json'; 17 | import { setWallpaper } from '../WallpaperSetter/WallpaperSetter'; 18 | import EmptyPatternImg from '../../res/imgs/empty_pattern_gray.png'; 19 | import axios from 'axios'; 20 | import process from 'process'; 21 | import { remote } from 'electron'; 22 | import path from 'path'; 23 | import { feedDownloadUpdate } from '../../actions/feedDownloadActions'; 24 | import { editorSelectionUpdate } from '../../actions/editorSelectorActions'; 25 | import fs from 'fs'; 26 | import { 27 | createFolder, 28 | downloadImage, 29 | } from '../DownloadManager/DownloadManager'; 30 | import CloudDownloadIcon from '@material-ui/icons/CloudDownload'; 31 | 32 | let urlJoin = require('url-join'); 33 | const useStyles = makeStyles({ 34 | root: { 35 | maxWidth: 240, 36 | }, 37 | media: { 38 | objectFit: 'contain', 39 | }, 40 | }); 41 | 42 | export default function Ranking() { 43 | const classes = useStyles(); 44 | const store = useStore(); 45 | const feedDate = useSelector((state: StoreData) => state.feedDate); 46 | const illusts = useSelector((state: StoreData) => state.feedIllust); 47 | const thumbnails = useSelector((state: StoreData) => state.feedThumbnail); 48 | const downloads = useSelector((state: StoreData) => state.feedDownload); 49 | const apiURL = useSelector((store: StoreData) => store.apiURL); 50 | 51 | const [state, setState] = useState({ 52 | hideAdult: true, 53 | }); 54 | 55 | const handleChange = (event: any) => { 56 | setState({ ...state, hideAdult: event.target.checked }); 57 | }; 58 | 59 | let downloadAndSet = async (illustID: number, feedDate: string) => { 60 | let imageUrl = await axios 61 | .get( 62 | urlJoin(apiURL, 'image', 'original', illustID.toString()) 63 | ) 64 | .then((res) => { 65 | return res.data.url; 66 | }); 67 | downloadImage( 68 | imageUrl, 69 | illustID, 70 | feedDate, 71 | 'original', 72 | (filePath: string) => { 73 | store.dispatch(feedDownloadUpdate(illustID, filePath)); 74 | store.dispatch(editorSelectionUpdate(illustID, filePath, feedDate)); 75 | setWallpaper(filePath); 76 | localStorage.setItem( 77 | 'feedDownload', 78 | JSON.stringify(store.getState().feedDownload) 79 | ); 80 | } 81 | ); 82 | }; 83 | 84 | const illustTemplate = ( 85 | id: number, 86 | illustID: number, 87 | adult: string, 88 | url: string, 89 | feedDate: string 90 | ) => { 91 | if ((adult == 'LIKELY' || adult == 'VERY_LIKELY') && state.hideAdult) { 92 | return ''; 93 | } else { 94 | return ( 95 | 96 | 97 | {adult == 'LIKELY' || adult == 'VERY_LIKELY' ? ( 98 | 99 | ) : ( 100 | 101 | )} 102 | 103 | 104 | { 106 | downloadAndSet(illustID, feedDate); 107 | }} 108 | > 109 | 110 | # {id} 111 | 112 | 117 | 118 | {illustID in downloads ? ( 119 | 120 | 121 | 122 | 123 | 124 | ) : null} 125 | 126 | 127 | ); 128 | } 129 | }; 130 | 131 | return ( 132 |
140 |

141 | Artworks can be missing because they have already appeared in previous 142 | feeds. 143 |

144 | 152 | } 153 | label="Hide adult content" 154 | /> 155 | 156 | {illusts.length == 0 ? : null} 157 | 158 | 159 | {illusts.map((data) => 160 | illustTemplate( 161 | data.Rank, 162 | data.IllustID, 163 | data.Adult, 164 | thumbnails[data.IllustID], 165 | feedDate 166 | ) 167 | )} 168 | 169 |
170 | ); 171 | } 172 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/components/Recommender/Recommender.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { useSelector, useStore } from 'react-redux'; 3 | import { remote } from 'electron'; 4 | import { ActionType as RecommenderDataActionType } from '../../reducers/recommenderDataReducer'; 5 | 6 | export default function Recommender() { 7 | const store = useStore(); 8 | useState(() => { 9 | const { width, height } = remote.screen.getPrimaryDisplay().size; 10 | let aspectRatio = width / height; 11 | let recommenderDataAction: RecommenderDataActionType = { 12 | type: 'UPDATE_AR', 13 | key: 'aspectRatio', 14 | data: aspectRatio, 15 | }; 16 | store.dispatch(recommenderDataAction); 17 | }); 18 | return null; 19 | } 20 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/components/Settings/Settings.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | const { getCurrentWindow } = require('electron').remote; 3 | import { 4 | FormControl, 5 | CardContent, 6 | Typography, 7 | Card, 8 | Box, 9 | Grid, 10 | Button, 11 | TextField, 12 | } from '@material-ui/core'; 13 | import { 14 | clearDownloads, 15 | showDownloadFolder, 16 | getDownloadFolder, 17 | getFolderSize, 18 | } from '../DownloadManager/DownloadManager'; 19 | import { useSelector, useStore } from 'react-redux'; 20 | import StoreType, { IllustData } from '../../constants/storeType'; 21 | import { Refresh } from '@material-ui/icons'; 22 | import GlobalStyles from '../../constants/styles.json'; 23 | import { updateAPIURL } from '../../actions/APIConnectionActions'; 24 | 25 | export default function Settings() { 26 | const store = useStore(); 27 | const sizeString = useSelector( 28 | (store: StoreType) => store.settings.downloadSizeString 29 | ); 30 | const apiURL = useSelector((store: StoreType) => store.apiURL); 31 | const [inputValue, setInputValue] = useState(''); 32 | function updateDLSize() { 33 | getFolderSize((sizeString: string) => { 34 | store.dispatch({ type: 'UPDATE_DL_SIZE', data: sizeString }); 35 | }); 36 | } 37 | useEffect(() => { 38 | updateDLSize(); 39 | }); 40 | 41 | return ( 42 |
49 |
50 |

Settings

51 |
52 | 53 | 54 | 55 | 67 | 68 | 69 | 79 | 80 | 81 | 91 | 92 | 93 | 94 |

API

95 |
96 |
97 |
98 | 99 | 100 | 101 | 102 | API Endpoint 103 | {`${apiURL}`} 106 | New API URL 107 | 108 | setInputValue(e.target.value)} 114 | /> 115 | 116 | 117 | 118 | 119 | 120 | 132 | 133 | 134 |
135 | 136 |

Download

137 |
138 |
139 | 140 | 141 | 142 | 143 | Download Folder 144 | {`${getDownloadFolder()}`} 147 | Download Size 148 | {`${sizeString}`} 149 | 150 | 151 | 152 | 153 | 163 | 164 | 165 | 175 | 176 | 177 | 188 | 189 | 190 |
191 |
192 | ); 193 | } 194 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/components/WallpaperSetter/WallpaperSetter.ts: -------------------------------------------------------------------------------- 1 | import { remote } from 'electron'; 2 | 3 | export async function setWallpaper(filePath: string, callback?: () => {}) { 4 | const setWallpaperFunction = remote.getGlobal('setWallpaper'); 5 | await setWallpaperFunction(filePath); 6 | if (callback != null) callback(); 7 | } 8 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/components/css.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.scss' { 2 | const content: { [className: string]: string }; 3 | export default content; 4 | } 5 | 6 | declare module '*.css' { 7 | const content: { [className: string]: string }; 8 | export default content; 9 | } 10 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/constants/defaultConfigs.json: -------------------------------------------------------------------------------- 1 | { 2 | "downloadFolder": "WallpaperDownloads", 3 | "feedRefreshRate": 3600 4 | } 5 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/constants/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "HOME": "/", 3 | "RANKING": "/ranking", 4 | "SETTINGS": "/settings", 5 | "EDITOR": "/editor" 6 | } 7 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/constants/storeType.ts: -------------------------------------------------------------------------------- 1 | import { StateType as SettingsStateType } from '../reducers/settingsReducer'; 2 | import { StateType as EditorSelectionStateType } from '../reducers/editorSelectionReducer'; 3 | 4 | export default interface StoreData { 5 | appInitialized: boolean; 6 | apiURL: string; 7 | feedTimer: number; 8 | feedDate: string; 9 | feedIllust: Array; 10 | feedThumbnail: { [key: number]: string }; 11 | feedDownload: { [key: number]: string }; 12 | settings: SettingsStateType; 13 | editorSelection: EditorSelectionStateType; 14 | } 15 | 16 | export interface IllustData { 17 | Rank: number; 18 | IllustID: number; 19 | Adult: string; 20 | } 21 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/constants/styles.json: -------------------------------------------------------------------------------- 1 | { "navWidth": 200, "footerHeight": 30 } 2 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/containers/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react'; 2 | 3 | type Props = { 4 | children: ReactNode; 5 | }; 6 | 7 | export default function App(props: Props) { 8 | const { children } = props; 9 | return <>{children}; 10 | } 11 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/containers/Root.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import { ConnectedRouter } from 'connected-react-router'; 4 | import { hot } from 'react-hot-loader/root'; 5 | import { History } from 'history'; 6 | import { Store } from '../store'; 7 | import Routes from '../Routes'; 8 | 9 | type Props = { 10 | store: Store; 11 | history: History; 12 | }; 13 | 14 | const Root = ({ store, history }: Props) => ( 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | 22 | export default hot(Root); 23 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import { render } from 'react-dom'; 3 | import { AppContainer as ReactHotAppContainer } from 'react-hot-loader'; 4 | import { history, configuredStore } from './store'; 5 | import './app.global.css'; 6 | 7 | const store = configuredStore(); 8 | 9 | const AppContainer = process.env.PLAIN_HMR ? Fragment : ReactHotAppContainer; 10 | 11 | document.addEventListener('DOMContentLoaded', () => { 12 | // eslint-disable-next-line global-require 13 | const Root = require('./containers/Root').default; 14 | render( 15 | 16 | 17 | , 18 | document.getElementById('root') 19 | ); 20 | }); 21 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/main.dev.ts: -------------------------------------------------------------------------------- 1 | /* eslint global-require: off, no-console: off */ 2 | 3 | /** 4 | * This module executes inside of electron's main process. You can start 5 | * electron renderer process from here and communicate with the other processes 6 | * through IPC. 7 | * 8 | * When running `yarn build` or `yarn build-main`, this file is compiled to 9 | * `./app/main.prod.js` using webpack. This gives us some performance wins. 10 | */ 11 | import 'core-js/stable'; 12 | import 'regenerator-runtime/runtime'; 13 | import path from 'path'; 14 | import { app, BrowserWindow } from 'electron'; 15 | import { autoUpdater } from 'electron-updater'; 16 | import log from 'electron-log'; 17 | import wallpaper from 'wallpaper'; 18 | import MenuBuilder from './menu'; 19 | 20 | global.setWallpaper = (filePath: string) => { 21 | wallpaper.set(filePath); 22 | }; 23 | 24 | export default class AppUpdater { 25 | constructor() { 26 | log.transports.file.level = 'info'; 27 | autoUpdater.logger = log; 28 | autoUpdater.checkForUpdatesAndNotify(); 29 | } 30 | } 31 | 32 | let mainWindow: BrowserWindow | null = null; 33 | 34 | if (process.env.NODE_ENV === 'production') { 35 | const sourceMapSupport = require('source-map-support'); 36 | sourceMapSupport.install(); 37 | } 38 | 39 | if ( 40 | process.env.NODE_ENV === 'development' || 41 | process.env.DEBUG_PROD === 'true' 42 | ) { 43 | require('electron-debug')(); 44 | } 45 | 46 | const installExtensions = async () => { 47 | const installer = require('electron-devtools-installer'); 48 | const forceDownload = !!process.env.UPGRADE_EXTENSIONS; 49 | const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS']; 50 | 51 | return Promise.all( 52 | extensions.map((name) => installer.default(installer[name], forceDownload)) 53 | ).catch(console.log); 54 | }; 55 | 56 | const createWindow = async () => { 57 | if ( 58 | process.env.NODE_ENV === 'development' || 59 | process.env.DEBUG_PROD === 'true' 60 | ) { 61 | await installExtensions(); 62 | } 63 | 64 | mainWindow = new BrowserWindow({ 65 | show: false, 66 | width: 1024, 67 | height: 728, 68 | webPreferences: 69 | (process.env.NODE_ENV === 'development' || 70 | process.env.E2E_BUILD === 'true') && 71 | process.env.ERB_SECURE !== 'true' 72 | ? { 73 | nodeIntegration: true, 74 | } 75 | : { 76 | preload: path.join(__dirname, 'dist/renderer.prod.js'), 77 | }, 78 | }); 79 | 80 | mainWindow.loadURL(`file://${__dirname}/app.html`); 81 | 82 | // @TODO: Use 'ready-to-show' event 83 | // https://github.com/electron/electron/blob/master/docs/api/browser-window.md#using-ready-to-show-event 84 | mainWindow.webContents.on('did-finish-load', () => { 85 | if (!mainWindow) { 86 | throw new Error('"mainWindow" is not defined'); 87 | } 88 | if (process.env.START_MINIMIZED) { 89 | mainWindow.minimize(); 90 | } else { 91 | mainWindow.show(); 92 | mainWindow.focus(); 93 | } 94 | }); 95 | 96 | mainWindow.on('closed', () => { 97 | mainWindow = null; 98 | }); 99 | 100 | const menuBuilder = new MenuBuilder(mainWindow); 101 | menuBuilder.buildMenu(); 102 | 103 | // Remove this if your app does not use auto updates 104 | // eslint-disable-next-line 105 | new AppUpdater(); 106 | }; 107 | 108 | /** 109 | * Add event listeners... 110 | */ 111 | 112 | app.on('window-all-closed', () => { 113 | // Respect the OSX convention of having the application in memory even 114 | // after all windows have been closed 115 | if (process.platform !== 'darwin') { 116 | app.quit(); 117 | } 118 | }); 119 | 120 | if (process.env.E2E_BUILD === 'true') { 121 | // eslint-disable-next-line promise/catch-or-return 122 | app.whenReady().then(createWindow); 123 | } else { 124 | app.on('ready', createWindow); 125 | } 126 | 127 | app.on('activate', () => { 128 | // On macOS it's common to re-create a window in the app when the 129 | // dock icon is clicked and there are no other windows open. 130 | if (mainWindow === null) createWindow(); 131 | }); 132 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pixiv-wallpaper-feed", 3 | "version": "2.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "wallpaper": { 8 | "version": "4.4.1", 9 | "resolved": "https://registry.npmjs.org/wallpaper/-/wallpaper-4.4.1.tgz", 10 | "integrity": "sha512-eHB4VgIwGmL0NAfghxKO4UY0YZbJyvyC3HKE6LOWtjvlMiT8/ywgVOWXd2+4E7pGPWrRY92scTw+e6bgoq66zQ==" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pixiv-wallpaper-feed", 3 | "version": "2.0.0", 4 | "main": "./main.prod.js", 5 | "scripts": { 6 | "electron-rebuild": "node -r ../internals/scripts/BabelRegister.js ../internals/scripts/ElectronRebuild.js", 7 | "postinstall": "yarn electron-rebuild" 8 | }, 9 | "license": "MIT", 10 | "dependencies": { 11 | "wallpaper": "^4.4.1" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/reducers/APIConnectionReducer.ts: -------------------------------------------------------------------------------- 1 | interface ActionType { 2 | type: string; 3 | data: string; 4 | } 5 | 6 | export default function APIConnectionReducer( 7 | state: string = "", 8 | action: ActionType 9 | ) { 10 | switch (action.type) { 11 | case 'API_LOADED': 12 | return action.data; 13 | default: 14 | return state; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/reducers/appInitializedReducer.ts: -------------------------------------------------------------------------------- 1 | interface ActionType { 2 | type: string; 3 | } 4 | 5 | export default function appInitializedReducer( 6 | state: Boolean = false, 7 | action: ActionType 8 | ) { 9 | switch (action.type) { 10 | case 'INIT_DONE': 11 | return true; 12 | default: 13 | return state; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/reducers/editorSelectionReducer.ts: -------------------------------------------------------------------------------- 1 | export interface StateType { 2 | illustID: number; 3 | path: string; 4 | feedDate: string; 5 | } 6 | 7 | interface ActionType { 8 | type: string; 9 | illustID: number; 10 | path: string; 11 | feedDate: string; 12 | } 13 | 14 | let initialState = { illustID: 0, path: '', feedDate: '' }; 15 | 16 | export default function editorSelectionReducer( 17 | state: StateType = initialState, 18 | action: ActionType 19 | ) { 20 | switch (action.type) { 21 | case 'SELECTION_UPDATE': 22 | return { 23 | illustID: action.illustID, 24 | path: action.path, 25 | feedDate: action.feedDate, 26 | }; 27 | case 'RESET_ALL': 28 | return initialState; 29 | default: 30 | return state; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/reducers/feedDateReducer.ts: -------------------------------------------------------------------------------- 1 | let initialState = ''; 2 | 3 | export default function feedDateReducer(state = initialState, action: any) { 4 | switch (action.type) { 5 | case 'DATE_UPDATE': 6 | return action.data; 7 | case 'RESET_ALL': 8 | return initialState; 9 | default: 10 | return state; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/reducers/feedDownloadReducer.ts: -------------------------------------------------------------------------------- 1 | interface ActionType { 2 | type: string; 3 | key: number; 4 | data: string; 5 | } 6 | 7 | export default function feedDownloadReducer( 8 | state: { [key: number]: string } = {}, 9 | action: ActionType 10 | ) { 11 | switch (action.type) { 12 | case 'DOWNLOAD_RESET': 13 | return {}; 14 | case 'DOWNLOAD_UPDATE': 15 | return { ...state, [action.key]: action.data }; 16 | default: 17 | return state; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/reducers/feedIllustReducer.ts: -------------------------------------------------------------------------------- 1 | let initialState = []; 2 | 3 | export default function feedIllustReducer(state = initialState, action: any) { 4 | switch (action.type) { 5 | case 'ILLUST_UPDATE': 6 | return action.data; 7 | case 'RESET_ALL': 8 | return initialState; 9 | default: 10 | return state; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/reducers/feedThumbnailReducer.ts: -------------------------------------------------------------------------------- 1 | interface ActionType { 2 | type: string; 3 | key: number; 4 | data: string; 5 | } 6 | 7 | let initialState = {}; 8 | 9 | export default function feedThumbnailReducer( 10 | state: { [key: number]: string } = initialState, 11 | action: ActionType 12 | ) { 13 | switch (action.type) { 14 | case 'THUMBNAIL_RESET': 15 | return {}; 16 | case 'THUMBNAIL_UPDATE': 17 | return { ...state, [action.key]: action.data }; 18 | case 'RESET_ALL': 19 | return initialState; 20 | default: 21 | return state; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/reducers/feedTimerReducer.ts: -------------------------------------------------------------------------------- 1 | import { feedRefreshRate } from '../constants/defaultConfigs.json'; 2 | 3 | export default function feedTimerReducer(state = 0, action: any) { 4 | switch (action.type) { 5 | case 'TICK': 6 | return state - 1; 7 | case 'RESET': 8 | return feedRefreshRate; 9 | default: 10 | return state; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/reducers/recommenderDataReducer.ts: -------------------------------------------------------------------------------- 1 | export interface RecommenderDataType { 2 | aspectRatio: number; 3 | } 4 | 5 | const initialState: RecommenderDataType = { 6 | aspectRatio: 0, 7 | }; 8 | 9 | export interface ActionType { 10 | type: string; 11 | key: string; 12 | data: any; 13 | } 14 | 15 | export default function recommenderDataReducer( 16 | state: RecommenderDataType = initialState, 17 | action: ActionType 18 | ) { 19 | switch (action.type) { 20 | case 'UPDATE_AR': 21 | return { ...state, [action.key]: action.data }; 22 | default: 23 | return state; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/reducers/settingsReducer.ts: -------------------------------------------------------------------------------- 1 | export interface StateType { 2 | downloadSizeString: string; 3 | } 4 | 5 | const initialState = { 6 | downloadSizeString: '', 7 | }; 8 | 9 | export default function settingsReducer( 10 | state: StateType = initialState, 11 | action: any 12 | ) { 13 | switch (action.type) { 14 | case 'UPDATE_DL_SIZE': 15 | return { ...state, downloadSizeString: action.data }; 16 | default: 17 | return state; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/res/imgs/empty_pattern_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/app/res/imgs/empty_pattern_gray.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/rootReducer.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { connectRouter } from 'connected-react-router'; 3 | import { History } from 'history'; 4 | import feedTimerReducer from './reducers/feedTimerReducer'; 5 | import feedDateReducer from './reducers/feedDateReducer'; 6 | import feedIllustReducer from './reducers/feedIllustReducer'; 7 | import feedThumbnailReducer from './reducers/feedThumbnailReducer'; 8 | import appInitializedReducer from './reducers/appInitializedReducer'; 9 | import settingsReducer from './reducers/settingsReducer'; 10 | import feedDownloadReducer from './reducers/feedDownloadReducer'; 11 | import recommenderDataReducer from './reducers/recommenderDataReducer'; 12 | import editorSelectionReducer from './reducers/editorSelectionReducer'; 13 | import APIConnectionReducer from './reducers/APIConnectionReducer'; 14 | // eslint-disable-next-line import/no-cycle 15 | 16 | export default function createRootReducer(history: History) { 17 | return combineReducers({ 18 | router: connectRouter(history), 19 | appInitialized: appInitializedReducer, 20 | apiURL: APIConnectionReducer, 21 | feedTimer: feedTimerReducer, 22 | feedDate: feedDateReducer, 23 | feedIllust: feedIllustReducer, 24 | feedThumbnail: feedThumbnailReducer, 25 | settings: settingsReducer, 26 | feedDownload: feedDownloadReducer, 27 | recommenderData: recommenderDataReducer, 28 | editorSelection: editorSelectionReducer, 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore, getDefaultMiddleware, Action } from '@reduxjs/toolkit'; 2 | import { createHashHistory } from 'history'; 3 | import { routerMiddleware } from 'connected-react-router'; 4 | import { createLogger } from 'redux-logger'; 5 | import { ThunkAction } from 'redux-thunk'; 6 | // eslint-disable-next-line import/no-cycle 7 | import createRootReducer from './rootReducer'; 8 | 9 | export const history = createHashHistory(); 10 | const rootReducer = createRootReducer(history); 11 | export type RootState = ReturnType; 12 | 13 | const router = routerMiddleware(history); 14 | const middleware = [...getDefaultMiddleware(), router]; 15 | 16 | const excludeLoggerEnvs = ['test', 'production']; 17 | const shouldIncludeLogger = !excludeLoggerEnvs.includes( 18 | process.env.NODE_ENV || '' 19 | ); 20 | 21 | if (shouldIncludeLogger) { 22 | const logger = createLogger({ 23 | level: 'info', 24 | collapsed: true, 25 | }); 26 | middleware.push(logger); 27 | } 28 | 29 | export const configuredStore = (initialState?: RootState) => { 30 | // Create Store 31 | const store = configureStore({ 32 | reducer: rootReducer, 33 | middleware, 34 | preloadedState: initialState, 35 | }); 36 | 37 | if (process.env.NODE_ENV === 'development' && module.hot) { 38 | module.hot.accept( 39 | './rootReducer', 40 | // eslint-disable-next-line global-require 41 | () => store.replaceReducer(require('./rootReducer').default) 42 | ); 43 | } 44 | return store; 45 | }; 46 | export type Store = ReturnType; 47 | export type AppThunk = ThunkAction>; 48 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/utils/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/app/utils/.gitkeep -------------------------------------------------------------------------------- /Client/PWF_Desktop/app/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | wallpaper@^4.4.1: 6 | version "4.4.1" 7 | resolved "https://registry.yarnpkg.com/wallpaper/-/wallpaper-4.4.1.tgz#45d8c3236e0937a742a3ce5b27fc239b73bb8859" 8 | integrity sha512-eHB4VgIwGmL0NAfghxKO4UY0YZbJyvyC3HKE6LOWtjvlMiT8/ywgVOWXd2+4E7pGPWrRY92scTw+e6bgoq66zQ== 9 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/babel.config.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: off, import/no-extraneous-dependencies: off */ 2 | 3 | const developmentEnvironments = ['development', 'test']; 4 | 5 | const developmentPlugins = [require('react-hot-loader/babel')]; 6 | 7 | const productionPlugins = [ 8 | require('babel-plugin-dev-expression'), 9 | 10 | // babel-preset-react-optimize 11 | require('@babel/plugin-transform-react-constant-elements'), 12 | require('@babel/plugin-transform-react-inline-elements'), 13 | require('babel-plugin-transform-react-remove-prop-types'), 14 | ]; 15 | 16 | module.exports = (api) => { 17 | // See docs about api at https://babeljs.io/docs/en/config-files#apicache 18 | 19 | const development = api.env(developmentEnvironments); 20 | 21 | return { 22 | presets: [ 23 | // @babel/preset-env will automatically target our browserslist targets 24 | require('@babel/preset-env'), 25 | require('@babel/preset-typescript'), 26 | [require('@babel/preset-react'), { development }], 27 | ], 28 | plugins: [ 29 | // Stage 0 30 | require('@babel/plugin-proposal-function-bind'), 31 | 32 | // Stage 1 33 | require('@babel/plugin-proposal-export-default-from'), 34 | require('@babel/plugin-proposal-logical-assignment-operators'), 35 | [require('@babel/plugin-proposal-optional-chaining'), { loose: false }], 36 | [ 37 | require('@babel/plugin-proposal-pipeline-operator'), 38 | { proposal: 'minimal' }, 39 | ], 40 | [ 41 | require('@babel/plugin-proposal-nullish-coalescing-operator'), 42 | { loose: false }, 43 | ], 44 | require('@babel/plugin-proposal-do-expressions'), 45 | 46 | // Stage 2 47 | [require('@babel/plugin-proposal-decorators'), { legacy: true }], 48 | require('@babel/plugin-proposal-function-sent'), 49 | require('@babel/plugin-proposal-export-namespace-from'), 50 | require('@babel/plugin-proposal-numeric-separator'), 51 | require('@babel/plugin-proposal-throw-expressions'), 52 | 53 | // Stage 3 54 | require('@babel/plugin-syntax-dynamic-import'), 55 | require('@babel/plugin-syntax-import-meta'), 56 | [require('@babel/plugin-proposal-class-properties'), { loose: true }], 57 | require('@babel/plugin-proposal-json-strings'), 58 | 59 | ...(development ? developmentPlugins : productionPlugins), 60 | ], 61 | }; 62 | }; 63 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/configs/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/configs/webpack.config.base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Base webpack config used across other specific configs 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import { dependencies as externals } from '../app/package.json'; 8 | 9 | export default { 10 | externals: [...Object.keys(externals || {})], 11 | 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.tsx?$/, 16 | exclude: /node_modules/, 17 | use: { 18 | loader: 'babel-loader', 19 | options: { 20 | cacheDirectory: true, 21 | }, 22 | }, 23 | }, 24 | ], 25 | }, 26 | 27 | output: { 28 | path: path.join(__dirname, '..', 'app'), 29 | // https://github.com/webpack/webpack/issues/1114 30 | libraryTarget: 'commonjs2', 31 | }, 32 | 33 | /** 34 | * Determine the array of extensions that should be used to resolve modules. 35 | */ 36 | resolve: { 37 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'], 38 | modules: [path.join(__dirname, '..', 'app'), 'node_modules'], 39 | }, 40 | 41 | plugins: [ 42 | new webpack.EnvironmentPlugin({ 43 | NODE_ENV: 'production', 44 | }), 45 | 46 | new webpack.NamedModulesPlugin(), 47 | ], 48 | }; 49 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/configs/webpack.config.eslint.js: -------------------------------------------------------------------------------- 1 | /* eslint import/no-unresolved: off, import/no-self-import: off */ 2 | require('@babel/register'); 3 | 4 | module.exports = require('./webpack.config.renderer.dev.babel').default; 5 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/configs/webpack.config.main.prod.babel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack config for production electron main process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import { merge } from 'webpack-merge'; 8 | import TerserPlugin from 'terser-webpack-plugin'; 9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 10 | import baseConfig from './webpack.config.base'; 11 | import CheckNodeEnv from '../internals/scripts/CheckNodeEnv'; 12 | import DeleteSourceMaps from '../internals/scripts/DeleteSourceMaps'; 13 | 14 | CheckNodeEnv('production'); 15 | DeleteSourceMaps(); 16 | 17 | export default merge(baseConfig, { 18 | devtool: process.env.DEBUG_PROD === 'true' ? 'source-map' : 'none', 19 | 20 | mode: 'production', 21 | 22 | target: 'electron-main', 23 | 24 | entry: './app/main.dev.ts', 25 | 26 | output: { 27 | path: path.join(__dirname, '..'), 28 | filename: './app/main.prod.js', 29 | }, 30 | 31 | optimization: { 32 | minimizer: process.env.E2E_BUILD 33 | ? [] 34 | : [ 35 | new TerserPlugin({ 36 | parallel: true, 37 | sourceMap: true, 38 | cache: true, 39 | }), 40 | ], 41 | }, 42 | 43 | plugins: [ 44 | new BundleAnalyzerPlugin({ 45 | analyzerMode: 46 | process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled', 47 | openAnalyzer: process.env.OPEN_ANALYZER === 'true', 48 | }), 49 | 50 | /** 51 | * Create global constants which can be configured at compile time. 52 | * 53 | * Useful for allowing different behaviour between development builds and 54 | * release builds 55 | * 56 | * NODE_ENV should be production so that modules do not perform certain 57 | * development checks 58 | */ 59 | new webpack.EnvironmentPlugin({ 60 | NODE_ENV: 'production', 61 | DEBUG_PROD: false, 62 | START_MINIMIZED: false, 63 | E2E_BUILD: false, 64 | }), 65 | ], 66 | 67 | /** 68 | * Disables webpack processing of __dirname and __filename. 69 | * If you run the bundle in node.js it falls back to these values of node.js. 70 | * https://github.com/webpack/webpack/issues/2010 71 | */ 72 | node: { 73 | __dirname: false, 74 | __filename: false, 75 | }, 76 | }); 77 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/configs/webpack.config.renderer.dev.dll.babel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds the DLL for development electron renderer process 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import path from 'path'; 7 | import { merge } from 'webpack-merge'; 8 | import baseConfig from './webpack.config.base'; 9 | import { dependencies } from '../package.json'; 10 | import CheckNodeEnv from '../internals/scripts/CheckNodeEnv'; 11 | 12 | CheckNodeEnv('development'); 13 | 14 | const dist = path.join(__dirname, '..', 'dll'); 15 | 16 | export default merge(baseConfig, { 17 | context: path.join(__dirname, '..'), 18 | 19 | devtool: 'eval', 20 | 21 | mode: 'development', 22 | 23 | target: 'electron-renderer', 24 | 25 | externals: ['fsevents', 'crypto-browserify'], 26 | 27 | /** 28 | * Use `module` from `webpack.config.renderer.dev.js` 29 | */ 30 | module: require('./webpack.config.renderer.dev.babel').default.module, 31 | 32 | entry: { 33 | renderer: Object.keys(dependencies || {}), 34 | }, 35 | 36 | output: { 37 | library: 'renderer', 38 | path: dist, 39 | filename: '[name].dev.dll.js', 40 | libraryTarget: 'var', 41 | }, 42 | 43 | plugins: [ 44 | new webpack.DllPlugin({ 45 | path: path.join(dist, '[name].json'), 46 | name: '[name]', 47 | }), 48 | 49 | /** 50 | * Create global constants which can be configured at compile time. 51 | * 52 | * Useful for allowing different behaviour between development builds and 53 | * release builds 54 | * 55 | * NODE_ENV should be production so that modules do not perform certain 56 | * development checks 57 | */ 58 | new webpack.EnvironmentPlugin({ 59 | NODE_ENV: 'development', 60 | }), 61 | 62 | new webpack.LoaderOptionsPlugin({ 63 | debug: true, 64 | options: { 65 | context: path.join(__dirname, '..', 'app'), 66 | output: { 67 | path: path.join(__dirname, '..', 'dll'), 68 | }, 69 | }, 70 | }), 71 | ], 72 | }); 73 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/erb-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/erb-banner.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/erb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/erb-logo.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/eslint-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/eslint-padded-90.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/eslint-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/eslint-padded.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/eslint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/eslint.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/jest-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/jest-padded-90.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/jest-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/jest-padded.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/jest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/jest.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/js-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/js-padded.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/js.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/npm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/npm.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/react-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/react-padded-90.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/react-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/react-padded.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/react-router-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/react-router-padded-90.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/react-router-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/react-router-padded.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/react-router.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/react-router.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/react.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/redux-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/redux-padded-90.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/redux-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/redux-padded.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/redux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/redux.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/webpack-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/webpack-padded-90.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/webpack-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/webpack-padded.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/webpack.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/yarn-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/yarn-padded-90.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/yarn-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/yarn-padded.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/img/yarn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/internals/img/yarn.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/mocks/fileMock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/scripts/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off", 6 | "import/no-extraneous-dependencies": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/scripts/BabelRegister.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | require('@babel/register')({ 4 | extensions: ['.es6', '.es', '.jsx', '.js', '.mjs', '.ts', '.tsx'], 5 | cwd: path.join(__dirname, '..', '..'), 6 | }); 7 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/scripts/CheckBuildsExist.js: -------------------------------------------------------------------------------- 1 | // Check if the renderer and main bundles are built 2 | import path from 'path'; 3 | import chalk from 'chalk'; 4 | import fs from 'fs'; 5 | 6 | const mainPath = path.join(__dirname, '..', '..', 'app', 'main.prod.js'); 7 | const rendererPath = path.join( 8 | __dirname, 9 | '..', 10 | '..', 11 | 'app', 12 | 'dist', 13 | 'renderer.prod.js' 14 | ); 15 | 16 | if (!fs.existsSync(mainPath)) { 17 | throw new Error( 18 | chalk.whiteBright.bgRed.bold( 19 | 'The main process is not built yet. Build it by running "yarn build-main"' 20 | ) 21 | ); 22 | } 23 | 24 | if (!fs.existsSync(rendererPath)) { 25 | throw new Error( 26 | chalk.whiteBright.bgRed.bold( 27 | 'The renderer process is not built yet. Build it by running "yarn build-renderer"' 28 | ) 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/scripts/CheckNativeDep.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import chalk from 'chalk'; 3 | import { execSync } from 'child_process'; 4 | import { dependencies } from '../../package.json'; 5 | 6 | if (dependencies) { 7 | const dependenciesKeys = Object.keys(dependencies); 8 | const nativeDeps = fs 9 | .readdirSync('node_modules') 10 | .filter((folder) => fs.existsSync(`node_modules/${folder}/binding.gyp`)); 11 | try { 12 | // Find the reason for why the dependency is installed. If it is installed 13 | // because of a devDependency then that is okay. Warn when it is installed 14 | // because of a dependency 15 | const { dependencies: dependenciesObject } = JSON.parse( 16 | execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString() 17 | ); 18 | const rootDependencies = Object.keys(dependenciesObject); 19 | const filteredRootDependencies = rootDependencies.filter((rootDependency) => 20 | dependenciesKeys.includes(rootDependency) 21 | ); 22 | if (filteredRootDependencies.length > 0) { 23 | const plural = filteredRootDependencies.length > 1; 24 | console.log(` 25 | ${chalk.whiteBright.bgYellow.bold( 26 | 'Webpack does not work with native dependencies.' 27 | )} 28 | ${chalk.bold(filteredRootDependencies.join(', '))} ${ 29 | plural ? 'are native dependencies' : 'is a native dependency' 30 | } and should be installed inside of the "./app" folder. 31 | First, uninstall the packages from "./package.json": 32 | ${chalk.whiteBright.bgGreen.bold('yarn remove your-package')} 33 | ${chalk.bold( 34 | 'Then, instead of installing the package to the root "./package.json":' 35 | )} 36 | ${chalk.whiteBright.bgRed.bold('yarn add your-package')} 37 | ${chalk.bold('Install the package to "./app/package.json"')} 38 | ${chalk.whiteBright.bgGreen.bold('cd ./app && yarn add your-package')} 39 | Read more about native dependencies at: 40 | ${chalk.bold( 41 | 'https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure' 42 | )} 43 | `); 44 | process.exit(1); 45 | } 46 | } catch (e) { 47 | console.log('Native dependencies could not be checked'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/scripts/CheckNodeEnv.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | export default function CheckNodeEnv(expectedEnv) { 4 | if (!expectedEnv) { 5 | throw new Error('"expectedEnv" not set'); 6 | } 7 | 8 | if (process.env.NODE_ENV !== expectedEnv) { 9 | console.log( 10 | chalk.whiteBright.bgRed.bold( 11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config` 12 | ) 13 | ); 14 | process.exit(2); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/scripts/CheckPortInUse.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import detectPort from 'detect-port'; 3 | 4 | const port = process.env.PORT || '1212'; 5 | 6 | detectPort(port, (err, availablePort) => { 7 | if (port !== String(availablePort)) { 8 | throw new Error( 9 | chalk.whiteBright.bgRed.bold( 10 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 yarn dev` 11 | ) 12 | ); 13 | } else { 14 | process.exit(0); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/scripts/CheckYarn.js: -------------------------------------------------------------------------------- 1 | if (!/yarn\.js$/.test(process.env.npm_execpath || '')) { 2 | console.warn( 3 | "\u001b[33mYou don't seem to be using yarn. This could produce unexpected results.\u001b[39m" 4 | ); 5 | } 6 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/scripts/DeleteSourceMaps.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import rimraf from 'rimraf'; 3 | 4 | export default function deleteSourceMaps() { 5 | rimraf.sync(path.join(__dirname, '../../app/dist/*.js.map')); 6 | rimraf.sync(path.join(__dirname, '../../app/*.js.map')); 7 | } 8 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/internals/scripts/ElectronRebuild.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { execSync } from 'child_process'; 3 | import fs from 'fs'; 4 | import { dependencies } from '../../app/package.json'; 5 | 6 | const nodeModulesPath = path.join(__dirname, '..', '..', 'app', 'node_modules'); 7 | 8 | if ( 9 | Object.keys(dependencies || {}).length > 0 && 10 | fs.existsSync(nodeModulesPath) 11 | ) { 12 | const electronRebuildCmd = 13 | '../node_modules/.bin/electron-rebuild --parallel --force --types prod,dev,optional --module-dir .'; 14 | const cmd = 15 | process.platform === 'win32' 16 | ? electronRebuildCmd.replace(/\//g, '\\') 17 | : electronRebuildCmd; 18 | execSync(cmd, { 19 | cwd: path.join(__dirname, '..', '..', 'app'), 20 | stdio: 'inherit', 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /Client/PWF_Desktop/resources/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/resources/icon.icns -------------------------------------------------------------------------------- /Client/PWF_Desktop/resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/resources/icon.ico -------------------------------------------------------------------------------- /Client/PWF_Desktop/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Client/PWF_Desktop/resources/icon.png -------------------------------------------------------------------------------- /Client/PWF_Desktop/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "CommonJS", 5 | "lib": ["dom", "esnext"], 6 | "declaration": true, 7 | "declarationMap": true, 8 | "noEmit": true, 9 | "jsx": "react", 10 | "strict": true, 11 | "pretty": true, 12 | "sourceMap": true, 13 | /* Additional Checks */ 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true, 18 | /* Module Resolution Options */ 19 | "moduleResolution": "node", 20 | "esModuleInterop": true, 21 | "allowSyntheticDefaultImports": true, 22 | "resolveJsonModule": true, 23 | "allowJs": true 24 | }, 25 | "exclude": [ 26 | "test", 27 | "release", 28 | "app/main.prod.js", 29 | "app/renderer.prod.js", 30 | "app/dist", 31 | "dll" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /Demo/pick_wallpaper_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Demo/pick_wallpaper_demo.gif -------------------------------------------------------------------------------- /Demo/set_wallpaper_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Demo/set_wallpaper_demo.gif -------------------------------------------------------------------------------- /Docs/PWF Architecture Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Docs/PWF Architecture Diagram.png -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 SingularityF 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.cn.md: -------------------------------------------------------------------------------- 1 | ## 下载 2 | 3 | 请[点击此处](https://github.com/SingularityF/PixivWallpaper/releases)下载最新版(支持64位Windows) 4 | 5 | ## 功能演示 6 | 7 | 为你推荐[今日最佳](https://singf.space/pixiv/controls/demo)Pixiv插画,只需要输入你的屏幕分辨率,我们的系统就会找到最适合你的壁纸。点击上方连接下载客户端即可将今日最佳设为你的桌面! 8 | 9 | ## 功能 10 | 11 | 此程序能够从Pixiv每日排行的插画中,通过智能选择算法为你的显示器适配最合适的插画作为壁纸。你只需要双击程序就能够实现更换壁纸,如果你不喜欢程序为你选择的壁纸,你还可以使用此程序选择你喜欢的壁纸。如果你对壁纸的分辨率不够满意,你还可以利用程序自带的[waifu2x](https://github.com/nagadomi/waifu2x)来放大图像。 12 | 13 | ## 使用方法 14 | 15 | 双击`set_wallpaper.exe`即可一键更换壁纸。运行`pixivwallpaper_gui.exe`来查看更多选项。 16 | -------------------------------------------------------------------------------- /README.jp.md: -------------------------------------------------------------------------------- 1 | ## ダウンロード 2 | 3 | [こちらから](https://github.com/SingularityF/PixivWallpaper/releases)ダウンロードできます(64ビットのシステムでないと起動できません) 4 | 5 | 6 | ## デモ 7 | 8 | 今日の一番いいイラストはどれでしょう?[こちらに](https://singf.space/pixiv/controls/demo)お使いのモニターの解像度を入力すればすぐわかります!プログラムをダウンロードすれば簡単にお気に入りのイラストを壁紙にできます! 9 | 10 | ## 説明 11 | 12 | このソフトがお使いのモニターの解像度から一番見栄えのいいイラストをPixivデイリーランキングから選んで、壁紙にできます。選択の基準はソフトが自動で決めてくれますので見比べは全くいりません!たとえ選択の結果が望ましくなくてもいつでも自由に変更できます。[waifu2x](https://github.com/nagadomi/waifu2x)がビルドリンされているため、画像を自由に拡大できます。 13 | 14 | ## 使い方 15 | 16 | `set_wallpaper.exe`を実行するだけで壁紙をセットできます。`pixivwallpaper_gui.exe`を使用したらもっといろんな機能が使えます。 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PixivWallpaper 2 | 3 | [![License](https://img.shields.io/github/license/SingularityF/PixivWallpaper.svg)](https://opensource.org/licenses/MIT) 4 | [![Download All Releases](https://img.shields.io/github/downloads/SingularityF/PixivWallpaper/total.svg)](https://github.com/SingularityF/PixivWallpaper/releases) 5 | [![GitHub release](https://img.shields.io/github/release/SingularityF/PixivWallpaper.svg)](https://github.com/SingularityF/PixivWallpaper/releases/latest) 6 | 7 | Set Windows wallpaper with daily Pixiv high ranking illustrations 8 | 9 | 将p站每日排行设为壁纸/桌布 [使用说明&下载](https://github.com/SingularityF/PixivWallpaper/blob/master/README.cn.md) 10 | 11 | Pixivのイラストデイリーランキングから一番いいのを壁紙にするソフトです [使い方&ダウンロード](https://github.com/SingularityF/PixivWallpaper/blob/master/README.jp.md) 12 | 13 | ## ⚠ANOUNCEMENT⚠ 14 | This repo is not actively maintained right now. The following sections have outdated information. However, there is a beta version available that can be built from the Client/PWF_Desktop folder. You can use https://us-central1-pixivwallpaper.cloudfunctions.net/PWF_backend as the API URL in the Settings tab. Please note that this beta version is a work in progress and can have bugs. 15 | 16 | ## Demo 17 | 18 | Decorate your desktop with just a simple click. 19 | 20 | ![Set Wallpaper Demo](Demo/set_wallpaper_demo.gif) 21 | 22 | Don't like it? Then pick your own favorite. 23 | 24 | ![Pick Wallpaper Demo](Demo/pick_wallpaper_demo.gif) 25 | 26 | Find out the best wallpaper from today's Pixiv daily rankings recommended by our application with our [demo](https://singf.space/pixiv/controls/demo) here! 27 | 28 | ## Description & Features 29 | 30 | Explore the features offered by the application with our [web user interface](http://singf.space/pixiv/controls). 31 | 32 | This application, running on Windows and Mac (with limited features), sets the **best looking** illustration from [Pixiv Daily Rankings](https://www.pixiv.net/ranking.php?mode=daily&content=illust) as your wallpaper based on your screen resolution and a smart image selection algorithm. 33 | 34 | Pixiv is the best place to explore anime style artworks. If you like anime wallpapers, you will like this app! 35 | 36 | You can enable the built-in swapper to automatically change your wallpaper every day. 37 | 38 | ~The application also has [waifu2x](https://github.com/nagadomi/waifu2x) built-in so you'll get a high-resolution image even if the creator only uploaded a low resolution illustration.~ 39 | 40 | ## Download 41 | 42 | Download the latest binary from [here](https://github.com/SingularityF/PixivWallpaper/releases) if your machine is running 64-bit Windows or Mac OS. 43 | -------------------------------------------------------------------------------- /Server/README.md: -------------------------------------------------------------------------------- 1 | # Server application setup guide 2 | 3 | The server side applications are mainly divided into three parts, namely database, pipeline and web services. 4 | 5 | ## Configure the database server 6 | 7 | Use the `pixiv_db_schema.sql` file as a guide to create a new database. The RDBMS I'm using is MariaDB(MySQL). You should create two users, the two should have SELECT and INSERT privilege, used by `upload_user.php` and `paper_user.php` respectively. 8 | 9 | ## Configure the web services server 10 | 11 | This server will handle the client requests and provide GUI for users. 12 | 13 | For backend, just set up the server as a web server that runs php and add database login information to `paper_user.php`. 14 | 15 | To provide GUI for users, you have to compile the angular project first, then copy the compiled files to where you want the URL to be. This application uses routing, so you may want to copy `.htaccess` file to where you placed the files if you're using apache. Also change the base-url in the compiled `index.html` if you're not using the root path. 16 | 17 | Notice that some links are hardcoded to my domain, you will have to change them if you want to set up your version of user interface. 18 | 19 | ## Configure pipeline server 20 | 21 | Follow the guides in the `pipeline` folder. 22 | -------------------------------------------------------------------------------- /Server/backend/Old/paper_user.php: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /Server/backend/Old/pick_paper.php: -------------------------------------------------------------------------------- 1 | setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 10 | 11 | 12 | header("Access-Control-Allow-Origin: *"); 13 | header("Access-Control-Allow-Methods: PUT, GET, POST"); 14 | header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept"); 15 | 16 | function isValidJSON($str) { 17 | json_decode($str); 18 | return json_last_error() == JSON_ERROR_NONE; 19 | } 20 | 21 | $json_params = file_get_contents("php://input"); 22 | 23 | if (strlen($json_params) > 0 && isValidJSON($json_params)) 24 | $jpost= json_decode($json_params,true); 25 | else 26 | die("Request error"); 27 | 28 | if( is_null($jpost["uuid"]) || is_null($jpost["illustID"]) || is_null($jpost["timestamp"])){ 29 | die("Request error"); 30 | }else{ 31 | $query=$pdo->prepare("INSERT INTO user_selection(MacAddr,SelectedIllust,TimeStamp) VALUES(:macaddr,:illustID,:timestamp)"); 32 | $query->bindParam(":macaddr",$jpost["uuid"]); 33 | $query->bindParam(":illustID",$jpost["illustID"]); 34 | $query->bindParam(":timestamp",$jpost["timestamp"]); 35 | 36 | $query->execute(); 37 | echo "successful"; 38 | } 39 | ?> 40 | -------------------------------------------------------------------------------- /Server/backend/Old/ranking_gallery.php: -------------------------------------------------------------------------------- 1 | prepare("SELECT * FROM todays_best"); 16 | 17 | $query->execute(); 18 | 19 | while($res=$query->fetch(PDO::FETCH_ASSOC)){ 20 | $rows[]=$res; 21 | } 22 | 23 | for($i=0;$i 31 | -------------------------------------------------------------------------------- /Server/backend/Old/select_paper.php: -------------------------------------------------------------------------------- 1 | setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 12 | 13 | $query=$pdo->prepare("SELECT A.*, B.Score FROM images_l A, (SELECT *,(AvgGradient-POW(7*ABS(AspectRatio-?),2)-POW(10*Variance,2)) AS Score FROM todays_best ORDER BY Score DESC LIMIT 1) B WHERE A.IllustID=B.IllustID"); 14 | $query_selection=$pdo->prepare(" 15 | SELECT IllustID FROM todays_best 16 | WHERE IllustID= 17 | (SELECT SelectedIllust FROM user_selection 18 | WHERE MacAddr=? 19 | ORDER BY DateCreated DESC 20 | LIMIT 1) 21 | "); 22 | $query_pinpoint=$pdo->prepare(" 23 | SELECT * FROM images_l 24 | WHERE IllustID=? 25 | "); 26 | $query_demo=$pdo->prepare("SELECT A.*, B.Score FROM images A, (SELECT *,(AvgGradient-POW(7*ABS(AspectRatio-?),2)-POW(10*Variance,2)) AS Score FROM todays_best ORDER BY Score DESC LIMIT 1) B WHERE A.IllustID=B.IllustID"); 27 | $query_thumb=$pdo->prepare("SELECT Image FROM images_t WHERE IllustID=?"); 28 | 29 | $ratio=$_POST["ar"]; 30 | $mac=$_POST["uuid"]; 31 | $version=$_POST["version"]; 32 | 33 | $demo=$_GET["demo"]; 34 | $ratio_g=$_GET["ar"]; 35 | $img_info=$_GET["info"]; 36 | 37 | $thumb=$_GET["thumb"]; 38 | $illustID=$_GET["id"]; 39 | 40 | $ip_addr = isset($_SERVER['HTTP_CLIENT_IP'])?$_SERVER['HTTP_CLIENT_IP']:isset($_SERVER['HTTP_X_FORWARDED_FOR'])?$_SERVER['HTTP_X_FORWARDED_FOR']:$_SERVER['REMOTE_ADDR']; 41 | 42 | if(!is_null($thumb)){ 43 | # Thumbnail mode 44 | if(is_null($illustID)){ 45 | die("Not enough parameters"); 46 | } 47 | $query_thumb->execute(array($illustID)); 48 | $res=$query_thumb->fetch(PDO::FETCH_ASSOC); 49 | header("Content-type: image/jpg"); 50 | echo $res["Image"]; 51 | }else if(!is_null($demo)){ 52 | # Demo mode (1200 resolution) 53 | if(is_null($ratio_g)){ 54 | die("Not enough parameters"); 55 | } 56 | $query_demo->execute(array($ratio_g)); 57 | $res=$query_demo->fetch(PDO::FETCH_ASSOC); 58 | if(is_null($img_info)){ 59 | $format=$res["Format"]; 60 | header("Content-type: image/".$format); 61 | echo $res["Image"]; 62 | }else{ 63 | header("Access-Control-Allow-Origin: *"); 64 | header("Access-Control-Allow-Methods: PUT, GET, POST"); 65 | header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept"); 66 | $data_arr=array("IllustID" => $res["IllustID"]); 67 | echo json_encode($data_arr); 68 | } 69 | }else{ 70 | if(is_null($ratio)){ 71 | die("Not enough parameters"); 72 | } 73 | # Client query mode 74 | $query_selection->execute(array($mac)); 75 | if($res=$query_selection->fetch(PDO::FETCH_ASSOC)){ 76 | $selected_id=$res["IllustID"]; 77 | $query_pinpoint->execute(array($selected_id)); 78 | $res=$query_pinpoint->fetch(PDO::FETCH_ASSOC); 79 | }else{ 80 | $query->execute(array($ratio)); 81 | $res=$query->fetch(PDO::FETCH_ASSOC); 82 | } 83 | $query_log=$pdo->prepare("INSERT INTO log_set_wallpaper(MacAddr,IPAddr,ClientVersion) VALUES(:macaddr,:ipaddr,:version)"); 84 | $query_log->bindParam(":macaddr",$mac); 85 | $query_log->bindParam(":ipaddr",$ip_addr); 86 | $query_log->bindParam(":version",$version); 87 | 88 | $query_log->execute(); 89 | 90 | $format=$res["Format"]; 91 | 92 | header("Content-type: image/".$format); 93 | echo $res["Image"]; 94 | } 95 | -------------------------------------------------------------------------------- /Server/backend/pwf-api/app.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const yaml = require("js-yaml"); 3 | var cors = require("cors"); 4 | const { MongoClient } = require("mongodb"); 5 | 6 | //let configFile = fs.readFileSync('config.yaml', 'utf8'); 7 | //let configs = yaml.safeLoad(configFile); 8 | 9 | //const mongodbConnection = configs['db']['uri'] 10 | const mongodbConnection = process.env.MONGO_URI; 11 | const client = new MongoClient(mongodbConnection, { useUnifiedTopology: true }); 12 | const app = express(); 13 | const dbName = "PWFData"; 14 | 15 | app.use(cors()); 16 | 17 | process.on("uncaughtException", (error) => { 18 | console.log(error); 19 | }); 20 | 21 | app.get("/pipeline/date/latest", async (req, res) => { 22 | connected = await (client.isConnected() ? null : client.connect()); 23 | client 24 | .db(dbName) 25 | .collection("PipelineStatus") 26 | .find({ Status: "Complete" }) 27 | .sort({ Created: -1 }) 28 | .limit(1) 29 | .toArray((err, data) => { 30 | if (data === null) { 31 | res.send("No record found."); 32 | } else { 33 | date = data[0]["DateString"]; 34 | res.send({ latest: date }); 35 | } 36 | }); 37 | }); 38 | 39 | app.get("/image/thumbnail/:id", async (req, res) => { 40 | connected = await (client.isConnected() ? null : client.connect()); 41 | id = req.params["id"]; 42 | client 43 | .db(dbName) 44 | .collection("ImageLocation") 45 | .find({ IllustID: parseInt(id) }) 46 | .sort({ Created: 1 }) 47 | .limit(1) 48 | .toArray((err, data) => { 49 | if (data === null) { 50 | res.send("Image not found"); 51 | } else { 52 | url = data[0]["Thumbnail"]; 53 | res.send({ url: url }); 54 | } 55 | }); 56 | }); 57 | 58 | app.get("/image/compressed/:id", async (req, res) => { 59 | connected = await (client.isConnected() ? null : client.connect()); 60 | id = req.params["id"]; 61 | client 62 | .db(dbName) 63 | .collection("ImageLocation") 64 | .find({ IllustID: parseInt(id) }) 65 | .sort({ Created: 1 }) 66 | .limit(1) 67 | .toArray((err, data) => { 68 | if (data === null) { 69 | res.send("Image not found"); 70 | } else { 71 | url = data[0]["Compressed"]; 72 | res.send({ url: url }); 73 | } 74 | }); 75 | }); 76 | 77 | app.get("/image/original/:id", async (req, res) => { 78 | connected = await (client.isConnected() ? null : client.connect()); 79 | id = req.params["id"]; 80 | client 81 | .db(dbName) 82 | .collection("ImageLocation") 83 | .find({ IllustID: parseInt(id) }) 84 | .sort({ Created: 1 }) 85 | .limit(1) 86 | .toArray((err, data) => { 87 | if (data === null) { 88 | res.send("Image not found"); 89 | } else { 90 | url = data[0]["Original"]; 91 | res.send({ url: url }); 92 | } 93 | }); 94 | }); 95 | 96 | // app.get("/", (req, res) => { 97 | // client 98 | // .db("AppData") 99 | // .collection("Ranking") 100 | // .findOne({ Rank: 1 }) 101 | // .then((data) => { 102 | // return data["IllustID"]; 103 | // }) 104 | // .then((id) => { 105 | // client 106 | // .db("AppData") 107 | // .collection("ImageLocation") 108 | // .findOne({ IllustID: id }) 109 | // .then((data) => { 110 | // url = data["Compressed"]; 111 | // res.send(``); 112 | // }); 113 | // }); 114 | // }); 115 | 116 | app.get("/ranking/unique/:date", async (req, res) => { 117 | connected = await (client.isConnected() ? null : client.connect()); 118 | client 119 | .db(dbName) 120 | .collection("Ranking") 121 | .find({ DateString: req.params["date"], Downloaded: true, IsNew: true }) 122 | .project({ Rank: 1, IllustID: 1, Adult: 1, _id: 0 }) 123 | .sort({ Rank: 1 }) 124 | .toArray((err, data) => { 125 | if (data.length > 0) { 126 | //idArray = data.map((row) => row["IllustID"]); 127 | res.send({ illustData: data }); 128 | } else { 129 | res.send("nothing found"); 130 | } 131 | }); 132 | }); 133 | 134 | app.get("/ranking/raw/:date", async (req, res) => { 135 | connected = await (client.isConnected() ? null : client.connect()); 136 | client 137 | .db(dbName) 138 | .collection("Ranking") 139 | .find({ DateString: req.params["date"], Downloaded: true }) 140 | .project({ Rank: 1, IllustID: 1, Adult: 1, _id: 0 }) 141 | .sort({ Rank: 1 }) 142 | .toArray((err, data) => { 143 | if (data.length > 0) { 144 | //idArray = data.map((row) => row["IllustID"]); 145 | res.send({ illustData: data }); 146 | } else { 147 | res.send("nothing found"); 148 | } 149 | }); 150 | }); 151 | 152 | exports.cloudFunction = app; 153 | -------------------------------------------------------------------------------- /Server/backend/pwf-api/index.js: -------------------------------------------------------------------------------- 1 | 2 | const express = require('express'); 3 | const app = require('./app'); 4 | const port = 3000; 5 | 6 | app.cloudFunction.listen(port, () => { 7 | console.log(`Example app listening at http://localhost:${port}`) 8 | }) -------------------------------------------------------------------------------- /Server/backend/pwf-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pwf-api", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "cors": "^2.8.5", 13 | "express": "^4.17.1", 14 | "js-yaml": "^3.14.0", 15 | "mongodb": "^3.6.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Server/frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /Server/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | -------------------------------------------------------------------------------- /Server/frontend/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine On 3 | RewriteBase / 4 | RewriteRule ^index\.html$ - [L] 5 | RewriteCond %{REQUEST_FILENAME} !-f 6 | RewriteCond %{REQUEST_FILENAME} !-d 7 | RewriteRule . index.html [L] 8 | 9 | -------------------------------------------------------------------------------- /Server/frontend/README.md: -------------------------------------------------------------------------------- 1 | # Controls 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.1.2. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /Server/frontend/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "controls": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": {}, 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/controls", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "src/tsconfig.app.json", 21 | "assets": [ 22 | "src/favicon.ico", 23 | "src/assets" 24 | ], 25 | "styles": [ 26 | "src/styles.css" 27 | ], 28 | "scripts": [] 29 | }, 30 | "configurations": { 31 | "production": { 32 | "fileReplacements": [ 33 | { 34 | "replace": "src/environments/environment.ts", 35 | "with": "src/environments/environment.prod.ts" 36 | } 37 | ], 38 | "optimization": true, 39 | "outputHashing": "all", 40 | "sourceMap": false, 41 | "extractCss": true, 42 | "namedChunks": false, 43 | "aot": true, 44 | "extractLicenses": true, 45 | "vendorChunk": false, 46 | "buildOptimizer": true 47 | } 48 | } 49 | }, 50 | "serve": { 51 | "builder": "@angular-devkit/build-angular:dev-server", 52 | "options": { 53 | "browserTarget": "controls:build" 54 | }, 55 | "configurations": { 56 | "production": { 57 | "browserTarget": "controls:build:production" 58 | } 59 | } 60 | }, 61 | "extract-i18n": { 62 | "builder": "@angular-devkit/build-angular:extract-i18n", 63 | "options": { 64 | "browserTarget": "controls:build" 65 | } 66 | }, 67 | "test": { 68 | "builder": "@angular-devkit/build-angular:karma", 69 | "options": { 70 | "main": "src/test.ts", 71 | "polyfills": "src/polyfills.ts", 72 | "tsConfig": "src/tsconfig.spec.json", 73 | "karmaConfig": "src/karma.conf.js", 74 | "styles": [ 75 | "src/styles.css" 76 | ], 77 | "scripts": [], 78 | "assets": [ 79 | "src/favicon.ico", 80 | "src/assets" 81 | ] 82 | } 83 | }, 84 | "lint": { 85 | "builder": "@angular-devkit/build-angular:tslint", 86 | "options": { 87 | "tsConfig": [ 88 | "src/tsconfig.app.json", 89 | "src/tsconfig.spec.json" 90 | ], 91 | "exclude": [ 92 | "**/node_modules/**" 93 | ] 94 | } 95 | } 96 | } 97 | }, 98 | "controls-e2e": { 99 | "root": "e2e/", 100 | "projectType": "application", 101 | "architect": { 102 | "e2e": { 103 | "builder": "@angular-devkit/build-angular:protractor", 104 | "options": { 105 | "protractorConfig": "e2e/protractor.conf.js", 106 | "devServerTarget": "controls:serve" 107 | }, 108 | "configurations": { 109 | "production": { 110 | "devServerTarget": "controls:serve:production" 111 | } 112 | } 113 | }, 114 | "lint": { 115 | "builder": "@angular-devkit/build-angular:tslint", 116 | "options": { 117 | "tsConfig": "e2e/tsconfig.e2e.json", 118 | "exclude": [ 119 | "**/node_modules/**" 120 | ] 121 | } 122 | } 123 | } 124 | } 125 | }, 126 | "defaultProject": "controls" 127 | } -------------------------------------------------------------------------------- /Server/frontend/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; -------------------------------------------------------------------------------- /Server/frontend/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('workspace-project App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to controls!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /Server/frontend/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Server/frontend/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /Server/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "controls", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "^6.1.0", 15 | "@angular/common": "^6.1.0", 16 | "@angular/compiler": "^6.1.0", 17 | "@angular/core": "^6.1.0", 18 | "@angular/forms": "^6.1.0", 19 | "@angular/http": "^6.1.0", 20 | "@angular/platform-browser": "^6.1.0", 21 | "@angular/platform-browser-dynamic": "^6.1.0", 22 | "@angular/router": "^6.1.0", 23 | "@ng-bootstrap/ng-bootstrap": "^4.0.0", 24 | "core-js": "^2.5.4", 25 | "rxjs": "^6.0.0", 26 | "zone.js": "~0.8.26" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "~0.7.0", 30 | "@angular/cli": "~6.1.2", 31 | "@angular/compiler-cli": "^6.1.0", 32 | "@angular/language-service": "^6.1.0", 33 | "@types/jasmine": "~2.8.6", 34 | "@types/jasminewd2": "~2.0.3", 35 | "@types/node": "~8.9.4", 36 | "codelyzer": "~4.2.1", 37 | "jasmine-core": "~2.99.1", 38 | "jasmine-spec-reporter": "~4.2.1", 39 | "karma": "~1.7.1", 40 | "karma-chrome-launcher": "~2.2.0", 41 | "karma-coverage-istanbul-reporter": "~2.0.0", 42 | "karma-jasmine": "~1.1.1", 43 | "karma-jasmine-html-reporter": "^0.2.2", 44 | "protractor": "~5.3.0", 45 | "ts-node": "~5.0.1", 46 | "tslint": "~5.9.1", 47 | "typescript": "~2.7.2" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Server/frontend/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | .navbar-nav-svg{ 2 | width: 2rem; 3 | height: 2rem; 4 | } -------------------------------------------------------------------------------- /Server/frontend/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 24 | 25 | -------------------------------------------------------------------------------- /Server/frontend/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | describe('AppComponent', () => { 4 | beforeEach(async(() => { 5 | TestBed.configureTestingModule({ 6 | declarations: [ 7 | AppComponent 8 | ], 9 | }).compileComponents(); 10 | })); 11 | it('should create the app', async(() => { 12 | const fixture = TestBed.createComponent(AppComponent); 13 | const app = fixture.debugElement.componentInstance; 14 | expect(app).toBeTruthy(); 15 | })); 16 | it(`should have as title 'controls'`, async(() => { 17 | const fixture = TestBed.createComponent(AppComponent); 18 | const app = fixture.debugElement.componentInstance; 19 | expect(app.title).toEqual('controls'); 20 | })); 21 | it('should render title in a h1 tag', async(() => { 22 | const fixture = TestBed.createComponent(AppComponent); 23 | fixture.detectChanges(); 24 | const compiled = fixture.debugElement.nativeElement; 25 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to controls!'); 26 | })); 27 | }); 28 | -------------------------------------------------------------------------------- /Server/frontend/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | } 10 | -------------------------------------------------------------------------------- /Server/frontend/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { RouterModule, Routes } from '@angular/router'; 4 | import { HttpClientModule } from '@angular/common/http'; 5 | 6 | import { AppComponent } from './app.component'; 7 | import { DemoComponent } from './demo/demo.component'; 8 | import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; 9 | import { FaqComponent } from './faq/faq.component'; 10 | import { FeaturesComponent } from './features/features.component'; 11 | import { RankingComponent } from './ranking/ranking.component'; 12 | 13 | const appRoutes: Routes = [ 14 | { path: '', component: FeaturesComponent }, 15 | { path: 'demo', component: DemoComponent }, 16 | { path: 'faq', component: FaqComponent }, 17 | { path: 'ranking/:uuid', component: RankingComponent }, 18 | { path: 'ranking', component: RankingComponent }, 19 | ]; 20 | 21 | @NgModule({ 22 | declarations: [ 23 | AppComponent, 24 | DemoComponent, 25 | FaqComponent, 26 | FeaturesComponent, 27 | RankingComponent 28 | ], 29 | imports: [ 30 | BrowserModule, 31 | RouterModule.forRoot(appRoutes), 32 | NgbModule, 33 | HttpClientModule 34 | ], 35 | providers: [], 36 | bootstrap: [AppComponent] 37 | }) 38 | export class AppModule { } 39 | -------------------------------------------------------------------------------- /Server/frontend/src/app/demo.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { DemoService } from './demo.service'; 4 | 5 | describe('DemoService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [DemoService] 9 | }); 10 | }); 11 | 12 | it('should be created', inject([DemoService], (service: DemoService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /Server/frontend/src/app/demo.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class DemoService { 8 | requestUrl = "https://singf.space/pixiv/select_paper.php?demo=yes&info=yes&ar="; 9 | 10 | constructor(private http: HttpClient) { 11 | } 12 | 13 | public getIllustInfo(aspectRatio) { 14 | let fullUrl = this.requestUrl + aspectRatio; 15 | return this.http.get(fullUrl); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Server/frontend/src/app/demo/demo.component.css: -------------------------------------------------------------------------------- 1 | .number-input{ 2 | max-width: 6em; 3 | margin-left:1em; 4 | margin-right: 1.5em; 5 | } 6 | 7 | .rounded-border{ 8 | border-radius:.7em; 9 | } 10 | 11 | .loader { 12 | border: 0.7em solid #f3f3f3; /* Light grey */ 13 | border-top: 0.7em solid #3498db; /* Blue */ 14 | border-radius: 50%; 15 | width: 3em; 16 | height: 3em; 17 | animation: spin 0.5s linear infinite; 18 | bottom: 0; 19 | } 20 | 21 | @keyframes spin { 22 | 0% { transform: rotate(0deg); } 23 | 100% { transform: rotate(360deg); } 24 | } -------------------------------------------------------------------------------- /Server/frontend/src/app/demo/demo.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Enter your screen resolution

3 | Invalid resolution 4 |
5 |
6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 | 15 |

And we'll find you the best wallpaper from today's Pixiv daily rankings for your screen!

16 |
17 |
18 |
19 | 20 |
21 | 26 |
27 | 28 | 29 |

Want this illustration in its original resolution to be your wallpaper?

30 | 33 | 34 | 35 |
-------------------------------------------------------------------------------- /Server/frontend/src/app/demo/demo.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DemoComponent } from './demo.component'; 4 | 5 | describe('DemoComponent', () => { 6 | let component: DemoComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ DemoComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(DemoComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /Server/frontend/src/app/demo/demo.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { DemoService } from '../demo.service'; 3 | 4 | @Component({ 5 | selector: 'app-demo', 6 | templateUrl: './demo.component.html', 7 | styleUrls: ['./demo.component.css'] 8 | }) 9 | 10 | export class DemoComponent implements OnInit { 11 | showAlert: boolean; 12 | pixivLink: string; 13 | imageLocation: string; 14 | aspectRatio: number; 15 | imageLoading: boolean; 16 | illustID: string; 17 | 18 | constructor(private demoService: DemoService) { } 19 | 20 | loadBestMatch(widthInput, heightInput) { 21 | if (widthInput == parseInt(widthInput) && heightInput == parseInt(heightInput)) { 22 | this.showAlert = false; 23 | this.aspectRatio = parseInt(widthInput) / parseInt(heightInput); 24 | this.getSet(); 25 | } else { 26 | this.showAlert = true; 27 | } 28 | return false; 29 | } 30 | 31 | getSet() { 32 | this.reloadImage(); 33 | this.refreshLink(); 34 | } 35 | 36 | reloadImage() { 37 | this.imageLoading = true; 38 | this.imageLocation = "https://singf.space/pixiv/select_paper.php?demo=yes&ar=" + this.aspectRatio; 39 | } 40 | 41 | infoCallback(data: returnData) { 42 | this.illustID = data["IllustID"]; 43 | this.pixivLink = "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=" + this.illustID; 44 | } 45 | 46 | refreshLink() { 47 | this.demoService.getIllustInfo(this.aspectRatio) 48 | .subscribe((data: returnData) => this.infoCallback(data) 49 | ); 50 | } 51 | 52 | ngOnInit() { 53 | document.getElementById("nav_demo").className += " active"; 54 | this.aspectRatio = 16 / 9; 55 | this.getSet(); 56 | } 57 | 58 | } 59 | 60 | interface returnData { 61 | IllustID: string; 62 | } -------------------------------------------------------------------------------- /Server/frontend/src/app/faq/faq.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Server/frontend/src/app/faq/faq.component.css -------------------------------------------------------------------------------- /Server/frontend/src/app/faq/faq.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Frequently asked questions

3 |
4 | Press Ctrl+F to find answers to your questions with your browser 5 |
6 |

Why are there illustrations I can't pick from even though they're in the Pixiv daily rankings?

7 |
The application only collects the top 50 illustrations from Pixiv daily rankings. Additionally, only 8 | illustrations that did not show up in 9 | the previous day's top 50 will be collected for users to choose from so that users won't 10 | receive the same wallpaper two days in a row. Therefore you will almost always get less than 50 images to choose 11 | from.
Additionally, illustrations involving adult contents are not collected by our application. See 12 | below for details.
13 |

Will the application deliver adult/lewd/NSFW images?

14 |
No. But this depends on how Pixiv handle those images. By default, our application does not collect 15 | illustrations you'll need to log in to see them in higher resolutions. Pixiv restricts not-logged-in users from 16 | viewing images involving adult contents. I guess this filter won't work all the time so there's no guarantee. 17 | But even if you get an obscene wallpaper, it's not the application's fault.
18 |

Why is the illustration I got in low resolution? Shouldn't it be in its original resolution?

19 |
The creator of this illustration only uploaded a low resolution version to Pixiv, therefore we cannot obtain a 20 | version 21 | with a higher resolution and the version you got is already "original". Please log into Pixiv and enlarge the 22 | illustration to confirm this.
23 | You can use the application's built-in waifu2x to enlarge your wallpaper without sacrificing quality, although the 24 | enlarging process is a bit time-consuming.
25 |
-------------------------------------------------------------------------------- /Server/frontend/src/app/faq/faq.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FaqComponent } from './faq.component'; 4 | 5 | describe('FaqComponent', () => { 6 | let component: FaqComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ FaqComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(FaqComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /Server/frontend/src/app/faq/faq.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-faq', 5 | templateUrl: './faq.component.html', 6 | styleUrls: ['./faq.component.css'] 7 | }) 8 | export class FaqComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | document.getElementById("nav_faq").className += " active"; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Server/frontend/src/app/features/features.component.css: -------------------------------------------------------------------------------- 1 | .marketing-img{ 2 | width:100%; 3 | } 4 | .border-text{ 5 | text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; 6 | } 7 | .alt-color{ 8 | background-color: #383838; 9 | color:white; 10 | } -------------------------------------------------------------------------------- /Server/frontend/src/app/features/features.component.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | PixivWallpaper is a Windows application that delivers illustrations from Pixiv 4 | Daily Rankings to your desktop 5 | every day. 6 |

7 | 8 |
9 | 10 | 11 | 12 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 30 | 31 | 32 |
33 |
34 | 35 |
36 |
37 |
FEATURES
38 |
39 |

40 | Smart selection algorithm 41 |

42 | The application implements a smart selection algorithm that selects the best illustration for your screen 43 | resolution. Save the trouble to browse through the illustrations, our algorithm will make sure you always get the 44 | best looking 45 | illustration for your desktop!
46 | See our demo here. 47 |
48 | 49 |
50 |

51 | Easy-to-use interface 52 |

53 | A double-click is all you need to change your wallpaper, and your desktop will start to look amazing. Add the 54 | application to startup to let it change wallpaper for you every day, without human interaction. 55 |
56 | 57 |
58 |

59 | Pick your personal favorite 60 |

61 | You can pick your favorite wallpaper from our ranking 62 | gallery and have it delivered to your desktop! 63 |
64 | 65 |
66 |

67 | Image supersampling 68 |

69 | Don't worry if the illustration is too small for your screen, the application's built-in waifu2x 70 | support will make your wallpaper look smooth and clear. 71 |
72 | 73 |
74 |

75 | And much more! 76 |

77 | Download the application to discover its features. The application is currently under active development. Stay 78 | tuned for more features to come. 79 |
80 | 81 |
82 |
83 | 84 |
85 |
86 | 87 | 92 | 93 |
64-bit Windows only
94 |
95 |
-------------------------------------------------------------------------------- /Server/frontend/src/app/features/features.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FeaturesComponent } from './features.component'; 4 | 5 | describe('FeaturesComponent', () => { 6 | let component: FeaturesComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ FeaturesComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(FeaturesComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /Server/frontend/src/app/features/features.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Meta, Title } from '@angular/platform-browser'; 3 | 4 | @Component({ 5 | selector: 'app-features', 6 | templateUrl: './features.component.html', 7 | styleUrls: ['./features.component.css'] 8 | }) 9 | export class FeaturesComponent implements OnInit { 10 | 11 | constructor(private meta: Meta, private title: Title) { } 12 | 13 | ngOnInit() { 14 | document.getElementById("nav_feature").className += " active"; 15 | this.meta.addTag({ name: "Description", content: "PixivWallpaper is a Windows application that delivers illustrations from Pixiv Daily Rankings to your desktop every day. It comes with a smart selection algorithm that automatically picks the best looking illustration for your screen." }); 16 | this.title.setTitle("PixivWallpaper - Daily Top Wallpapers on Pixiv"); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Server/frontend/src/app/picker.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { PickerService } from './picker.service'; 4 | 5 | describe('PickerService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [PickerService] 9 | }); 10 | }); 11 | 12 | it('should be created', inject([PickerService], (service: PickerService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /Server/frontend/src/app/picker.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class PickerService { 8 | requestUrl = "https://singf.space/pixiv/pick_paper.php"; 9 | 10 | constructor(private http: HttpClient) { } 11 | 12 | public setWallpaper(uuid: string, illustID: string, timestamp: string) { 13 | return this.http.post(this.requestUrl, { uuid: uuid, illustID: illustID, timestamp: timestamp }, { responseType: 'text' }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Server/frontend/src/app/ranking.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { RankingService } from './ranking.service'; 4 | 5 | describe('RankingService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [RankingService] 9 | }); 10 | }); 11 | 12 | it('should be created', inject([RankingService], (service: RankingService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /Server/frontend/src/app/ranking.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class RankingService { 8 | requestUrl = "https://singf.space/pixiv/ranking_gallery.php"; 9 | 10 | constructor(private http: HttpClient) { } 11 | 12 | public fetchGallery() { 13 | return this.http.get(this.requestUrl); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Server/frontend/src/app/ranking/ranking.component.css: -------------------------------------------------------------------------------- 1 | .loader { 2 | border: 0.7em solid #f3f3f3; /* Light grey */ 3 | border-top: 0.7em solid #3498db; /* Blue */ 4 | border-radius: 50%; 5 | width: 3em; 6 | height: 3em; 7 | animation: spin 0.5s linear infinite; 8 | bottom: 0; 9 | } 10 | 11 | @keyframes spin { 12 | 0% { transform: rotate(0deg); } 13 | 100% { transform: rotate(360deg); } 14 | } -------------------------------------------------------------------------------- /Server/frontend/src/app/ranking/ranking.component.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 12 | 13 | 14 |
15 |

Today's Illustrations from Pixiv daily rankings (?)

16 |
17 | You're now in preview mode, download the application to select from the wallpapers below.
18 | If you have downloaded the app, run pixivwallpaper_gui.exe and press pick wallpaper. 19 |
20 |
21 | Pick any of the illustrations below, and click Set as wallpaper, then get wallpaper from your desktop client 22 | again to see the change. 23 |
24 | 25 |
26 |
27 |
28 |
#{{artwork.Ranking}}
29 |
30 |
31 | 32 | 33 | 34 |
35 |
{{artwork.OrigWidth}} x {{artwork.OrigHeight}}
36 |
37 |
38 | 39 |
40 |
41 |
42 |
#{{artwork.Ranking}}
43 |
44 |
45 | 47 |
48 | 51 | 54 |
55 |
56 |
{{artwork.OrigWidth}} x {{artwork.OrigHeight}}
57 |
58 |
59 |
-------------------------------------------------------------------------------- /Server/frontend/src/app/ranking/ranking.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RankingComponent } from './ranking.component'; 4 | 5 | describe('RankingComponent', () => { 6 | let component: RankingComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ RankingComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(RankingComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /Server/frontend/src/app/ranking/ranking.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { RankingService } from '../ranking.service'; 4 | import { PickerService } from '../picker.service'; 5 | 6 | import { NgbModal, ModalDismissReasons } from '@ng-bootstrap/ng-bootstrap'; 7 | 8 | @Component({ 9 | selector: 'app-ranking', 10 | templateUrl: './ranking.component.html', 11 | styleUrls: ['./ranking.component.css'] 12 | }) 13 | export class RankingComponent implements OnInit { 14 | artworks: returnData[]; 15 | selectMode: boolean = false; 16 | selectedRank: number = -1; 17 | uuid: string; 18 | requestPending: boolean = false; 19 | modalMessage: string; 20 | 21 | constructor(private modalService: NgbModal, private rankingService: RankingService, private pickerService: PickerService, private route: ActivatedRoute) { } 22 | 23 | ajaxCallback(data: returnData[]) { 24 | this.artworks = data; 25 | } 26 | 27 | open(content) { 28 | this.modalService.open(content, { centered: true }); 29 | } 30 | 31 | setWallpaper(illustID: string, timestamp: string) { 32 | this.requestPending = true; 33 | this.modalMessage = ""; 34 | this.pickerService.setWallpaper(this.uuid, illustID, timestamp) 35 | .subscribe((data) => { 36 | if (data == "successful") { 37 | this.requestPending = false; 38 | this.modalMessage = "Wallpaper was set successfully, get wallpaper from your desktop client again to see the change."; 39 | } else { 40 | this.requestPending = false; 41 | this.modalMessage = "Request failed, try again later or try updating the client."; 42 | } 43 | } 44 | ); 45 | } 46 | 47 | ngOnInit() { 48 | document.getElementById("nav_rank").className += " active"; 49 | 50 | this.route.params.subscribe(params => { 51 | if (typeof params["uuid"] != "undefined") { 52 | this.selectMode = true; 53 | this.uuid = params["uuid"]; 54 | } 55 | }); 56 | 57 | this.rankingService.fetchGallery() 58 | .subscribe((data: returnData[]) => this.ajaxCallback(data) 59 | ); 60 | } 61 | 62 | } 63 | 64 | interface returnData { 65 | ImageID: string; 66 | Width: string; 67 | Height: string; 68 | AspectRatio: string; 69 | CheckSum: string; 70 | Entropy: string; 71 | AvgGradient: string; 72 | Variance: string; 73 | Format: string; 74 | DateCreated: string; 75 | Type: string; 76 | IllustID: string; 77 | Ranking: string; 78 | TimeStamp: string; 79 | OrigWidth: string; 80 | OrigHeight: string; 81 | } -------------------------------------------------------------------------------- /Server/frontend/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Server/frontend/src/assets/.gitkeep -------------------------------------------------------------------------------- /Server/frontend/src/assets/img/desktop_cool.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Server/frontend/src/assets/img/desktop_cool.jpg -------------------------------------------------------------------------------- /Server/frontend/src/assets/img/desktop_cute.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Server/frontend/src/assets/img/desktop_cute.jpg -------------------------------------------------------------------------------- /Server/frontend/src/assets/img/desktop_default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Server/frontend/src/assets/img/desktop_default.jpg -------------------------------------------------------------------------------- /Server/frontend/src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # For IE 9-11 support, please uncomment the last line of the file and adjust as needed 5 | > 0.5% 6 | last 2 versions 7 | Firefox ESR 8 | not dead 9 | # IE 9-11 -------------------------------------------------------------------------------- /Server/frontend/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /Server/frontend/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * In development mode, to ignore zone related error stack frames such as 11 | * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can 12 | * import the following file, but please comment it out in production mode 13 | * because it will have performance impact when throw error 14 | */ 15 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 16 | -------------------------------------------------------------------------------- /Server/frontend/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Server/frontend/src/favicon.ico -------------------------------------------------------------------------------- /Server/frontend/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | PixivWallpaper 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Server/frontend/src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; -------------------------------------------------------------------------------- /Server/frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.log(err)); 13 | -------------------------------------------------------------------------------- /Server/frontend/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | import 'core-js/es6/symbol'; 23 | import 'core-js/es6/object'; 24 | import 'core-js/es6/function'; 25 | import 'core-js/es6/parse-int'; 26 | import 'core-js/es6/parse-float'; 27 | import 'core-js/es6/number'; 28 | import 'core-js/es6/math'; 29 | import 'core-js/es6/string'; 30 | import 'core-js/es6/date'; 31 | import 'core-js/es6/array'; 32 | import 'core-js/es6/regexp'; 33 | import 'core-js/es6/map'; 34 | import 'core-js/es6/weak-map'; 35 | import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** IE10 and IE11 requires the following for the Reflect API. */ 41 | // import 'core-js/es6/reflect'; 42 | 43 | 44 | /** Evergreen browsers require these. **/ 45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 46 | import 'core-js/es7/reflect'; 47 | 48 | 49 | /** 50 | * Web Animations `@angular/platform-browser/animations` 51 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 52 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 53 | **/ 54 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 55 | 56 | /** 57 | * By default, zone.js will patch all possible macroTask and DomEvents 58 | * user can disable parts of macroTask/DomEvents patch by setting following flags 59 | */ 60 | 61 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 62 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 63 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 64 | 65 | /* 66 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 67 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 68 | */ 69 | // (window as any).__Zone_enable_cross_context_check = true; 70 | 71 | /*************************************************************************************************** 72 | * Zone JS is required by default for Angular itself. 73 | */ 74 | import 'zone.js/dist/zone'; // Included with Angular CLI. 75 | 76 | 77 | 78 | /*************************************************************************************************** 79 | * APPLICATION IMPORTS 80 | */ 81 | -------------------------------------------------------------------------------- /Server/frontend/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /Server/frontend/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /Server/frontend/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | "src/test.ts", 9 | "**/*.spec.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /Server/frontend/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /Server/frontend/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Server/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "target": "es5", 13 | "typeRoots": [ 14 | "node_modules/@types" 15 | ], 16 | "lib": [ 17 | "es2017", 18 | "dom" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Server/frontend/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-shadowed-variable": true, 69 | "no-string-literal": false, 70 | "no-string-throw": true, 71 | "no-switch-case-fall-through": true, 72 | "no-trailing-whitespace": true, 73 | "no-unnecessary-initializer": true, 74 | "no-unused-expression": true, 75 | "no-use-before-declare": true, 76 | "no-var-keyword": true, 77 | "object-literal-sort-keys": false, 78 | "one-line": [ 79 | true, 80 | "check-open-brace", 81 | "check-catch", 82 | "check-else", 83 | "check-whitespace" 84 | ], 85 | "prefer-const": true, 86 | "quotemark": [ 87 | true, 88 | "single" 89 | ], 90 | "radix": true, 91 | "semicolon": [ 92 | true, 93 | "always" 94 | ], 95 | "triple-equals": [ 96 | true, 97 | "allow-null-check" 98 | ], 99 | "typedef-whitespace": [ 100 | true, 101 | { 102 | "call-signature": "nospace", 103 | "index-signature": "nospace", 104 | "parameter": "nospace", 105 | "property-declaration": "nospace", 106 | "variable-declaration": "nospace" 107 | } 108 | ], 109 | "unified-signatures": true, 110 | "variable-name": false, 111 | "whitespace": [ 112 | true, 113 | "check-branch", 114 | "check-decl", 115 | "check-operator", 116 | "check-separator", 117 | "check-type" 118 | ], 119 | "no-output-on-prefix": true, 120 | "use-input-property-decorator": true, 121 | "use-output-property-decorator": true, 122 | "use-host-property-decorator": true, 123 | "no-input-rename": true, 124 | "no-output-rename": true, 125 | "use-life-cycle-interface": true, 126 | "use-pipe-transform-interface": true, 127 | "component-class-suffix": true, 128 | "directive-class-suffix": true 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Server/pipeline/image-sr/.gitignore: -------------------------------------------------------------------------------- 1 | nohup.out 2 | src/script_configs.sh 3 | src/Dockerfile 4 | src/data.json 5 | src/service_account.json 6 | src/__pycache__/ 7 | src/imgs/ -------------------------------------------------------------------------------- /Server/pipeline/image-sr/configs/dev/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM tensorflow/tensorflow:latest-gpu 2 | 3 | WORKDIR /usr/src/app 4 | COPY . . 5 | 6 | RUN apt update && apt install -y vim 7 | RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt 8 | CMD ["sh", "-c", "tail -f /dev/null"] -------------------------------------------------------------------------------- /Server/pipeline/image-sr/configs/dev/deploy_configs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | GCP_PROJ="" 4 | GKE_CLUSTER_NAME="" 5 | GKE_CLUSTER_ZONE="" 6 | DOCKER_IMG_NAME="" 7 | NODE_POOL="" 8 | SA_BUCKET="" 9 | SA_OBJECT="" 10 | -------------------------------------------------------------------------------- /Server/pipeline/image-sr/configs/dev/script_configs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | GCP_PROJ="" 4 | SUB_ID="" 5 | -------------------------------------------------------------------------------- /Server/pipeline/image-sr/configs/prod/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM tensorflow/tensorflow:latest-gpu 2 | 3 | WORKDIR /usr/src/app 4 | COPY . . 5 | 6 | RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt 7 | CMD [ "bash", "./exec.sh" ] -------------------------------------------------------------------------------- /Server/pipeline/image-sr/configs/prod/deploy_configs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | GCP_PROJ="" 4 | GKE_CLUSTER_NAME="" 5 | GKE_CLUSTER_ZONE="" 6 | DOCKER_IMG_NAME="" 7 | NODE_POOL="" 8 | SA_BUCKET="" 9 | SA_OBJECT="" 10 | -------------------------------------------------------------------------------- /Server/pipeline/image-sr/configs/prod/script_configs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | GCP_PROJ="" 4 | SUB_ID="" 5 | -------------------------------------------------------------------------------- /Server/pipeline/image-sr/configs/qa/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM tensorflow/tensorflow:latest-gpu 2 | 3 | WORKDIR /usr/src/app 4 | COPY . . 5 | 6 | RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt 7 | CMD [ "bash", "./exec.sh" ] -------------------------------------------------------------------------------- /Server/pipeline/image-sr/configs/qa/deploy_configs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | GCP_PROJ="" 4 | GKE_CLUSTER_NAME="" 5 | GKE_CLUSTER_ZONE="" 6 | DOCKER_IMG_NAME="" 7 | NODE_POOL="" 8 | SA_BUCKET="" 9 | SA_OBJECT="" 10 | -------------------------------------------------------------------------------- /Server/pipeline/image-sr/configs/qa/script_configs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | GCP_PROJ="" 4 | SUB_ID="" 5 | -------------------------------------------------------------------------------- /Server/pipeline/image-sr/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -a 5 | 6 | echo "Execution started" 7 | date 8 | 9 | echo "Getting environment: ${1}" 10 | CURR_ENV=${1} 11 | 12 | echo "Loading environmental variables" 13 | . ./configs/${CURR_ENV}/deploy_configs.sh 14 | 15 | echo "Copying config files" 16 | cp ./configs/${CURR_ENV}/script_configs.sh ./src/ 17 | cp ./configs/${CURR_ENV}/Dockerfile ./src/ 18 | 19 | echo "Downloading service account" 20 | gsutil cp gs://${SA_BUCKET}/${SA_OBJECT} ./src/service_account.json 21 | 22 | pushd src 23 | echo "Setting project" 24 | gcloud config set project ${GCP_PROJ} 25 | echo "Submitting cloud build job" 26 | gcloud builds submit --tag gcr.io/${GCP_PROJ}/${DOCKER_IMG_NAME} 27 | popd 28 | 29 | pushd ./k8s/${CURR_ENV} 30 | echo "Connecting to GKE cluster" 31 | gcloud container clusters get-credentials ${GKE_CLUSTER_NAME} --zone ${GKE_CLUSTER_ZONE} --project ${GCP_PROJ} 32 | echo "Deploying pods in GKE cluster" 33 | envsubst < deploy.yaml | kubectl apply -f - 34 | popd 35 | 36 | echo "Execution completed" 37 | date 38 | -------------------------------------------------------------------------------- /Server/pipeline/image-sr/k8s/dev/deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: ${DOCKER_IMG_NAME} 5 | spec: 6 | containers: 7 | - name: ${DOCKER_IMG_NAME} 8 | image: gcr.io/${GCP_PROJ}/${DOCKER_IMG_NAME}:latest 9 | nodeSelector: 10 | cloud.google.com/gke-nodepool: ${NODE_POOL} 11 | restartPolicy: Never 12 | tolerations: 13 | - key: "EvictSystem" 14 | operator: "Exists" 15 | effect: "NoSchedule" -------------------------------------------------------------------------------- /Server/pipeline/image-sr/k8s/prod/deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: ${DOCKER_IMG_NAME} 5 | spec: 6 | template: 7 | spec: 8 | containers: 9 | - name: ${DOCKER_IMG_NAME} 10 | image: gcr.io/${GCP_PROJ}/${DOCKER_IMG_NAME}:latest 11 | nodeSelector: 12 | cloud.google.com/gke-nodepool: ${NODE_POOL} 13 | restartPolicy: Never 14 | tolerations: 15 | - key: "EvictSystem" 16 | operator: "Exists" 17 | effect: "NoSchedule" -------------------------------------------------------------------------------- /Server/pipeline/image-sr/k8s/qa/deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: ${DOCKER_IMG_NAME} 5 | spec: 6 | template: 7 | spec: 8 | containers: 9 | - name: ${DOCKER_IMG_NAME} 10 | image: gcr.io/${GCP_PROJ}/${DOCKER_IMG_NAME}:latest 11 | nodeSelector: 12 | cloud.google.com/gke-nodepool: ${NODE_POOL} 13 | restartPolicy: Never 14 | tolerations: 15 | - key: "EvictSystem" 16 | operator: "Exists" 17 | effect: "NoSchedule" -------------------------------------------------------------------------------- /Server/pipeline/image-sr/src/downloader.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | import re 3 | import json 4 | from pathlib import Path 5 | from error_handlers import exception_handler 6 | from google.cloud import storage 7 | 8 | 9 | def download_image(url): 10 | bucket, object_path = re.findall("gs://(.*?)/(.*)", url)[0] 11 | ext = object_path.split(".")[-1] 12 | save_path = f"./imgs/{uuid.uuid4()}.{ext}" 13 | download_blob(bucket, object_path, save_path) 14 | return save_path 15 | 16 | 17 | def download_blob(bucket_name, source_blob_name, destination_file_name): 18 | """Downloads a blob from the bucket.""" 19 | storage_client = storage.Client.from_service_account_json( 20 | 'service_account.json') 21 | bucket = storage_client.bucket(bucket_name) 22 | 23 | blob = bucket.blob(source_blob_name) 24 | blob.download_to_filename(destination_file_name) 25 | print("Blob {} downloaded to {}.".format( 26 | source_blob_name, destination_file_name)) 27 | 28 | 29 | if __name__ == "__main__": 30 | try: 31 | with open("data.json", "r") as f: 32 | list_data = json.load(f) 33 | print("JSON file parsed") 34 | except: 35 | exception_handler("Read JSON failed") 36 | 37 | if(len(list_data) == 0): 38 | print("No image to process, exiting...") 39 | exit(0) 40 | 41 | try: 42 | Path("./imgs").mkdir(parents=True, exist_ok=True) 43 | list_new_data = [] 44 | for data in list_data: 45 | save_path = download_image(data["input_file"]) 46 | list_new_data.append({**data, "saved_file": save_path}) 47 | print("Images downloaded") 48 | except: 49 | exception_handler("Image download failed") 50 | 51 | try: 52 | with open('data.json', 'w') as f: 53 | json.dump(list_new_data, f) 54 | print("JSON overwritten") 55 | except: 56 | exception_handler("Overwriting JSON failed") 57 | -------------------------------------------------------------------------------- /Server/pipeline/image-sr/src/error_handlers.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import traceback 3 | 4 | 5 | def exception_handler(error_msg): 6 | print(error_msg, file=sys.stderr) 7 | traceback.print_exc() 8 | exit(1) 9 | -------------------------------------------------------------------------------- /Server/pipeline/image-sr/src/exec.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -a 5 | 6 | echo "Loading environmental variables" 7 | echo "====================" 8 | . ./script_configs.sh 9 | echo "" 10 | 11 | echo "Running subscriber" 12 | echo "====================" 13 | python subscriber.py 14 | echo "" 15 | 16 | echo "Downloading images" 17 | echo "====================" 18 | python downloader.py 19 | echo "" 20 | 21 | echo "Running super-resolution model" 22 | echo "====================" 23 | python model.py 24 | echo "" 25 | 26 | echo "Uploading enhanced images to Cloud Storage" 27 | echo "====================" 28 | python uploader.py 29 | echo "" 30 | 31 | echo "Cleaning up workspace" 32 | echo "====================" 33 | rm -f data.json 34 | rm -Rf ./imgs 35 | echo "" -------------------------------------------------------------------------------- /Server/pipeline/image-sr/src/model.py: -------------------------------------------------------------------------------- 1 | import time 2 | import json 3 | import uuid 4 | import tensorflow_hub as hub 5 | import tensorflow as tf 6 | import numpy as np 7 | from PIL import Image 8 | from error_handlers import exception_handler 9 | 10 | 11 | def get_enhanced_path(): 12 | save_path = f"./imgs/{uuid.uuid4()}.jpg" 13 | return save_path 14 | 15 | 16 | def preprocess_image(contents): 17 | """ Loads image from path and preprocesses to make it model ready 18 | Args: 19 | image_path: Path to the image file 20 | """ 21 | hr_image = tf.image.decode_image(contents) 22 | # If PNG, remove the alpha channel. The model only supports 23 | # images with 3 color channels. 24 | if hr_image.shape[-1] == 4: 25 | hr_image = hr_image[..., :-1] 26 | hr_size = (tf.convert_to_tensor(hr_image.shape[:-1]) // 4) * 4 27 | hr_image = tf.image.crop_to_bounding_box( 28 | hr_image, 0, 0, hr_size[0], hr_size[1]) 29 | hr_image = tf.cast(hr_image, tf.float32) 30 | return tf.expand_dims(hr_image, 0) 31 | 32 | 33 | def save_image(image, filename): 34 | """ 35 | Saves unscaled Tensor Images. 36 | Args: 37 | image: 3D image tensor. [height, width, channels] 38 | filename: Name of the file to save to. 39 | """ 40 | if not isinstance(image, Image.Image): 41 | image = tf.clip_by_value(image, 0, 255) 42 | image = Image.fromarray(tf.cast(image, tf.uint8).numpy()) 43 | image.save(filename) 44 | print("Saved as %s" % filename) 45 | 46 | 47 | if __name__ == "__main__": 48 | start_time = time.time() 49 | print("Starting image super-resolution") 50 | 51 | try: 52 | model = hub.load("https://tfhub.dev/captain-pool/esrgan-tf2/1") 53 | print("Model downloaded") 54 | except: 55 | exception_handler("Model download failed") 56 | 57 | try: 58 | with open("data.json", "r") as f: 59 | list_data = json.load(f) 60 | print("JSON file parsed") 61 | except: 62 | exception_handler("Read JSON failed") 63 | 64 | if(len(list_data) == 0): 65 | print("No image to process, exiting...") 66 | exit(0) 67 | 68 | try: 69 | list_new_data = [] 70 | img_batch = [] 71 | for data in list_data: 72 | saved_file = data["saved_file"] 73 | with open(saved_file, "br") as f: 74 | img_mat = preprocess_image(f.read()) 75 | img_batch.append(img_mat) 76 | print("Images loaded") 77 | except: 78 | exception_handler("Image load failed") 79 | 80 | try: 81 | processed_batch = [] 82 | for img in img_batch: 83 | processed_batch.append(model(img)) 84 | print("Super-resolution successful") 85 | except: 86 | exception_handler("Model execution failed") 87 | 88 | try: 89 | for i, data in enumerate(list_data): 90 | enhanced_path = get_enhanced_path() 91 | save_image(processed_batch[i][0], enhanced_path) 92 | list_new_data.append({**data, "enhanced_file": enhanced_path}) 93 | print("Processed images saved") 94 | except: 95 | exception_handler("Saving image failed") 96 | 97 | try: 98 | with open('data.json', 'w') as f: 99 | json.dump(list_new_data, f) 100 | print("JSON overwritten") 101 | except: 102 | exception_handler("Overwriting JSON failed") 103 | 104 | print(f"Execution took {time.time() - start_time} seconds") 105 | -------------------------------------------------------------------------------- /Server/pipeline/image-sr/src/requirements.txt: -------------------------------------------------------------------------------- 1 | Pillow 2 | numpy 3 | requests 4 | google-cloud-storage 5 | tensorflow-hub 6 | google-cloud-pubsub 7 | -------------------------------------------------------------------------------- /Server/pipeline/image-sr/src/subscriber.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from error_handlers import exception_handler 4 | from google.api_core import retry 5 | from google.cloud import pubsub_v1 6 | 7 | 8 | if __name__ == "__main__": 9 | try: 10 | project_id = os.environ['GCP_PROJ'] 11 | subscription_id = os.environ['SUB_ID'] 12 | subscriber = pubsub_v1.SubscriberClient.from_service_account_json( 13 | 'service_account.json') 14 | subscription_path = subscriber.subscription_path( 15 | project_id, subscription_id) 16 | print("Connected to Pub/Sub") 17 | except: 18 | exception_handler("Connection to Pub/Sub failed") 19 | 20 | try: 21 | list_messages = [] 22 | with subscriber: 23 | while True: 24 | response = subscriber.pull( 25 | request={"subscription": subscription_path, 26 | "max_messages": 1}, 27 | retry=retry.Retry(deadline=300), 28 | ) 29 | if len(response.received_messages) == 1: 30 | received_message = response.received_messages[0] 31 | list_messages.append( 32 | dict((key, received_message.message.attributes[key]) for key in received_message.message.attributes)) 33 | print(f"Received: {received_message.message.attributes}.") 34 | subscriber.acknowledge(request={"subscription": subscription_path, 35 | "ack_ids": [received_message.ack_id]}) 36 | else: 37 | break 38 | print("Messages pulled from Pub/Sub") 39 | except: 40 | exception_handler("Message pulling from Pub/Sub failed") 41 | 42 | try: 43 | with open('data.json', 'w') as f: 44 | json.dump(list_messages, f) 45 | print("Saved output to JSON") 46 | except: 47 | exception_handler("Output to JSON failed") 48 | -------------------------------------------------------------------------------- /Server/pipeline/image-sr/src/uploader.py: -------------------------------------------------------------------------------- 1 | import re 2 | import json 3 | from error_handlers import exception_handler 4 | from google.cloud import storage 5 | 6 | 7 | def upload_image(source_file_name, url): 8 | bucket, object_path = re.findall("gs://(.*?)/(.*)", url)[0] 9 | upload_blob(bucket, source_file_name, object_path) 10 | 11 | 12 | def upload_blob(bucket_name, source_file_name, destination_blob_name): 13 | """Uploads a file to the bucket.""" 14 | 15 | storage_client = storage.Client.from_service_account_json( 16 | 'service_account.json') 17 | bucket = storage_client.bucket(bucket_name) 18 | blob = bucket.blob(destination_blob_name) 19 | 20 | blob.upload_from_filename(source_file_name) 21 | 22 | print(f"File {source_file_name} uploaded to {destination_blob_name}.") 23 | 24 | 25 | if __name__ == "__main__": 26 | try: 27 | with open("data.json", "r") as f: 28 | list_data = json.load(f) 29 | print("JSON file parsed") 30 | except: 31 | exception_handler("Read JSON failed") 32 | 33 | if(len(list_data) == 0): 34 | print("No image to upload, exiting...") 35 | exit(0) 36 | 37 | try: 38 | for data in list_data: 39 | upload_image(data["enhanced_file"], data["output_file"]) 40 | print("Image upload complete") 41 | except: 42 | exception_handler("Cannot establish connection to GCS") 43 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper-old/.dockerignore: -------------------------------------------------------------------------------- 1 | cron 2 | Dockerfile 3 | README.md 4 | .gitignore 5 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper-old/.gitignore: -------------------------------------------------------------------------------- 1 | service_account.json 2 | config.yaml 3 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper-old/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | 3 | WORKDIR /usr/local/PixivWallpaper 4 | ENV TZ=US/Eastern 5 | 6 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 7 | 8 | COPY . . 9 | 10 | RUN apt-get update 11 | RUN apt-get -y install procps git xvfb python3 php7.2 vim php-gd php-mysql python3-pip \ 12 | fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 \ 13 | libnspr4 libnss3 lsb-release xdg-utils libxss1 libdbus-glib-1-2 \ 14 | curl unzip wget firefox 15 | # Install node npm 16 | RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash - 17 | RUN apt-get update 18 | RUN apt-get install -y nodejs 19 | RUN curl -L https://www.npmjs.com/install.sh | sh 20 | RUN export GCSFUSE_REPO=gcsfuse-`lsb_release -c -s` && echo "deb http://packages.cloud.google.com/apt $GCSFUSE_REPO main" | tee /etc/apt/sources.list.d/gcsfuse.list 21 | RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - 22 | RUN apt-get update 23 | RUN apt-get -y install gcsfuse 24 | RUN mkdir images 25 | 26 | RUN pip3 install --upgrade pip 27 | RUN pip3 install -r requirements.txt 28 | 29 | RUN cd data_upload && npm i 30 | 31 | COPY ./geckodriver /usr/local/bin/ 32 | RUN chmod a+x /usr/local/bin/geckodriver 33 | ENV GOOGLE_APPLICATION_CREDENTIALS="/usr/local/PixivWallpaper/service_account.json" 34 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper-old/README.md: -------------------------------------------------------------------------------- 1 | ## How to run 2 | 3 | 1. Copy service account json credential to this folder, rename to `service_account.json` 4 | 5 | 2. Edit `config_template.yaml`, rename as `config.yaml` 6 | 7 | 3. Install docker 8 | 9 | 4. Add database connection credentials to `upload_user.php` 10 | 11 | 5. Run the following commands 12 | 13 | `docker build -t pixivwallpaper .` 14 | 15 | 16 | `docker run -d -it --privileged --name pw pixivwallpaper` 17 | 18 | 6. Run `crontab -e` and add the line in file `cron` 19 | 20 | 7. Schedule task to start instance every hour at 5 minutes (in cron term, `5 * * * *`) 21 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper-old/config_template.yaml: -------------------------------------------------------------------------------- 1 | db: 2 | uri: "" 3 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper-old/cron: -------------------------------------------------------------------------------- 1 | 10 * * * * ( docker start pw; docker exec pw bash /usr/local/PixivWallpaper/refresh.sh; /sbin/shutdown now ) >/root/log 2>&1 2 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper-old/data_upload/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and not Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Stores VSCode versions used for testing VSCode extensions 109 | .vscode-test 110 | 111 | # yarn v2 112 | .yarn/cache 113 | .yarn/unplugged 114 | .yarn/build-state.yml 115 | .yarn/install-state.gz 116 | .pnp.* 117 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper-old/data_upload/index.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const d3 = require("d3"); 3 | const yaml = require("js-yaml"); 4 | const { MongoClient } = require("mongodb"); 5 | 6 | let configFile = fs.readFileSync("config.yaml", "utf8"); 7 | let configs = yaml.safeLoad(configFile); 8 | const mongodbConnection = configs["db"]["uri"]; 9 | 10 | const client = new MongoClient(mongodbConnection, { 11 | useNewUrlParser: true, 12 | useUnifiedTopology: true, 13 | }); 14 | let csvFilePath = ""; 15 | let queueCounter = 0; 16 | let dateString = ""; 17 | 18 | function counterCheck() { 19 | if (queueCounter == 0) { 20 | data = { 21 | Status: "Complete", 22 | DateString: dateString, 23 | Created: new Date(Date.now()), 24 | Updated: new Date(Date.now()), 25 | }; 26 | client 27 | .db("AppData") 28 | .collection("PipelineStatus") 29 | .insertOne(data) 30 | .then(() => { 31 | client.close(); 32 | console.log("Client closed"); 33 | }); 34 | } 35 | } 36 | 37 | async function dbInsert(collectionName, data) { 38 | const database = client.db("AppData"); 39 | const collection = database.collection(collectionName); 40 | queueCounter++; 41 | const result = await collection.insertOne(data); 42 | console.log( 43 | `${result.insertedCount} documents were inserted with the _id: ${result.insertedId}` 44 | ); 45 | queueCounter--; 46 | counterCheck(); 47 | } 48 | 49 | duplicateCheckInsert = (field, collectionName, data) => { 50 | queryData = {}; 51 | queryData[field] = data[field]; 52 | queueCounter++; 53 | client 54 | .db("AppData") 55 | .collection(collectionName) 56 | .findOne(queryData) 57 | .then((res) => { 58 | if (res === null) { 59 | data["IsNew"] = true; 60 | } else { 61 | data["IsNew"] = false; 62 | } 63 | dbInsert(collectionName, data).catch(console.dir); 64 | queueCounter--; 65 | }); 66 | }; 67 | 68 | saveRanking = (data) => { 69 | [month, day, year] = data["Timestamp"].split("-"); 70 | data = { 71 | Rank: parseInt(data["Rank"]), 72 | IllustID: parseInt(data["IllustID"]), 73 | Downloaded: Boolean(parseInt(data["Downloaded"])), 74 | DateString: data["Timestamp"], 75 | Adult: data["Adult"], 76 | Created: new Date(Date.now()), 77 | Updated: new Date(Date.now()), 78 | }; 79 | duplicateCheckInsert("IllustID", "Ranking", data); 80 | }; 81 | 82 | saveLocation = (data) => { 83 | if (data["Downloaded"] < 0.5) { 84 | return; 85 | } 86 | data = { 87 | IllustID: parseInt(data["IllustID"]), 88 | Thumbnail: data["Thumbnail"], 89 | ThumbnailHeight: parseInt(data["ThumbnailHeight"]), 90 | ThumbnailWidth: parseInt(data["ThumbnailWidth"]), 91 | Original: data["Original"], 92 | OriginalHeight: parseInt(data["OriginalHeight"]), 93 | OriginalWidth: parseInt(data["OriginalWidth"]), 94 | Compressed: data["Compressed"], 95 | CompressedHeight: parseInt(data["CompressedHeight"]), 96 | CompressedWidth: parseInt(data["CompressedWidth"]), 97 | Created: new Date(Date.now()), 98 | Updated: new Date(Date.now()), 99 | }; 100 | dbInsert("ImageLocation", data).catch(console.dir); 101 | }; 102 | 103 | client.connect((err) => { 104 | csvFilePath = fs.readFileSync("csv_path", "utf8").trim(); 105 | 106 | fs.readFile(csvFilePath, "utf8", (err, csvData) => { 107 | let scraperData = d3.csvParse(csvData); 108 | dateString = scraperData[0]["Timestamp"]; 109 | for (let idx in scraperData) { 110 | if (idx == "columns") continue; 111 | row = scraperData[idx]; 112 | saveRanking(row); 113 | saveLocation(row); 114 | } 115 | counterCheck(); 116 | }); 117 | }); 118 | 119 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper-old/data_upload/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "data_upload", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "d3": "^5.16.0", 13 | "js-yaml": "^3.14.0", 14 | "mongodb": "^3.6.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper-old/geckodriver: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Server/pipeline/pixiv-scraper-old/geckodriver -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper-old/processing.php: -------------------------------------------------------------------------------- 1 | 90 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper-old/refresh.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | gcsfuse --key-file /usr/local/PixivWallpaper/service_account.json image_cache ./images 3 | python3 download.py 4 | if [ $? -eq 0 ] 5 | then 6 | echo "Download successful, continuing" 7 | kill `pgrep Xvfb` 8 | kill `pgrep geckodriver` 9 | else 10 | echo "Download failed, exiting" >&2 11 | kill `pgrep Xvfb` 12 | kill `pgrep geckodriver` 13 | exit 1 14 | fi 15 | node data_upload/index.js 16 | php upload.php 17 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper-old/requirements.txt: -------------------------------------------------------------------------------- 1 | selenium 2 | pyvirtualdisplay 3 | pandas 4 | requests 5 | beautifulsoup4 6 | google-cloud-vision 7 | opencv-python -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper-old/tests/timestamp.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from bs4 import BeautifulSoup 3 | 4 | URL_RANKING = "https://www.pixiv.net/ranking.php?mode=daily&content=illust" 5 | 6 | try: 7 | res = requests.get(URL_RANKING, timeout = 90, headers={"Accept-Language": "en-US"}) 8 | soup = BeautifulSoup(res.text,"html.parser") 9 | page_title = soup.find('title').getText() 10 | timestamp = page_title.split(" ")[-1].replace('/', '-') 11 | response = timestamp 12 | except Exception as e: 13 | response = "An error occurred" 14 | print(e) 15 | 16 | print(response) 17 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper-old/update_detector/README.md: -------------------------------------------------------------------------------- 1 | Detects date changes on [Pixiv Illustrations Daily Rankings](https://www.pixiv.net/ranking.php?mode=daily&content=illust) 2 | and starts pipeline execution when changes are detected. 3 | 4 | Runs on Google Cloud Function. 5 | 6 | Triggered by Google Cloud Scheduler. 7 | 8 | ### Deployment 9 | 10 | 1. Create a new service account with `Cloud Functions Invoker` role 11 | 12 | 2. Copy and save `Trigger URL` 13 | 14 | 3. Select `App Engine default service account` under `Service account` 15 | 16 | 4. Add `API_URL`, `PROJECT`, `ZONE`, `INSTANCE_NAME` under `Environment variables` with configurations of the instance running the pipeline 17 | 18 | 5. Select `Python 3.7` under `Runtime`, specify `main` under `Entry point` 19 | 20 | 6. Copy code to `main.py` and dependencies to `requirements.txt`, then deploy 21 | 22 | 7. In Cloud Scheduler, create a new job and copy the `Trigger URL` to under `URL` and specify `GET` under `HTTP method` 23 | 24 | 8. Use `5 * * * *` as frequency 25 | 26 | 9. Specify `Add OIDC token` under `Auth header` and use the created service account as `Service account` 27 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper-old/update_detector/requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4 -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper-old/update_detector/update_detector.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from bs4 import BeautifulSoup 3 | import googleapiclient.discovery 4 | import traceback 5 | import json 6 | import os 7 | 8 | URL_RANKING = "https://www.pixiv.net/ranking.php?mode=daily&content=illust" 9 | API_URL = os.getenv("API_URL") 10 | PROJECT = os.getenv("PROJECT") 11 | ZONE = os.getenv("ZONE") 12 | INSTANCE_NAME = os.getenv("INSTANCE_NAME") 13 | 14 | 15 | def get_pipeline_timestamp(): 16 | res = requests.get(API_URL, timeout=90) 17 | json_res = res.json() 18 | timestamp = json_res['latest'] 19 | return timestamp 20 | 21 | 22 | def get_pixiv_timestamp(): 23 | res = requests.get(URL_RANKING, timeout=90, headers={ 24 | "Accept-Language": "en-US"}) 25 | soup = BeautifulSoup(res.text, "html.parser") 26 | page_title = soup.find('title').getText() 27 | timestamp = page_title.split(" ")[-1].replace('/', '-') 28 | return timestamp 29 | 30 | 31 | def start_instance(): 32 | compute = googleapiclient.discovery.build( 33 | 'compute', 'v1', cache_discovery=False) 34 | command = compute.instances().start( 35 | project=PROJECT, zone=ZONE, instance=INSTANCE_NAME) 36 | json_res = json.dumps(command.execute()) 37 | return(json_res) 38 | 39 | 40 | def main(request): 41 | try: 42 | latest_timestamp = get_pipeline_timestamp() 43 | pixiv_timestamp = get_pixiv_timestamp() 44 | if pixiv_timestamp == latest_timestamp: 45 | print("No update detected") 46 | else: 47 | print("Update detected") 48 | start_instance() 49 | return "Success" 50 | except Exception as e: 51 | print(e) 52 | print(traceback.print_exc()) 53 | return str(e) 54 | 55 | 56 | if __name__ == "__main__": 57 | print(main("")) -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper-old/upload.php: -------------------------------------------------------------------------------- 1 | setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 31 | 32 | function upload_img($img_path, $ranking, $illustid, $format, $type, $timestamp) 33 | { 34 | $pdo = $GLOBALS["pdo"]; 35 | if ($type == "D") { 36 | $query = $pdo->prepare("INSERT INTO images(Image,Width,Height,AspectRatio,Checksum,Format,Type,IllustID,Ranking,TimeStamp) VALUES(:img,:width,:height,:ratio,MD5(Image),:format,:type,:illustid,:ranking,:timestamp)"); 37 | $duplicate_query = $pdo->prepare("SELECT COUNT(*) FROM images WHERE Checksum=?"); 38 | } else if ($type == "T") { 39 | $query = $pdo->prepare("INSERT INTO images_t(Image,Width,Height,AspectRatio,Checksum,Format,Type,IllustID,Ranking,AvgGradient,Variance,TimeStamp) VALUES(:img,:width,:height,:ratio,MD5(Image),:format,:type,:illustid,:ranking,:gradient,:variance,:timestamp)"); 40 | $duplicate_query = $pdo->prepare("SELECT COUNT(*) FROM images_t WHERE Checksum=?"); 41 | } else if ($type == "L") { 42 | $query = $pdo->prepare("INSERT INTO images_l(Image,Width,Height,AspectRatio,Checksum,Format,Type,IllustID,Ranking,TimeStamp) VALUES(:img,:width,:height,:ratio,MD5(Image),:format,:type,:illustid,:ranking,:timestamp)"); 43 | $duplicate_query = $pdo->prepare("SELECT COUNT(*) FROM images_l WHERE IllustID=?"); 44 | } 45 | 46 | $img = fopen($img_path, "rb"); 47 | $md5 = md5_file($img_path); 48 | 49 | $size = getimagesize($img_path); 50 | $ratio = $size[0] / $size[1]; 51 | 52 | if (($size[0] + $size[1]) > 6000) { 53 | return 0; 54 | } 55 | 56 | if ($type == "L") { 57 | if ($size[0] > 2560 || filesize($img_path) > 8388608) { 58 | $im = load_img($img_path); 59 | $im = orig_scale($im); 60 | ob_start(); 61 | if ($format == "gif") { 62 | imagegif($im); 63 | } else { 64 | imagejpeg($im); 65 | $format = "jpg"; 66 | } 67 | $img = ob_get_clean(); 68 | $size[0] = imagesx($im); 69 | $size[1] = imagesy($im); 70 | $ratio = $size[0] / $size[1]; 71 | } else { 72 | $type = "O"; 73 | } 74 | } 75 | 76 | if ($type == "L" || $type == "O") { 77 | $duplicate_query->execute(array($illustid)); 78 | if ($duplicate_query->fetch()[0] != 0) { 79 | return 0; 80 | } 81 | } else { 82 | $duplicate_query->execute(array($md5)); 83 | if ($duplicate_query->fetch()[0] != 0) { 84 | return 0; 85 | } 86 | } 87 | 88 | # Upload image 89 | $query->bindParam(":img", $img, PDO::PARAM_LOB); 90 | 91 | # Size 92 | $query->bindParam(":width", $size[0]); 93 | $query->bindParam(":height", $size[1]); 94 | $query->bindParam(":ratio", $ratio); 95 | 96 | # File info 97 | $query->bindParam(":format", $format); 98 | $query->bindParam(":type", $type); 99 | 100 | if ($type == "T") { 101 | $gradient = calc_gradient($img_path); 102 | $query->bindParam(":gradient", $gradient); 103 | $variance = calc_variance($img_path); 104 | $query->bindParam(":variance", $variance); 105 | } 106 | 107 | # Artwork info 108 | $query->bindParam(":ranking", $ranking); 109 | $query->bindParam(":illustid", $illustid); 110 | $query->bindParam(":timestamp", $timestamp); 111 | 112 | $query->execute(); 113 | return 1; 114 | } 115 | 116 | foreach ($csv as $row) { 117 | if ($row["Downloaded"] == 1) { 118 | $ranking = $row["Rank"]; 119 | $illustid = $row["IllustID"]; 120 | $timestamp = date('Y-m-d', strtotime(str_replace('-', '/', $row["Timestamp"]))); 121 | 122 | $img_path ="/usr/local/PixivWallpaper/images" . explode("image_cache",$row["Original"])[1]; 123 | $format = end(explode(".", $row["Original"])); 124 | $status = upload_img($img_path, $ranking, $illustid, $format, "L", $timestamp); 125 | 126 | $img_path ="/usr/local/PixivWallpaper/images" . explode("image_cache",$row["Compressed"])[1]; 127 | $format = end(explode(".", $row["Compressed"])); 128 | if ($status == 1) 129 | upload_img($img_path, $ranking, $illustid, $format, "D", $timestamp); 130 | 131 | $img_path ="/usr/local/PixivWallpaper/images" . explode("image_cache",$row["Thumbnail"])[1]; 132 | $format = end(explode(".", $row["Thumbnail"])); 133 | if ($status == 1) 134 | upload_img($img_path, $ranking, $illustid, $format, "T", $timestamp); 135 | } 136 | } 137 | 138 | echo "Insert successful"; 139 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper-old/upload_user.php: -------------------------------------------------------------------------------- 1 | 8 | 9 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper/.gitignore: -------------------------------------------------------------------------------- 1 | nohup.out 2 | src/script_configs.sh 3 | src/Dockerfile 4 | src/data.json 5 | src/service_account.json 6 | src/__pycache__/ 7 | src/imgs/ -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper/README.md: -------------------------------------------------------------------------------- 1 | # Instructions 2 | 3 | 1. Clone this repo to your Cloud Shell 4 | 5 | 2. Edit config files under `./configs/` 6 | 7 | 3. Run `bash ./deploy.sh ${ENV}`. Replace `${ENV}` with environment -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper/configs/dev/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | 3 | WORKDIR /usr/src/app 4 | ENV TZ=US/Eastern 5 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 6 | COPY . . 7 | COPY ./geckodriver /usr/local/bin/ 8 | RUN chmod a+x /usr/local/bin/geckodriver 9 | 10 | RUN apt update && apt -y install procps git xvfb python3 vim python3-pip \ 11 | fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 \ 12 | libnspr4 libnss3 lsb-release xdg-utils libxss1 libdbus-glib-1-2 \ 13 | curl unzip wget firefox 14 | # Install node npm 15 | RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash - 16 | RUN apt update && apt install -y nodejs 17 | RUN curl -L https://www.npmjs.com/install.sh | sh 18 | RUN pip3 install --upgrade pip && pip3 install -r requirements.txt 19 | RUN cd data_upload && npm i 20 | CMD ["sh", "-c", "tail -f /dev/null"] -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper/configs/dev/deploy_configs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | GCP_PROJ="" 4 | GKE_CLUSTER_NAME="" 5 | GKE_CLUSTER_ZONE="" 6 | # Repository name in Google Container Registry 7 | DOCKER_IMG_NAME="" 8 | # GKE cluster node pool 9 | NODE_POOL="" 10 | # The bucket name and object name of service account json file saved in Google Cloud Storage 11 | SA_BUCKET="" 12 | SA_OBJECT="" 13 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper/configs/dev/script_configs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | GCP_PROJ="" 4 | # The bucket to where images will be downloaded 5 | DOWNLOAD_BUCKET_NAME="" 6 | # Connection URI to MongoDB 7 | MONGO_URI="" 8 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -a 5 | 6 | echo "Execution started" 7 | date 8 | 9 | echo "Getting environment: ${1}" 10 | CURR_ENV=${1} 11 | 12 | echo "Loading environmental variables" 13 | . ./configs/${CURR_ENV}/deploy_configs.sh 14 | 15 | echo "Copying config files" 16 | cp ./configs/${CURR_ENV}/script_configs.sh ./src/ 17 | cp ./configs/${CURR_ENV}/Dockerfile ./src/ 18 | 19 | echo "Downloading service account" 20 | gsutil cp gs://${SA_BUCKET}/${SA_OBJECT} ./src/service_account.json 21 | 22 | pushd src 23 | echo "Setting project" 24 | gcloud config set project ${GCP_PROJ} 25 | echo "Submitting cloud build job" 26 | gcloud builds submit --tag gcr.io/${GCP_PROJ}/${DOCKER_IMG_NAME} 27 | popd 28 | 29 | pushd ./k8s/${CURR_ENV} 30 | echo "Connecting to GKE cluster" 31 | gcloud container clusters get-credentials ${GKE_CLUSTER_NAME} --zone ${GKE_CLUSTER_ZONE} --project ${GCP_PROJ} 32 | echo "Deploying pods in GKE cluster" 33 | envsubst < deploy.yaml | kubectl apply -f - 34 | popd 35 | 36 | echo "Execution completed" 37 | date 38 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper/k8s/dev/deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: ${DOCKER_IMG_NAME} 5 | spec: 6 | containers: 7 | - name: ${DOCKER_IMG_NAME} 8 | image: gcr.io/${GCP_PROJ}/${DOCKER_IMG_NAME}:latest 9 | nodeSelector: 10 | cloud.google.com/gke-nodepool: ${NODE_POOL} 11 | restartPolicy: Never 12 | tolerations: 13 | - key: "EvictSystem" 14 | operator: "Exists" 15 | effect: "NoSchedule" -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper/src/data_upload/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and not Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Stores VSCode versions used for testing VSCode extensions 109 | .vscode-test 110 | 111 | # yarn v2 112 | .yarn/cache 113 | .yarn/unplugged 114 | .yarn/build-state.yml 115 | .yarn/install-state.gz 116 | .pnp.* 117 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper/src/data_upload/index.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const yaml = require("js-yaml"); 3 | const { MongoClient } = require("mongodb"); 4 | 5 | const mongodbConnection = process.env.MONGO_URI; 6 | 7 | const client = new MongoClient(mongodbConnection, { 8 | useNewUrlParser: true, 9 | useUnifiedTopology: true, 10 | }); 11 | let queueCounter = 0; 12 | let dateString = ""; 13 | 14 | function counterCheck() { 15 | if (queueCounter == 0) { 16 | data = { 17 | Status: "Complete", 18 | DateString: dateString, 19 | Created: new Date(Date.now()), 20 | Updated: new Date(Date.now()), 21 | }; 22 | client 23 | .db("PWFData") 24 | .collection("PipelineStatus") 25 | .insertOne(data) 26 | .then(() => { 27 | client.close(); 28 | console.log("Client closed"); 29 | }); 30 | } 31 | } 32 | 33 | async function dbInsert(collectionName, data) { 34 | const database = client.db("PWFData"); 35 | const collection = database.collection(collectionName); 36 | queueCounter++; 37 | const result = await collection.insertOne(data); 38 | console.log( 39 | `${result.insertedCount} documents were inserted with the _id: ${result.insertedId}` 40 | ); 41 | queueCounter--; 42 | counterCheck(); 43 | } 44 | 45 | duplicateCheckInsert = (field, collectionName, data) => { 46 | queryData = {}; 47 | queryData[field] = data[field]; 48 | queueCounter++; 49 | client 50 | .db("PWFData") 51 | .collection(collectionName) 52 | .findOne(queryData) 53 | .then((res) => { 54 | if (res === null) { 55 | data["IsNew"] = true; 56 | } else { 57 | data["IsNew"] = false; 58 | } 59 | dbInsert(collectionName, data).catch(console.dir); 60 | queueCounter--; 61 | }); 62 | }; 63 | 64 | saveRanking = (data) => { 65 | [month, day, year] = data["Timestamp"].split("-"); 66 | data = { 67 | Rank: parseInt(data["Rank"]), 68 | IllustID: parseInt(data["IllustID"]), 69 | Downloaded: Boolean(parseInt(data["Downloaded"])), 70 | DateString: data["Timestamp"], 71 | AspectRatio: data["AspectRatio"], 72 | Adult: data["Adult"], 73 | Created: new Date(Date.now()), 74 | Updated: new Date(Date.now()), 75 | }; 76 | duplicateCheckInsert("IllustID", "Ranking", data); 77 | }; 78 | 79 | saveLocation = (data) => { 80 | if (data["Downloaded"] < 0.5) { 81 | return; 82 | } 83 | data = { 84 | IllustID: parseInt(data["IllustID"]), 85 | Thumbnail: data["Thumbnail"], 86 | ThumbnailHeight: parseInt(data["ThumbnailHeight"]), 87 | ThumbnailWidth: parseInt(data["ThumbnailWidth"]), 88 | Original: data["Original"], 89 | OriginalHeight: parseInt(data["OriginalHeight"]), 90 | OriginalWidth: parseInt(data["OriginalWidth"]), 91 | Compressed: data["Compressed"], 92 | CompressedHeight: parseInt(data["CompressedHeight"]), 93 | CompressedWidth: parseInt(data["CompressedWidth"]), 94 | Created: new Date(Date.now()), 95 | Updated: new Date(Date.now()), 96 | }; 97 | dbInsert("ImageLocation", data).catch(console.dir); 98 | }; 99 | 100 | client.connect((err) => { 101 | fs.readFile("data.json", "utf8", (err, rawData) => { 102 | let scraperData = JSON.parse(rawData); 103 | dateString = scraperData[0]["Timestamp"]; 104 | scraperData.forEach((entry) => { 105 | saveRanking(entry); 106 | saveLocation(entry); 107 | }) 108 | counterCheck(); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper/src/data_upload/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "data_upload", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "js-yaml": "^3.14.0", 13 | "mongodb": "^3.6.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper/src/error_handlers.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import traceback 3 | 4 | 5 | def exception_handler(error_msg): 6 | print(error_msg, file=sys.stderr) 7 | traceback.print_exc() 8 | exit(1) 9 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper/src/exec.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -a 5 | 6 | echo "Loading environmental variables" 7 | echo "====================" 8 | . ./script_configs.sh 9 | echo "" 10 | 11 | echo "Downloading images" 12 | echo "====================" 13 | python3 download.py 14 | echo "" 15 | 16 | echo "Uploading images to database" 17 | echo "====================" 18 | node data_upload/index.js 19 | echo "" 20 | 21 | echo "Cleaning up workspace" 22 | echo "====================" 23 | set +e 24 | rm -Rf ./imgs 25 | kill `pgrep Xvfb` 26 | kill `pgrep geckodriver` 27 | echo "" 28 | -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper/src/geckodriver: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingularityF/PixivWallpaper/8534dcd31a1555397776c3bc7a9efb843f6417fc/Server/pipeline/pixiv-scraper/src/geckodriver -------------------------------------------------------------------------------- /Server/pipeline/pixiv-scraper/src/requirements.txt: -------------------------------------------------------------------------------- 1 | selenium 2 | pyvirtualdisplay 3 | requests 4 | beautifulsoup4 5 | opencv-python 6 | google-cloud-vision 7 | google-cloud-storage -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1 3 | } 4 | --------------------------------------------------------------------------------