├── .babelrc
├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── files
└── GeoLite2-Country.mmdb
├── package.json
├── postcss.config.js
├── public
├── fonts
│ ├── Lato-Black.ttf
│ ├── Lato-BlackItalic.ttf
│ ├── Lato-Bold.ttf
│ ├── Lato-BoldItalic.ttf
│ ├── Lato-Hairline.ttf
│ ├── Lato-HairlineItalic.ttf
│ ├── Lato-Italic.ttf
│ ├── Lato-Light.ttf
│ ├── Lato-LightItalic.ttf
│ └── Lato-Regular.ttf
├── icons
│ └── icon.ico
├── index.html
└── styles
│ ├── Elements.postcss
│ ├── Footer.postcss
│ ├── Icons.postcss
│ ├── Main.postcss
│ ├── Process.postcss
│ ├── Result.postcss
│ └── Update.postcss
├── src
├── actions
│ ├── MainActions.js
│ ├── ProcessActions.js
│ ├── ResultActions.js
│ └── UpdateActions.js
├── components
│ ├── Footer.jsx
│ ├── ProcessCrawl.jsx
│ ├── ProcessCrawlLevel.jsx
│ ├── ProcessParse.jsx
│ ├── ResultCountryItem.jsx
│ ├── Root.jsx
│ └── ui
│ │ └── Checkbox.jsx
├── constants
│ ├── ActionTypes.js
│ └── SettingsConstants.js
├── containers
│ ├── Main.jsx
│ ├── Process.jsx
│ ├── Result.jsx
│ └── Update.jsx
├── core
│ ├── country.js
│ ├── links.js
│ ├── parser.js
│ ├── settings.js
│ └── updater.js
├── index.js
├── misc
│ ├── methods.js
│ ├── other.js
│ ├── regexes.js
│ └── text.js
├── renderer.dev.js
├── renderer.js
└── store
│ ├── index.js
│ └── reducers
│ ├── index.js
│ ├── main.js
│ ├── process.js
│ ├── result.js
│ └── update.js
├── webpack.config.base.js
├── webpack.config.main.babel.js
└── webpack.config.renderer.babel.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/env", "@babel/react"],
3 | "plugins": ["@babel/plugin-proposal-class-properties", "@babel/plugin-transform-runtime", ["@babel/plugin-proposal-object-rest-spread", { "useBuiltIns": false }]],
4 | "env": {
5 | "development": {
6 | "plugins": ["react-hot-loader/babel"]
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | public/styles/* linguist-vendored
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | settings.unfx.parser.json
4 | /public/*.js
5 | /.vscode
6 | dist
7 | package-lock.json
8 | tests
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 assnctr
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 
6 | 
7 |
8 | ## Features
9 | - Parsing from sites with tables
10 | - Parsing from primitive sites
11 | - Sorting proxies by countries
12 | - `Deep` - Allow to follow links
13 | - `External` - Allow to follow third-party domains
14 | - `Retry` - Retries crawl/parse if been received bad response
15 |
16 | ## Results
17 | Saving proxies in `ip` : `port` format.
18 |
19 | **NOTE:**
20 |
21 | `Double click` - select/deselect all countries.
22 |
23 | ## Updates
24 | Automatically checking for updates and notification if the latest version is available.
25 |
26 | #### Open Proxy Space
27 | [Premium](https://openproxy.space/premium) - Buy Proxy List
28 | [Free Proxy List](https://openproxy.space/list) - Always Updated Proxy Lists
29 |
--------------------------------------------------------------------------------
/files/GeoLite2-Country.mmdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openproxyspace/unfx-proxy-parser/2736d0345fdde40c9bd4a414bc6d29422f83d23b/files/GeoLite2-Country.mmdb
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "unfx-proxy-parser",
3 | "version": "2.0.0",
4 | "main": "public/main.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "build": "run-p build:*",
8 | "build:main": "cross-env NODE_ENV=production webpack -p --config webpack.config.main.babel.js",
9 | "build:renderer": "cross-env NODE_ENV=production webpack -p --config webpack.config.renderer.babel.js",
10 | "start": "run-p start:*",
11 | "start:main": "electron --require @babel/register src/index",
12 | "start:renderer": "cross-env NODE_ENV=development webpack-dev-server -d --config webpack.config.renderer.babel.js",
13 | "package": "npm run build && electron-builder --win",
14 | "publish": "npm run build && electron-builder --linux --win --publish always"
15 | },
16 | "devDependencies": {
17 | "@babel/cli": "^7.1.5",
18 | "@babel/core": "^7.1.6",
19 | "@babel/plugin-proposal-class-properties": "^7.1.0",
20 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0",
21 | "@babel/plugin-transform-runtime": "^7.1.0",
22 | "@babel/preset-env": "^7.1.6",
23 | "@babel/preset-react": "^7.0.0",
24 | "@babel/register": "^7.0.0",
25 | "@babel/runtime": "^7.1.5",
26 | "babel-loader": "^8.0.4",
27 | "cross-env": "^5.2.0",
28 | "css-loader": "^1.0.1",
29 | "electron": "^4.0.0",
30 | "electron-builder": "^20.36.2",
31 | "electron-devtools-installer": "^2.2.4",
32 | "electron-react-devtools": "^0.5.3",
33 | "file-loader": "^2.0.0",
34 | "js-flock": "^3.5.3",
35 | "mmdb-reader": "*",
36 | "npm-run-all": "^4.1.3",
37 | "postcss-color-mod-function": "^2.4.3",
38 | "postcss-loader": "^3.0.0",
39 | "postcss-preset-env": "^5.3.0",
40 | "react": "^16.6.3",
41 | "react-dom": "^16.6.3",
42 | "react-hot-loader": "^4.3.12",
43 | "react-markdown": "^4.0.3",
44 | "react-redux": "^5.1.1",
45 | "redux": "^4.0.1",
46 | "redux-thunk": "^2.3.0",
47 | "request-progress": "^3.0.0",
48 | "request-promise": "^4.2.2",
49 | "style-loader": "^0.23.1",
50 | "url-loader": "^1.1.2",
51 | "webpack": "^4.25.1",
52 | "webpack-cli": "^3.1.2",
53 | "webpack-dev-server": "^3.1.10"
54 | },
55 | "build": {
56 | "appId": "com.github.assnctr.unfxproxyparser",
57 | "win": {
58 | "target": [
59 | {
60 | "target": "zip",
61 | "arch": [
62 | "x64",
63 | "ia32"
64 | ]
65 | },
66 | {
67 | "target": "nsis-web",
68 | "arch": [
69 | "x64",
70 | "ia32"
71 | ]
72 | },
73 | {
74 | "target": "portable",
75 | "arch": [
76 | "x64",
77 | "ia32"
78 | ]
79 | }
80 | ],
81 | "icon": "/public/icons/icon.ico",
82 | "artifactName": "${name}-v${version}-${arch}-${os}.${ext}"
83 | },
84 | "linux": {
85 | "target": [
86 | {
87 | "target": "zip",
88 | "arch": [
89 | "x64",
90 | "ia32",
91 | "arm64",
92 | "armv7l"
93 | ]
94 | }
95 | ],
96 | "icon": "/public/icons/icon.ico",
97 | "artifactName": "${name}-v${version}-${arch}-${os}.${ext}"
98 | },
99 | "publish": [
100 | {
101 | "provider": "github",
102 | "owner": "assnctr",
103 | "repo": "unfx-proxy-parser",
104 | "private": false
105 | }
106 | ],
107 | "productName": "Unfx Proxy Parser",
108 | "copyright": "2019 assnctr (openproxy.space)",
109 | "extraResources": [
110 | "./files/**"
111 | ],
112 | "portable": {
113 | "artifactName": "${name}-v${version}-${arch}-${os}-portable.${ext}"
114 | },
115 | "nsisWeb": {
116 | "oneClick": false,
117 | "perMachine": true,
118 | "allowToChangeInstallationDirectory": true,
119 | "differentialPackage": true
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | 'postcss-preset-env': {
4 | stage: 4,
5 | features: {
6 | 'nesting-rules': true
7 | }
8 | },
9 | 'postcss-color-mod-function': {}
10 | }
11 | };
12 |
--------------------------------------------------------------------------------
/public/fonts/Lato-Black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openproxyspace/unfx-proxy-parser/2736d0345fdde40c9bd4a414bc6d29422f83d23b/public/fonts/Lato-Black.ttf
--------------------------------------------------------------------------------
/public/fonts/Lato-BlackItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openproxyspace/unfx-proxy-parser/2736d0345fdde40c9bd4a414bc6d29422f83d23b/public/fonts/Lato-BlackItalic.ttf
--------------------------------------------------------------------------------
/public/fonts/Lato-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openproxyspace/unfx-proxy-parser/2736d0345fdde40c9bd4a414bc6d29422f83d23b/public/fonts/Lato-Bold.ttf
--------------------------------------------------------------------------------
/public/fonts/Lato-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openproxyspace/unfx-proxy-parser/2736d0345fdde40c9bd4a414bc6d29422f83d23b/public/fonts/Lato-BoldItalic.ttf
--------------------------------------------------------------------------------
/public/fonts/Lato-Hairline.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openproxyspace/unfx-proxy-parser/2736d0345fdde40c9bd4a414bc6d29422f83d23b/public/fonts/Lato-Hairline.ttf
--------------------------------------------------------------------------------
/public/fonts/Lato-HairlineItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openproxyspace/unfx-proxy-parser/2736d0345fdde40c9bd4a414bc6d29422f83d23b/public/fonts/Lato-HairlineItalic.ttf
--------------------------------------------------------------------------------
/public/fonts/Lato-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openproxyspace/unfx-proxy-parser/2736d0345fdde40c9bd4a414bc6d29422f83d23b/public/fonts/Lato-Italic.ttf
--------------------------------------------------------------------------------
/public/fonts/Lato-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openproxyspace/unfx-proxy-parser/2736d0345fdde40c9bd4a414bc6d29422f83d23b/public/fonts/Lato-Light.ttf
--------------------------------------------------------------------------------
/public/fonts/Lato-LightItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openproxyspace/unfx-proxy-parser/2736d0345fdde40c9bd4a414bc6d29422f83d23b/public/fonts/Lato-LightItalic.ttf
--------------------------------------------------------------------------------
/public/fonts/Lato-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openproxyspace/unfx-proxy-parser/2736d0345fdde40c9bd4a414bc6d29422f83d23b/public/fonts/Lato-Regular.ttf
--------------------------------------------------------------------------------
/public/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openproxyspace/unfx-proxy-parser/2736d0345fdde40c9bd4a414bc6d29422f83d23b/public/icons/icon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Unfx Proxy Parser
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/public/styles/Elements.postcss:
--------------------------------------------------------------------------------
1 | :root {
2 | --green-color: #31bc86;
3 | --grey-color: #8c99a7;
4 | --blue-color: #5396d8;
5 | }
6 |
7 | ::-webkit-scrollbar {
8 | width: 15px;
9 | border-radius: 0.25em;
10 | }
11 |
12 | ::-webkit-scrollbar-thumb {
13 | background-color: color-mod(var(--blue-color) alpha(15%));
14 | border-radius: 0.25em;
15 | }
16 |
17 | ::-webkit-scrollbar-track {
18 | background-color: #fafafa;
19 | }
20 |
21 | /* --------------------------------
22 | Buttons section
23 | -------------------------------- */
24 |
25 | button {
26 | font-weight: 900;
27 | font-size: 1em;
28 | border-radius: 1.75em;
29 | outline: 0;
30 | border: 0;
31 | cursor: pointer;
32 | padding: 0.5em 1.5em;
33 | margin-right: 2em;
34 | background-color: color-mod(var(--green-color) alpha(15%));
35 | color: color-mod(var(--green-color) alpha(75%));
36 | transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
37 |
38 | &.blue {
39 | background-color: color-mod(var(--blue-color) alpha(15%));
40 | color: color-mod(var(--blue-color) alpha(75%));
41 | }
42 |
43 | &.less {
44 | padding: 0;
45 | background-color: transparent;
46 | color: color-mod(var(--green-color) alpha(50%));
47 |
48 | &:hover {
49 | color: color-mod(var(--green-color) alpha(75%));
50 | }
51 | }
52 |
53 | &:not(.less):hover {
54 | color: rgba(255, 255, 255, 0.75);
55 | background-color: rgba(49, 188, 135, 0.65);
56 |
57 | &.blue {
58 | background-color: color-mod(var(--blue-color) alpha(75%));
59 | }
60 | }
61 | }
62 |
63 | /* --------------------------------
64 | Inputs section
65 | -------------------------------- */
66 |
67 | textarea {
68 | font-weight: 400;
69 | border-radius: 0.35em;
70 | border: 0;
71 | outline: 0;
72 | padding: 1em;
73 | width: 100%;
74 | font-size: 1em;
75 | box-shadow: none;
76 | background-color: color-mod(var(--green-color) alpha(5%));
77 | color: #31bc86;
78 | resize: vertical;
79 | transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
80 | }
81 |
82 | input.field {
83 | width: 100%;
84 | border: 0;
85 | outline: 0;
86 | background-color: color-mod(var(--green-color) alpha(5%));
87 | font-weight: 900;
88 | padding: 0.5em 0.75em;
89 | border-radius: 1.75em;
90 | font-size: 1em;
91 | color: color-mod(var(--green-color) alpha(50%));
92 | transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1);
93 |
94 | &::-webkit-input-placeholder {
95 | color: color-mod(var(--green-color) alpha(25%));
96 | }
97 |
98 | &:not([value='']) {
99 | background-color: color-mod(var(--green-color) alpha(15%));
100 | }
101 |
102 | &:focus {
103 | color: #fff;
104 | background-color: color-mod(var(--green-color) alpha(65%));
105 |
106 | &::-webkit-input-placeholder {
107 | color: transparent;
108 | }
109 | }
110 | }
111 |
112 | input[type='range'] {
113 | -webkit-appearance: none;
114 | width: 100%;
115 | margin: 0;
116 | outline: none;
117 | padding: 0;
118 |
119 | &::-webkit-slider-runnable-track {
120 | width: 100%;
121 | height: 0.8em;
122 | cursor: pointer;
123 | border-radius: 1em;
124 | background-image: linear-gradient(to right, color-mod(var(--green-color) alpha(15%)), color-mod(var(--blue-color) alpha(15%)));
125 | }
126 |
127 | &::-webkit-slider-thumb {
128 | height: 16px;
129 | width: 16px;
130 | background-color: color-mod(var(--green-color) alpha(75%));
131 | cursor: pointer;
132 | -webkit-appearance: none;
133 | border-radius: 1em;
134 | margin-top: -0.2em;
135 | }
136 | }
137 |
138 | /* --------------------------------
139 | Checkboxes section
140 | -------------------------------- */
141 |
142 | .checkbox {
143 | display: inline-flex;
144 | margin: auto;
145 | user-select: none;
146 | cursor: pointer;
147 | padding-top: 1em;
148 | padding-right: 2em;
149 |
150 | &:last-child {
151 | padding-right: 0;
152 | }
153 |
154 | & span.count {
155 | margin-left: 0.5em;
156 | transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
157 | color: color-mod(var(--grey-color) alpha(15%));
158 | }
159 |
160 | & span:not(.count) {
161 | display: inline-block;
162 | vertical-align: middle;
163 | font-weight: 900;
164 | margin-top: -0.2em;
165 | transform: translate3d(0, 0, 0);
166 | color: color-mod(var(--green-color) alpha(35%));
167 | transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
168 |
169 | &:first-child {
170 | position: relative;
171 | width: 1.25em;
172 | height: 1.25em;
173 | border-radius: 1.25em;
174 | vertical-align: middle;
175 | transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
176 | background-color: color-mod(var(--green-color) alpha(15%));
177 |
178 | & svg {
179 | position: absolute;
180 | top: 5px;
181 | left: 4px;
182 | fill: none;
183 | stroke: #ffffff;
184 | stroke-width: 1.1;
185 | stroke-linecap: round;
186 | stroke-linejoin: round;
187 | stroke-dasharray: 1em;
188 | stroke-dashoffset: 1em;
189 | transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
190 | transition-delay: 0.1s;
191 | transform: translate3d(0, 0, 0);
192 | }
193 |
194 | &:before {
195 | content: '';
196 | width: 100%;
197 | height: 100%;
198 | background-color: color-mod(var(--green-color) alpha(75%));
199 | display: block;
200 | transform: scale(0);
201 | opacity: 1;
202 | border-radius: 50%;
203 | }
204 | }
205 |
206 | &:last-child {
207 | padding-left: 0.5em;
208 | font-size: 0.95em;
209 | }
210 | }
211 |
212 | &:hover span:first-child {
213 | border-color: color-mod(var(--green-color) alpha(75%));
214 | }
215 | }
216 |
217 | .input-checkbox {
218 | display: none;
219 | }
220 |
221 | .input-checkbox:checked + .checkbox span.count {
222 | color: color-mod(var(--grey-color) alpha(65%));
223 | }
224 |
225 | .input-checkbox:checked + .checkbox span:not(.count) {
226 | color: color-mod(var(--green-color) alpha(75%));
227 |
228 | &:first-child {
229 | background-color: rgba(49, 188, 135, 0.75);
230 | border-color: rgba(49, 188, 135, 0.75);
231 |
232 | & svg {
233 | stroke-dashoffset: 0;
234 | }
235 |
236 | &:before {
237 | transform: scale(3.5);
238 | opacity: 0;
239 | transition: all 0.7s cubic-bezier(0.23, 1, 0.32, 1);
240 | }
241 | }
242 | }
243 |
--------------------------------------------------------------------------------
/public/styles/Footer.postcss:
--------------------------------------------------------------------------------
1 | :root {
2 | --green-color: #31bc86;
3 | --grey-color: #8c99a7;
4 | }
5 |
6 | footer {
7 | display: flex;
8 |
9 | & .marks {
10 | display: flex;
11 | align-items: center;
12 |
13 | & .product-name {
14 | font-weight: 700;
15 | color: color-mod(var(--green-color) alpha(50%));
16 | }
17 |
18 | & .version {
19 | font-weight: 700;
20 | font-size: 0.9em;
21 | color: #fff;
22 | background-color: color-mod(var(--green-color) alpha(35%));
23 | border-radius: 1em;
24 | padding: 0.2em 0.65em;
25 | }
26 |
27 | & a {
28 | font-weight: 900;
29 | margin-right: 1em;
30 | transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
31 |
32 | &:hover {
33 | text-decoration: underline;
34 | color: color-mod(var(--green-color) alpha(75%));
35 | }
36 |
37 | &.copyright {
38 | color: var(--grey-color);
39 | font-weight: 400;
40 | text-decoration: none;
41 |
42 | &:hover {
43 | color: #0088cc;
44 |
45 | & svg {
46 | fill: #0088cc;
47 | }
48 | }
49 |
50 | & svg {
51 | width: 1em;
52 | height: 1em;
53 | fill: #8c99a7;
54 | transition: all 0.7s cubic-bezier(0.23, 1, 0.32, 1);
55 | }
56 | }
57 | }
58 |
59 | & span,
60 | & a {
61 | color: color-mod(var(--green-color) alpha(50%));
62 | }
63 |
64 | & * {
65 | display: flex;
66 | align-content: center;
67 | align-items: center;
68 | margin-right: 0.5em;
69 | }
70 | }
71 |
72 | & .socials {
73 | display: flex;
74 | align-items: center;
75 | align-content: center;
76 | justify-content: flex-end;
77 | margin-left: auto;
78 |
79 | & a {
80 | display: flex;
81 | align-items: center;
82 | align-content: center;
83 |
84 | &:not(:last-child) {
85 | margin-right: 2em;
86 | }
87 |
88 | &.become-a-patron {
89 | cursor: pointer;
90 | font-weight: 700;
91 | user-select: none;
92 | text-decoration: none;
93 | color: #8c99a7;
94 |
95 | & svg {
96 | width: 1.2em;
97 | height: 1.2em;
98 | margin-left: 0.5em;
99 | }
100 |
101 | & span {
102 | margin: 0 0.12em;
103 | transition: all 0.7s cubic-bezier(0.23, 1, 0.32, 1);
104 | }
105 |
106 | &:hover {
107 | & span:nth-child(1) {
108 | color: #052d49;
109 | }
110 |
111 | & span:nth-child(2) {
112 | color: #f96854;
113 | }
114 |
115 | & svg {
116 | & circle {
117 | fill: #f96854;
118 | }
119 |
120 | & rect {
121 | fill: #052d49;
122 | }
123 | }
124 | }
125 | }
126 | }
127 |
128 | & svg {
129 | height: 1.5em;
130 | width: 1.5em;
131 | cursor: pointer;
132 | fill: var(--grey-color);
133 |
134 | &:hover.github-svg path {
135 | fill: #333;
136 | }
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/public/styles/Main.postcss:
--------------------------------------------------------------------------------
1 | :root {
2 | --green-color: #31bc86;
3 | --grey-color: #8c99a7;
4 | --blue-color: #5396d8;
5 | }
6 |
7 | @font-face {
8 | font-family: 'Lato';
9 | font-style: normal;
10 | font-weight: 300;
11 | font-display: auto;
12 | src: local('Lato Light'), local('Lato-Light'), url('../fonts/Lato-Light.ttf') format('truetype');
13 | }
14 |
15 | @font-face {
16 | font-family: 'Lato';
17 | font-style: normal;
18 | font-weight: 400;
19 | font-display: auto;
20 | src: local('Lato Regular'), local('Lato-Regular'), url('../fonts/Lato-Regular.ttf') format('truetype');
21 | }
22 |
23 | @font-face {
24 | font-family: 'Lato';
25 | font-style: normal;
26 | font-weight: 700;
27 | font-display: auto;
28 | src: local('Lato Bold'), local('Lato-Bold'), url('../fonts/Lato-Bold.ttf') format('truetype');
29 | }
30 |
31 | body {
32 | padding: 0;
33 | margin: 0;
34 | font-family: 'Lato';
35 | text-rendering: geometricPrecision;
36 | -webkit-font-smoothing: antialiased;
37 | -moz-font-smoothing: grayscale;
38 | font-smoothing: antialiased;
39 | }
40 |
41 | .no-select {
42 | user-select: none;
43 | }
44 |
45 | input,
46 | textarea,
47 | button {
48 | font-family: 'Lato';
49 | text-rendering: geometricPrecision;
50 | -webkit-font-smoothing: antialiased;
51 | -moz-font-smoothing: grayscale;
52 | font-smoothing: antialiased;
53 | }
54 |
55 | *,
56 | :after,
57 | :before {
58 | -webkit-box-sizing: border-box;
59 | -moz-box-sizing: border-box;
60 | box-sizing: border-box;
61 | }
62 |
63 | .container {
64 | overflow: hidden;
65 | position: fixed;
66 | width: 100%;
67 | height: 100%;
68 | background-color: #fafafa;
69 | }
70 |
71 | .main-page-container {
72 | position: fixed;
73 | display: flex;
74 | flex-flow: column wrap;
75 | align-items: center;
76 | padding: 2em;
77 | width: 100%;
78 | height: 100%;
79 | top: 0;
80 | left: 0;
81 | overflow-y: auto;
82 | background-color: #fff;
83 | transition: all 0.7s cubic-bezier(0.23, 1, 0.32, 1);
84 |
85 | & .main-page-content {
86 | max-width: 960px;
87 | width: 100%;
88 |
89 | & button.check-button {
90 | margin: 2em;
91 | }
92 |
93 | & .blocks-row {
94 | display: flex;
95 | flex-flow: row wrap;
96 | align-items: center;
97 | justify-content: space-between;
98 | margin-bottom: 2em;
99 | padding-bottom: 2em;
100 | border-bottom: 1px dashed color-mod(var(--green-color) alpha(15%));
101 |
102 | &:last-child {
103 | padding-right: 0;
104 | }
105 |
106 | & .block {
107 | margin-bottom: 0;
108 | }
109 |
110 | & .block .content {
111 | border: none;
112 | }
113 | }
114 |
115 | & .block {
116 | display: inline-flex;
117 | flex-flow: row wrap;
118 | margin-bottom: 2em;
119 | padding-right: 2em;
120 |
121 | &.slider .content {
122 | padding-top: 1em;
123 | }
124 |
125 | &.wo-title .content {
126 | margin-top: -0.5em;
127 | }
128 |
129 | &:last-child {
130 | padding-right: 0;
131 | }
132 |
133 | & .sub-block {
134 | width: 100%;
135 |
136 | &:not(:first-child) .title {
137 | margin-top: 2em;
138 | }
139 | }
140 |
141 | & .input-area {
142 | width: 100%;
143 | margin-top: 1em;
144 | border-radius: 0.75em;
145 | padding: 0.75em 1em;
146 | background-color: #fff;
147 | resize: none;
148 | background-image: linear-gradient(to right, color-mod(var(--green-color) alpha(10%)), color-mod(var(--blue-color) alpha(10%)));
149 | box-shadow: 0 0.25em 1em color-mod(var(--green-color) alpha(5%));
150 | }
151 |
152 | & .content {
153 | display: flex;
154 | width: 100%;
155 | padding-bottom: 1em;
156 | border-bottom: 1px dashed color-mod(var(--green-color) alpha(15%));
157 |
158 | &.no-bot {
159 | padding-bottom: 0;
160 | border-bottom: none;
161 | }
162 |
163 | &.no-flex {
164 | display: block;
165 | }
166 |
167 | & button.ip-lookup-button,
168 | & button.add-url-button {
169 | margin: 0;
170 | margin-left: 2em;
171 | }
172 | }
173 |
174 | & h1:not(:first-child) {
175 | margin-top: 2em;
176 | }
177 |
178 | &.large {
179 | width: 100%;
180 | }
181 |
182 | &.middle {
183 | width: 50%;
184 | }
185 |
186 | &.small {
187 | width: 25%;
188 | }
189 |
190 | &.third {
191 | width: 33.3333333333%;
192 | }
193 |
194 | & .title {
195 | display: flex;
196 | justify-content: space-between;
197 | font-size: 0.85em;
198 | font-weight: 100;
199 | color: color-mod(var(--green-color) alpha(75%));
200 | width: 100%;
201 |
202 | &.space-bot {
203 | margin-bottom: 16px;
204 | }
205 |
206 | & span.name {
207 | background-color: #fafafa;
208 | padding: 0.25em 0.75em;
209 | border-radius: 0.75em;
210 | }
211 |
212 | & span.value {
213 | padding: 0.25em 0;
214 | color: color-mod(var(--grey-color) alpha(75%));
215 | }
216 | }
217 | }
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/public/styles/Process.postcss:
--------------------------------------------------------------------------------
1 | :root {
2 | --green-color: #31bc86;
3 | --grey-color: #8c99a7;
4 | --blue-color: #5396d8;
5 | }
6 |
7 | .process-container {
8 | position: fixed;
9 | display: flex;
10 | flex-flow: column nowrap;
11 | align-items: center;
12 | padding: 2em;
13 | width: 100%;
14 | height: 100%;
15 | top: 0;
16 | left: 0;
17 | overflow-y: auto;
18 | background-color: #fff;
19 | transition: all 0.7s cubic-bezier(0.23, 1, 0.32, 1);
20 | opacity: 0;
21 | visibility: hidden;
22 |
23 | &.active {
24 | opacity: 1;
25 | visibility: visible;
26 | }
27 |
28 | & .process-content {
29 | max-width: 960px;
30 | width: 100%;
31 |
32 | & .stage {
33 | margin-bottom: 6em;
34 | animation-name: fade-one;
35 | animation-duration: 0.7s;
36 | animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
37 |
38 | & span {
39 | font-weight: 700;
40 | }
41 |
42 | & h3 {
43 | display: inline-block;
44 | border-radius: 1.5em;
45 | margin: 0;
46 | margin-bottom: 2em;
47 | font-size: 1em;
48 | padding: 0.5em 1em;
49 | border: 1px dashed color-mod(var(--grey-color) alpha(35%));
50 | color: color-mod(var(--grey-color) alpha(75%));
51 | }
52 |
53 | & .crawl-level {
54 | margin-bottom: 2em;
55 | animation-name: fade-one;
56 | animation-duration: 0.7s;
57 | animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
58 |
59 | &:last-child {
60 | margin-bottom: 0;
61 | }
62 |
63 | & .stats {
64 | margin-bottom: 1em;
65 | display: flex;
66 | padding: 0 1em;
67 | align-items: center;
68 |
69 | & .level {
70 | color: color-mod(var(--green-color) alpha(75%));
71 | font-weight: 100;
72 | }
73 |
74 | & .found-links {
75 | margin-left: auto;
76 | color: color-mod(var(--blue-color) alpha(75%));
77 | border: 1px dashed color-mod(var(--blue-color) alpha(35%));
78 | font-size: 0.8em;
79 | padding: 0.25em 1em;
80 | border-radius: 1em;
81 | font-weight: 100;
82 | }
83 | }
84 | }
85 |
86 | & .progress-bar {
87 | position: relative;
88 | display: block;
89 | overflow: hidden;
90 | background-image: linear-gradient(to right, color-mod(var(--green-color) alpha(25%)), color-mod(var(--blue-color) alpha(25%)));
91 | padding: 0.75em;
92 | text-align: center;
93 | border-radius: 1.25em;
94 |
95 | & span {
96 | position: relative;
97 | color: #fff;
98 | }
99 |
100 | & .fill {
101 | top: 0;
102 | left: 0;
103 | width: 0%;
104 | height: 100%;
105 | position: absolute;
106 | border-radius: 1.25em;
107 | transition: all 0.7s cubic-bezier(0.23, 1, 0.32, 1);
108 | background-color: color-mod(var(--grey-color) alpha(15%));
109 | }
110 | }
111 | }
112 | }
113 | }
114 |
115 | @keyframes fade-one {
116 | from {
117 | opacity: 0;
118 | transform: translateY(10%);
119 | }
120 | to {
121 | opacity: 1;
122 | transform: translateY(0);
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/public/styles/Result.postcss:
--------------------------------------------------------------------------------
1 | :root {
2 | --green-color: #31bc86;
3 | --grey-color: #8c99a7;
4 | --blue-color: #5396d8;
5 | }
6 |
7 | .result-container {
8 | position: fixed;
9 | display: flex;
10 | flex-flow: column nowrap;
11 | align-items: center;
12 | padding: 2em;
13 | width: 100%;
14 | height: 100%;
15 | top: 0;
16 | left: 0;
17 | overflow-y: auto;
18 | background-color: #fff;
19 | transition: all 0.7s cubic-bezier(0.23, 1, 0.32, 1);
20 | opacity: 0;
21 | visibility: hidden;
22 |
23 | &.active {
24 | opacity: 1;
25 | visibility: visible;
26 |
27 | & .result-content {
28 | opacity: 1;
29 | transform: translateY(0%);
30 | }
31 | }
32 |
33 | & .result-content {
34 | max-width: 960px;
35 | width: 100%;
36 | opacity: 0;
37 | transform: translateY(-20%);
38 | transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1);
39 | transition-delay: transform 0.3s;
40 |
41 | & .content-header {
42 | display: flex;
43 | flex-flow: row wrap;
44 | margin-bottom: 2em;
45 |
46 | & .selected-counts {
47 | display: flex;
48 | align-items: center;
49 | color: #fff;
50 | font-weight: 700;
51 | user-select: none;
52 |
53 | & span {
54 | font-weight: 100;
55 | color: color-mod(var(--grey-color) alpha(75%));
56 | border-radius: 1.75em;
57 | margin-right: 1em;
58 | border: 1px solid color-mod(var(--grey-color) alpha(35%));
59 | padding: 0.25em 1em;
60 | font-size: 0.8em;
61 | }
62 | }
63 |
64 | & .controls {
65 | margin-left: auto;
66 | align-items: center;
67 | display: flex;
68 |
69 | & svg {
70 | width: 1.25em;
71 | height: 1.25em;
72 | transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
73 | fill: color-mod(var(--grey-color) alpha(35%));
74 | cursor: pointer;
75 | margin-left: 2em;
76 |
77 | &:hover {
78 | fill: color-mod(var(--blue-color) alpha(75%));
79 | }
80 | }
81 | }
82 | }
83 |
84 | & .countries {
85 | display: flex;
86 | flex-flow: row wrap;
87 | justify-content: space-between;
88 | text-align: left;
89 |
90 | & .country-item {
91 | display: flex;
92 | user-select: none;
93 | cursor: pointer;
94 | padding: 0.25em 0.5em;
95 | width: calc(25% - 0.5em);
96 | margin-bottom: 0.75em;
97 | overflow: hidden;
98 | align-items: center;
99 | border-radius: 2em;
100 | border: 1px dashed color-mod(var(--grey-color) alpha(15%));
101 | transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1);
102 | color: color-mod(var(--grey-color) alpha(50%));
103 |
104 | &.active {
105 | color: color-mod(var(--blue-color) alpha(75%));
106 | border-color: color-mod(var(--blue-color) alpha(50%));
107 | }
108 |
109 | & .ico {
110 | width: 40px;
111 | min-width: 40px;
112 | height: 26px;
113 | background-color: #fafafa;
114 | background-repeat: no-repeat;
115 | background-position: center;
116 | background-size: cover;
117 | border-radius: 1em;
118 | margin-right: 0.5em;
119 | }
120 |
121 | & .merge {
122 | width: calc(100% - 48px);
123 | }
124 |
125 | & .name {
126 | font-size: 0.9em;
127 | white-space: nowrap;
128 | overflow: hidden;
129 | text-overflow: ellipsis;
130 | }
131 |
132 | & .count {
133 | font-size: 0.75em;
134 | font-weight: 700;
135 | white-space: nowrap;
136 | overflow: hidden;
137 | text-overflow: ellipsis;
138 | }
139 | }
140 | }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/public/styles/Update.postcss:
--------------------------------------------------------------------------------
1 | :root {
2 | --green-color: #31bc86;
3 | --grey-color: #8c99a7;
4 | --blue-color: #5396d8;
5 | }
6 |
7 | .update-notify {
8 | position: fixed;
9 | width: 100%;
10 | height: 100%;
11 | display: flex;
12 | flex-flow: column nowrap;
13 | align-items: center;
14 | left: 0;
15 | top: 0;
16 | padding: 2em;
17 | overflow: auto;
18 | opacity: 1;
19 | transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1);
20 | background-image: linear-gradient(to right, color-mod(var(--green-color) alpha(25%)), color-mod(var(--blue-color) alpha(25%)));
21 | background-color: color-mod(#fff alpha(95%));
22 | z-index: 3;
23 |
24 | &.closed {
25 | opacity: 0;
26 | visibility: hidden;
27 | z-index: 0;
28 | }
29 |
30 | &.checking {
31 | width: 100%;
32 | height: 100%;
33 | left: 0;
34 | top: 0;
35 |
36 | & .lds-ripple {
37 | opacity: 1;
38 | }
39 | }
40 |
41 | &.downloading {
42 | width: 400px;
43 | height: 100px;
44 | left: calc(50% - 200px);
45 | top: calc(50% - 50px);
46 | overflow: hidden;
47 | border-radius: 0.75em;
48 |
49 | & .update-container {
50 | opacity: 0;
51 | }
52 | }
53 |
54 | & .lds-ripple {
55 | opacity: 0;
56 | position: fixed;
57 | width: 58px;
58 | height: 58px;
59 | left: calc(50% - 29px);
60 | top: calc(50% - 29px);
61 | border-radius: 100%;
62 |
63 | & div {
64 | position: absolute;
65 | border: 4px solid #fff;
66 | opacity: 1;
67 | border-radius: 50%;
68 | animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
69 |
70 | &:nth-child(2) {
71 | animation-delay: -0.5s;
72 | }
73 | }
74 | }
75 |
76 | & .update-container {
77 | max-width: 960px;
78 | width: 100%;
79 | border-radius: 0.75em;
80 | transform: translateY(0);
81 | opacity: 1;
82 | transition: all 0.7s cubic-bezier(0.23, 1, 0.32, 1);
83 | animation-name: fade-one;
84 | animation-duration: 0.7s;
85 | animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
86 | padding: 2em;
87 | border-radius: 1.25em;
88 | background-color: color-mod(#fff alpha(75%));
89 |
90 | & .section-name {
91 | display: inline-flex;
92 | font-weight: 100;
93 | font-size: 1em;
94 | color: color-mod(var(--green-color) alpha(50%));
95 | border: 1px dashed color-mod(var(--green-color) alpha(50%));
96 | padding: 0.25em 0.75em;
97 | border-radius: 1.25em;
98 | }
99 |
100 | & .update-description {
101 | color: color-mod(var(--green-color) alpha(50%));
102 | margin-bottom: 4em;
103 | }
104 | }
105 | }
106 |
107 | .download-progress {
108 | position: fixed;
109 | left: calc(50% - 200px + 2em);
110 | top: calc(50% - 5px);
111 | width: calc(400px - 4em);
112 | height: 10px;
113 | overflow: hidden;
114 | background-color: #ffffff;
115 | border-radius: 0.25em;
116 | }
117 |
118 | .download-progress-bar {
119 | position: absolute;
120 | background-color: rgba(49, 188, 135, 0.75);
121 | height: 100%;
122 | border-radius: 0.25em;
123 | transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1);
124 | }
125 |
126 | .update-download {
127 | margin: 1em 0 2em 0;
128 | display: flex;
129 | flex-flow: column nowrap;
130 |
131 | & a {
132 | color: color-mod(var(--green-color) alpha(50%));
133 | text-decoration: none;
134 | transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1);
135 | margin: 0.15em 0;
136 | white-space: nowrap;
137 | overflow: hidden;
138 | text-overflow: ellipsis;
139 |
140 | &:hover {
141 | color: var(--green-color);
142 | }
143 |
144 | & span.size {
145 | margin-right: 0.5em;
146 | color: #607d8b;
147 | font-size: 0.85em;
148 | text-decoration: none;
149 | }
150 | }
151 | }
152 |
153 | @keyframes lds-ripple {
154 | 0% {
155 | top: 28px;
156 | left: 28px;
157 | width: 0;
158 | height: 0;
159 | opacity: 1;
160 | }
161 | 100% {
162 | top: -1px;
163 | left: -1px;
164 | width: 58px;
165 | height: 58px;
166 | opacity: 0;
167 | }
168 | }
169 |
170 | @keyframes fade-one {
171 | from {
172 | opacity: 0;
173 | transform: translateY(10%);
174 | }
175 | to {
176 | opacity: 1;
177 | transform: translateY(0);
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/actions/MainActions.js:
--------------------------------------------------------------------------------
1 | import { MAIN_CHANGE_OPTION, MAIN_TOGGLE_OPTION } from '../constants/ActionTypes';
2 |
3 | export const changeOption = e => ({
4 | type: MAIN_CHANGE_OPTION,
5 | target: e.target.name,
6 | value: e.target.value
7 | });
8 |
9 | export const toggleOption = e => ({
10 | type: MAIN_TOGGLE_OPTION,
11 | target: e.target.name
12 | });
13 |
--------------------------------------------------------------------------------
/src/actions/ProcessActions.js:
--------------------------------------------------------------------------------
1 | import Links from '../core/links';
2 | import Parser from '../core/parser';
3 | import { uniq } from '../misc/other';
4 | import { saveSettings } from '../core/settings';
5 | import {
6 | PROCESS_START_CRAWLING,
7 | PROCESS_UPDATE_CRAWLING_LEVEL_STATUS,
8 | PROCESS_ADD_CRAWLING_LEVEL,
9 | PROCESS_START_PARSING,
10 | PROCESS_UPDATE_PARSING_STATUS
11 | } from '../constants/ActionTypes';
12 | import { showResults } from './ResultActions';
13 |
14 | const normalizeUrls = urls => {
15 | const links = urls.match(/((https?\:\/\/)|(www\.))(\S+)(\w{2,4})(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/gi);
16 |
17 | if (links == null) {
18 | throw new Error('No links found');
19 | }
20 |
21 | return links.map(url => (url.match(/http:\/\/|https:\/\//i) ? url : 'http://' + url));
22 | };
23 |
24 | export const start = () => async (dispatch, getState) => {
25 | try {
26 | const {
27 | main: { threads, retry, deep, external, level, input }
28 | } = getState();
29 | saveSettings({ main: { threads, retry, deep, external, level, input } });
30 |
31 | if (deep) {
32 | dispatch(startCrawling());
33 | }
34 |
35 | const normalizedLinks = uniq(normalizeUrls(input));
36 | const links = deep ? await new Links({ threads, retry, deep, external, level }, normalizedLinks) : normalizedLinks;
37 |
38 | dispatch(startParsing(links.length));
39 |
40 | const results = await new Parser({ threads, retry }, links);
41 |
42 | dispatch(showResults(results));
43 | } catch (error) {
44 | alert(error);
45 | }
46 | };
47 |
48 | export const startCrawling = () => ({
49 | type: PROCESS_START_CRAWLING
50 | });
51 |
52 | export const addCrawlLevel = (level, passed, all) => ({
53 | type: PROCESS_ADD_CRAWLING_LEVEL,
54 | level,
55 | passed,
56 | all
57 | });
58 |
59 | export const updateCrawlLevelStatus = (level, passed, found) => ({
60 | type: PROCESS_UPDATE_CRAWLING_LEVEL_STATUS,
61 | level,
62 | passed,
63 | found
64 | });
65 |
66 | export const startParsing = all => ({
67 | type: PROCESS_START_PARSING,
68 | all
69 | });
70 |
71 | export const updateParsingStatus = done => ({
72 | type: PROCESS_UPDATE_PARSING_STATUS,
73 | done
74 | });
75 |
--------------------------------------------------------------------------------
/src/actions/ResultActions.js:
--------------------------------------------------------------------------------
1 | import { lookup, codes } from '../core/country';
2 | import { uniq } from '../misc/other';
3 | import { sort } from 'js-flock';
4 | import { writeFile } from 'fs';
5 | import { remote } from 'electron';
6 | import { RESULT_SHOW, CLOSE, RESULT_TOGGLE_COUNTRY } from '../constants/ActionTypes';
7 |
8 | const { dialog } = remote;
9 |
10 | export const showResults = results => dispatch => {
11 | const res = [];
12 | let countries = {};
13 | const proxies = uniq(results.proxies.flat());
14 |
15 | proxies.forEach(item => {
16 | const [ip] = item.split(':');
17 | const code = lookup(ip);
18 |
19 | if (countries[code] == undefined) {
20 | countries[code] = {
21 | code,
22 | ...codes[code],
23 | items: [item]
24 | };
25 | } else {
26 | countries[code].items.push(item);
27 | }
28 | });
29 |
30 | Object.keys(countries).forEach(item => {
31 | res.push({
32 | ...countries[item],
33 | active: true
34 | });
35 | });
36 |
37 | dispatch({
38 | type: RESULT_SHOW,
39 | linksWithProxies: results.linksWithProxies,
40 | proxiesByCountries: sort(res).desc(item => item.items.length)
41 | });
42 | };
43 |
44 | export const toggleCountry = (code, state, all) => ({
45 | type: RESULT_TOGGLE_COUNTRY,
46 | code,
47 | state,
48 | all
49 | });
50 |
51 | export const save = () => (dispatch, getState) => {
52 | let savePath = dialog.showSaveDialog({
53 | filters: [
54 | {
55 | name: 'Text Files',
56 | extensions: ['txt']
57 | }
58 | ]
59 | });
60 |
61 | if (savePath) {
62 | const {
63 | result: { proxiesByCountries }
64 | } = getState();
65 |
66 | const results = proxiesByCountries
67 | .filter(item => item.active)
68 | .map(item => item.items)
69 | .reduce((prev, curr) => [...prev, ...curr], []);
70 |
71 | writeFile(savePath, results.join('\r\n'), () => null);
72 | }
73 | };
74 |
75 | export const close = () => ({
76 | type: CLOSE
77 | });
78 |
--------------------------------------------------------------------------------
/src/actions/UpdateActions.js:
--------------------------------------------------------------------------------
1 | import request from 'request';
2 | import progress from 'request-progress';
3 | import { remote, shell } from 'electron';
4 | import { getLatestVersionInfo } from '../core/updater';
5 | import { createWriteStream } from 'fs';
6 | import { wait } from '../misc/other';
7 | import { UPDATE_CHANGE_STATE, UPDATE_UP_DOWNLOAD_PROGRESS, UPDATE_CLOSE } from '../constants/ActionTypes';
8 |
9 | const { dialog, getCurrentWindow } = remote;
10 |
11 | export const checkAtAvailable = () => async dispatch => {
12 | const details = await getLatestVersionInfo();
13 |
14 | await wait(500);
15 |
16 | if (details) {
17 | dispatch(
18 | changeUpdateState({
19 | isAvailable: true,
20 | isChecking: false,
21 | info: details
22 | })
23 | );
24 | } else {
25 | dispatch(
26 | changeUpdateState({
27 | active: false,
28 | isChecking: false
29 | })
30 | );
31 | }
32 | };
33 |
34 | const changeUpdateState = nextState => ({
35 | type: UPDATE_CHANGE_STATE,
36 | nextState
37 | });
38 |
39 | export const close = () => ({
40 | type: UPDATE_CLOSE
41 | });
42 |
43 | const upDownloadProgress = percent => ({
44 | type: UPDATE_UP_DOWNLOAD_PROGRESS,
45 | percent
46 | });
47 |
48 | export const download = e => async dispatch => {
49 | e.preventDefault();
50 |
51 | let savePath = dialog.showSaveDialog({
52 | defaultPath: e.target.title,
53 | filters: [
54 | {
55 | name: '.rar, .exe, .zip, .7z',
56 | extensions: ['rar', 'exe', 'zip', '7z']
57 | }
58 | ]
59 | });
60 |
61 | if (!savePath) return;
62 |
63 | dispatch(changeUpdateState({ onDownloading: true }));
64 |
65 | progress(request(e.target.href), {
66 | throttle: 100
67 | })
68 | .on('progress', state => {
69 | dispatch(upDownloadProgress(state.percent * 100));
70 | })
71 | .on('end', () => {
72 | shell.showItemInFolder(savePath);
73 | getCurrentWindow().close();
74 | })
75 | .pipe(createWriteStream(savePath));
76 | };
77 |
--------------------------------------------------------------------------------
/src/components/Footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shell } from 'electron';
3 | import { currentVersion } from '../core/updater';
4 |
5 | import '../../public/styles/Footer.postcss';
6 |
7 | const openLink = e => {
8 | e.preventDefault();
9 | shell.openExternal(e.currentTarget.href || e.currentTarget.getAttribute('xlink:href'));
10 | };
11 |
12 | const Footer = () => (
13 |
43 | );
44 |
45 | export default Footer;
46 |
--------------------------------------------------------------------------------
/src/components/ProcessCrawl.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ProcessCrawlLevel from './ProcessCrawlLevel';
3 |
4 | const ProcessCrawl = ({ crawling }) => (
5 |
6 | Crawling
7 | {crawling.map(item => (
8 |
9 | ))}
10 |
11 | );
12 |
13 | export default ProcessCrawl;
14 |
--------------------------------------------------------------------------------
/src/components/ProcessCrawlLevel.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { splitByKK } from '../misc/text';
3 |
4 | const ProcessCrawlLevel = ({ level, links }) => {
5 | const progressStyle = {
6 | width: Math.floor((links.passed / links.all) * 100) + '%'
7 | };
8 |
9 | return (
10 |
11 |
12 | Deep level {level + 1}
13 | Found New Links: {splitByKK(links.found)}
14 |
15 |
16 |
17 |
18 | Crawled links {splitByKK(links.passed)} of {splitByKK(links.all)}
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export default ProcessCrawlLevel;
26 |
--------------------------------------------------------------------------------
/src/components/ProcessParse.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { splitByKK } from '../misc/text';
3 |
4 | const ProcessParse = ({ parsing }) => {
5 | const progressStyle = {
6 | width: Math.floor((parsing.done / parsing.all) * 100) + '%'
7 | };
8 |
9 | return (
10 |
11 | Parsing
12 |
13 |
14 |
15 | Parsed links {splitByKK(parsing.done)} of {splitByKK(parsing.all)}
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default ProcessParse;
23 |
--------------------------------------------------------------------------------
/src/components/ResultCountryItem.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { splitByKK } from '../misc/text';
3 |
4 | export default class ResultCountryItem extends React.PureComponent {
5 | toggle = () => {
6 | const { toggleCountry, code, active } = this.props;
7 | toggleCountry(code, !active);
8 | };
9 |
10 | toggleAll = () => {
11 | const { toggleCountry, code, active } = this.props;
12 | toggleCountry(code, !active, true);
13 | };
14 |
15 | render = () => {
16 | const { active, flag, name, items } = this.props;
17 |
18 | return (
19 |
20 |
21 |
22 |
{name}
23 |
Proxies: {splitByKK(items.length)}
24 |
25 |
26 | );
27 | };
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/Root.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Main from '../containers/Main';
3 | import Process from '../containers/Process';
4 | import Result from '../containers/Result';
5 | import Update from '../containers/Update';
6 |
7 | const Root = () => (
8 |
14 | );
15 |
16 | export default Root;
17 |
--------------------------------------------------------------------------------
/src/components/ui/Checkbox.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Checkbox = ({ id, name, checked, onChange, text }) => (
4 | <>
5 |
6 |
14 | >
15 | );
16 |
17 | export default Checkbox;
18 |
--------------------------------------------------------------------------------
/src/constants/ActionTypes.js:
--------------------------------------------------------------------------------
1 | //main
2 | export const MAIN_CHANGE_OPTION = 'MAIN_CHANGE_OPTION';
3 | export const MAIN_TOGGLE_OPTION = 'MAIN_TOGGLE_OPTION';
4 |
5 | //process
6 | export const PROCESS_START_CRAWLING = 'PROCESS_START_CRAWLING';
7 | export const PROCESS_ADD_CRAWLING_LEVEL = 'PROCESS_ADD_CRAWLING_LEVEL';
8 | export const PROCESS_UPDATE_CRAWLING_LEVEL_STATUS = 'PROCESS_UPDATE_CRAWLING_LEVEL_STATUS';
9 | export const PROCESS_START_PARSING = 'PROCESS_START_PARSING';
10 | export const PROCESS_UPDATE_PARSING_STATUS = 'PROCESS_UPDATE_PARSING_STATUS';
11 |
12 | //results
13 | export const RESULT_SHOW = 'RESULT_SHOW';
14 | export const RESULT_TOGGLE_COUNTRY = 'RESULT_TOGGLE_COUNTRY';
15 |
16 | //update
17 | export const UPDATE_CHANGE_STATE = 'UPDATE_CHANGE_STATE';
18 | export const UPDATE_UP_DOWNLOAD_PROGRESS = 'UPDATE_UP_DOWNLOAD_PROGRESS';
19 | export const UPDATE_CLOSE = 'UPDATE_CLOSE';
20 |
21 | // process + results
22 | export const CLOSE = 'CLOSE';
23 |
--------------------------------------------------------------------------------
/src/constants/SettingsConstants.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 |
3 | export const SETTINGS_FILE_NAME = 'settings.unfx.parser.json';
4 | export const SETTINGS_FILE_PATH = process.env.PORTABLE_EXECUTABLE_DIR ? path.resolve(process.env.PORTABLE_EXECUTABLE_DIR, SETTINGS_FILE_NAME) : SETTINGS_FILE_NAME;
5 |
6 | export const DEFAULT_MAIN_SETTINGS = {
7 | threads: 350,
8 | external: false,
9 | retry: false,
10 | deep: true,
11 | level: 2,
12 | input: ''
13 | };
14 |
15 | export const MERGED_DEFAULT_SETTINGS = {
16 | main: DEFAULT_MAIN_SETTINGS
17 | };
18 |
--------------------------------------------------------------------------------
/src/containers/Main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { changeOption, toggleOption } from '../actions/MainActions';
4 | import { start } from '../actions/ProcessActions';
5 | import Footer from '../components/Footer';
6 | import Checkbox from '../components/ui/Checkbox';
7 |
8 | import '../../public/styles/Main.postcss';
9 | import '../../public/styles/Elements.postcss';
10 |
11 | const Main = ({ threads, retry, deep, external, level, input, changeOption, toggleOption, start }) => (
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Threads
27 | {threads}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | Deep level
36 | {level}
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | Urls
47 |
48 |
49 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | );
74 |
75 | const mapStateToProps = state => ({
76 | ...state.main
77 | });
78 |
79 | const mapDispatchToProps = {
80 | changeOption,
81 | toggleOption,
82 | start
83 | };
84 |
85 | export default connect(
86 | mapStateToProps,
87 | mapDispatchToProps
88 | )(Main);
89 |
--------------------------------------------------------------------------------
/src/containers/Process.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import ProcessCrawl from '../components/ProcessCrawl';
4 | import ProcessParse from '../components/ProcessParse';
5 |
6 | import '../../public/styles/Process.postcss';
7 |
8 | const Process = ({ active, deep, stage, crawling, parsing }) => (
9 |
10 |
11 | {deep && (
12 |
13 | )}
14 | {stage > 1 && (
15 |
16 | )}
17 |
18 |
19 | );
20 |
21 | export default connect(state => ({ ...state.process, deep: state.main.deep }))(Process);
22 |
--------------------------------------------------------------------------------
/src/containers/Result.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { splitByKK } from '../misc/text';
4 | import { save, close, toggleCountry } from '../actions/ResultActions';
5 | import ResultCountryItem from '../components/ResultCountryItem';
6 |
7 | import '../../public/styles/Result.postcss';
8 | import '../../public/styles/Icons.postcss';
9 |
10 | const Result = ({ active, proxiesByCountries, save, close, toggleCountry }) => {
11 | const activeCountries = proxiesByCountries.filter(item => item.active);
12 | const selectedProxiesCount = activeCountries.reduce((prev, curr) => prev + curr.items.length, 0);
13 |
14 | return (
15 |
16 |
17 |
18 |
19 |
20 | Selected: {activeCountries.length} of {proxiesByCountries.length}
21 |
22 | Proxies: {splitByKK(selectedProxiesCount)}
23 |
24 |
25 |
32 |
36 |
37 |
38 |
39 | {proxiesByCountries.map(country => (
40 |
41 | ))}
42 |
43 |
44 |
45 | );
46 | };
47 |
48 | const mapStateToProps = state => ({
49 | ...state.result
50 | });
51 |
52 | const mapDispatchToProps = {
53 | save,
54 | close,
55 | toggleCountry
56 | };
57 |
58 | export default connect(
59 | mapStateToProps,
60 | mapDispatchToProps
61 | )(Result);
62 |
--------------------------------------------------------------------------------
/src/containers/Update.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactMarkdown from 'react-markdown';
3 | import { connect } from 'react-redux';
4 | import { checkAtAvailable, close, download } from '../actions/UpdateActions';
5 | import { bytesToSize } from '../misc/text';
6 |
7 | import '../../public/styles/Update.postcss';
8 |
9 | class Update extends React.PureComponent {
10 | componentWillMount = () => {
11 | const { checkAtAvailable } = this.props;
12 | checkAtAvailable();
13 | };
14 |
15 | render = () => {
16 | const { active, isAvailable, isChecking, onDownloading, downloadProgress, info, close, download } = this.props;
17 | const progress = { width: downloadProgress + '%' };
18 |
19 | return (
20 |
21 |
25 | {isAvailable && (
26 |
27 |
28 |
Available version: {info.version}
29 |
30 |
31 |
32 |
Downloads
33 |
34 |
42 |
43 |
44 | )}
45 | {onDownloading && (
46 |
49 | )}
50 |
51 | );
52 | };
53 | }
54 |
55 | const mapStateToProps = state => ({
56 | ...state.update
57 | });
58 |
59 | const mapDispatchToProps = {
60 | checkAtAvailable,
61 | close,
62 | download
63 | };
64 |
65 | export default connect(
66 | mapStateToProps,
67 | mapDispatchToProps
68 | )(Update);
69 |
--------------------------------------------------------------------------------
/src/core/country.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import MMDBReader from 'mmdb-reader';
3 |
4 | const pathToMmdb = process.env.NODE_ENV !== 'production' ? './files/GeoLite2-Country.mmdb' : './resources/files/GeoLite2-Country.mmdb';
5 |
6 | const reader = new MMDBReader(path.resolve(pathToMmdb));
7 |
8 | export const codes = {
9 | AF: { flag: 'afghanistan', name: 'Afghanistan' },
10 | AL: { flag: 'albania', name: 'Albania' },
11 | DZ: { flag: 'algeria', name: 'Algeria' },
12 | DS: { flag: 'american-samoa', name: 'American Samoa' },
13 | AD: { flag: 'andorra', name: 'Andorra' },
14 | AO: { flag: 'angola', name: 'Angola' },
15 | AI: { flag: 'anguilla', name: 'Anguilla' },
16 | AQ: { flag: 'antarctica', name: 'Antarctica' },
17 | AG: { flag: 'antigua-and-barbuda', name: 'Antigua and Barbuda' },
18 | AR: { flag: 'argentina', name: 'Argentina' },
19 | AM: { flag: 'armenia', name: 'Armenia' },
20 | AW: { flag: 'aruba', name: 'Aruba' },
21 | AU: { flag: 'australia', name: 'Australia' },
22 | AT: { flag: 'austria', name: 'Austria' },
23 | AZ: { flag: 'azerbaijan', name: 'Azerbaijan' },
24 | BS: { flag: 'bahamas', name: 'Bahamas' },
25 | BH: { flag: 'bahrain', name: 'Bahrain' },
26 | BD: { flag: 'bangladesh', name: 'Bangladesh' },
27 | BB: { flag: 'barbados', name: 'Barbados' },
28 | BY: { flag: 'belarus', name: 'Belarus' },
29 | BE: { flag: 'belgium', name: 'Belgium' },
30 | BZ: { flag: 'belize', name: 'Belize' },
31 | BJ: { flag: 'benin', name: 'Benin' },
32 | BM: { flag: 'bermuda', name: 'Bermuda' },
33 | BT: { flag: 'bhutan', name: 'Bhutan' },
34 | BO: { flag: 'bolivia', name: 'Bolivia' },
35 | BA: { flag: 'bosnia-and-herzegovina', name: 'Bosnia and Herzegovina' },
36 | BW: { flag: 'botswana', name: 'Botswana' },
37 | BV: { flag: 'bouvet-island', name: 'Bouvet Island' },
38 | BR: { flag: 'brazil', name: 'Brazil' },
39 | IO: { flag: 'british-indian-ocean-territory', name: 'British Indian Ocean Territory' },
40 | BN: { flag: 'brunei-darussalam', name: 'Brunei Darussalam' },
41 | BG: { flag: 'bulgaria', name: 'Bulgaria' },
42 | BF: { flag: 'burkina-faso', name: 'Burkina Faso' },
43 | BI: { flag: 'burundi', name: 'Burundi' },
44 | KH: { flag: 'cambodia', name: 'Cambodia' },
45 | CM: { flag: 'cameroon', name: 'Cameroon' },
46 | CA: { flag: 'canada', name: 'Canada' },
47 | CV: { flag: 'cape-verde', name: 'Cape Verde' },
48 | KY: { flag: 'cayman-islands', name: 'Cayman Islands' },
49 | CF: { flag: 'central-african-republic', name: 'Central African Republic' },
50 | TD: { flag: 'chad', name: 'Chad' },
51 | CL: { flag: 'chile', name: 'Chile' },
52 | CN: { flag: 'china', name: 'China' },
53 | CX: { flag: 'christmas-island', name: 'Christmas Island' },
54 | CC: { flag: 'cocos-keeling-islands', name: 'Cocos (Keeling) Islands' },
55 | CO: { flag: 'colombia', name: 'Colombia' },
56 | KM: { flag: 'comoros', name: 'Comoros' },
57 | CG: { flag: 'congo', name: 'Congo' },
58 | CK: { flag: 'cook-islands', name: 'Cook Islands' },
59 | CR: { flag: 'costa-rica', name: 'Costa Rica' },
60 | HR: { flag: 'croatia-hrvatska', name: 'Croatia (Hrvatska)' },
61 | CU: { flag: 'cuba', name: 'Cuba' },
62 | CY: { flag: 'cyprus', name: 'Cyprus' },
63 | CZ: { flag: 'czech-republic', name: 'Czech Republic' },
64 | DK: { flag: 'denmark', name: 'Denmark' },
65 | DJ: { flag: 'djibouti', name: 'Djibouti' },
66 | DM: { flag: 'dominica', name: 'Dominica' },
67 | DO: { flag: 'dominican-republic', name: 'Dominican Republic' },
68 | TP: { flag: 'east-timor', name: 'East Timor' },
69 | EC: { flag: 'ecuador', name: 'Ecuador' },
70 | EG: { flag: 'egypt', name: 'Egypt' },
71 | SV: { flag: 'el-salvador', name: 'El Salvador' },
72 | GQ: { flag: 'equatorial-guinea', name: 'Equatorial Guinea' },
73 | ER: { flag: 'eritrea', name: 'Eritrea' },
74 | EE: { flag: 'estonia', name: 'Estonia' },
75 | ET: { flag: 'ethiopia', name: 'Ethiopia' },
76 | FK: { flag: 'falkland-islands-malvinas', name: 'Falkland Islands (Malvinas)' },
77 | FO: { flag: 'faroe-islands', name: 'Faroe Islands' },
78 | FJ: { flag: 'fiji', name: 'Fiji' },
79 | FI: { flag: 'finland', name: 'Finland' },
80 | FR: { flag: 'france', name: 'France' },
81 | FX: { flag: 'france-metropolitan', name: 'France, Metropolitan' },
82 | GF: { flag: 'french-guiana', name: 'French Guiana' },
83 | PF: { flag: 'french-polynesia', name: 'French Polynesia' },
84 | TF: { flag: 'french-southern-territories', name: 'French Southern Territories' },
85 | GA: { flag: 'gabon', name: 'Gabon' },
86 | GM: { flag: 'gambia', name: 'Gambia' },
87 | GE: { flag: 'georgia', name: 'Georgia' },
88 | DE: { flag: 'germany', name: 'Germany' },
89 | GH: { flag: 'ghana', name: 'Ghana' },
90 | GI: { flag: 'gibraltar', name: 'Gibraltar' },
91 | GK: { flag: 'guernsey', name: 'Guernsey' },
92 | GR: { flag: 'greece', name: 'Greece' },
93 | GL: { flag: 'greenland', name: 'Greenland' },
94 | GD: { flag: 'grenada', name: 'Grenada' },
95 | GP: { flag: 'guadeloupe', name: 'Guadeloupe' },
96 | GU: { flag: 'guam', name: 'Guam' },
97 | GT: { flag: 'guatemala', name: 'Guatemala' },
98 | GN: { flag: 'guinea', name: 'Guinea' },
99 | GW: { flag: 'guinea-bissau', name: 'Guinea-Bissau' },
100 | GY: { flag: 'guyana', name: 'Guyana' },
101 | HT: { flag: 'haiti', name: 'Haiti' },
102 | HM: { flag: 'heard-and-mc-donald-islands', name: 'Heard and Mc Donald Islands' },
103 | HN: { flag: 'honduras', name: 'Honduras' },
104 | HK: { flag: 'hong-kong', name: 'Hong Kong' },
105 | HU: { flag: 'hungary', name: 'Hungary' },
106 | IS: { flag: 'iceland', name: 'Iceland' },
107 | IN: { flag: 'india', name: 'India' },
108 | IM: { flag: 'isle-of-man', name: 'Isle of Man' },
109 | ID: { flag: 'indonesia', name: 'Indonesia' },
110 | IR: { flag: 'iran', name: 'Iran' },
111 | IQ: { flag: 'iraq', name: 'Iraq' },
112 | IE: { flag: 'ireland', name: 'Ireland' },
113 | IL: { flag: 'israel', name: 'Israel' },
114 | IT: { flag: 'italy', name: 'Italy' },
115 | CI: { flag: 'ivory-coast', name: 'Ivory Coast' },
116 | JE: { flag: 'jersey', name: 'Jersey' },
117 | JM: { flag: 'jamaica', name: 'Jamaica' },
118 | JP: { flag: 'japan', name: 'Japan' },
119 | JO: { flag: 'jordan', name: 'Jordan' },
120 | KZ: { flag: 'kazakhstan', name: 'Kazakhstan' },
121 | KE: { flag: 'kenya', name: 'Kenya' },
122 | KI: { flag: 'kiribati', name: 'Kiribati' },
123 | KP: { flag: 'korea-democratic-peoples-republic-of', name: "Korea, Democratic People's Republic of" },
124 | KR: { flag: 'korea', name: 'Korea' },
125 | XK: { flag: 'kosovo', name: 'Kosovo' },
126 | KW: { flag: 'kuwait', name: 'Kuwait' },
127 | KG: { flag: 'kyrgyzstan', name: 'Kyrgyzstan' },
128 | LA: { flag: 'laos', name: 'Laos' },
129 | LV: { flag: 'latvia', name: 'Latvia' },
130 | LB: { flag: 'lebanon', name: 'Lebanon' },
131 | LS: { flag: 'lesotho', name: 'Lesotho' },
132 | LR: { flag: 'liberia', name: 'Liberia' },
133 | LY: { flag: 'libyan-arab-jamahiriya', name: 'Libyan Arab Jamahiriya' },
134 | LI: { flag: 'liechtenstein', name: 'Liechtenstein' },
135 | LT: { flag: 'lithuania', name: 'Lithuania' },
136 | LU: { flag: 'luxembourg', name: 'Luxembourg' },
137 | MO: { flag: 'macau', name: 'Macau' },
138 | MK: { flag: 'macedonia', name: 'Macedonia' },
139 | MG: { flag: 'madagascar', name: 'Madagascar' },
140 | MW: { flag: 'malawi', name: 'Malawi' },
141 | MY: { flag: 'malaysia', name: 'Malaysia' },
142 | MV: { flag: 'maldives', name: 'Maldives' },
143 | ML: { flag: 'mali', name: 'Mali' },
144 | MT: { flag: 'malta', name: 'Malta' },
145 | MH: { flag: 'marshall-islands', name: 'Marshall Islands' },
146 | MQ: { flag: 'martinique', name: 'Martinique' },
147 | MR: { flag: 'mauritania', name: 'Mauritania' },
148 | MU: { flag: 'mauritius', name: 'Mauritius' },
149 | TY: { flag: 'mayotte', name: 'Mayotte' },
150 | MX: { flag: 'mexico', name: 'Mexico' },
151 | FM: { flag: 'micronesia-federated-states-of', name: 'Micronesia, Federated States of' },
152 | MD: { flag: 'moldova', name: 'Moldova' },
153 | MC: { flag: 'monaco', name: 'Monaco' },
154 | MN: { flag: 'mongolia', name: 'Mongolia' },
155 | ME: { flag: 'montenegro', name: 'Montenegro' },
156 | MS: { flag: 'montserrat', name: 'Montserrat' },
157 | MA: { flag: 'morocco', name: 'Morocco' },
158 | MZ: { flag: 'mozambique', name: 'Mozambique' },
159 | MM: { flag: 'myanmar', name: 'Myanmar' },
160 | NA: { flag: 'namibia', name: 'Namibia' },
161 | NR: { flag: 'nauru', name: 'Nauru' },
162 | NP: { flag: 'nepal', name: 'Nepal' },
163 | NL: { flag: 'netherlands', name: 'Netherlands' },
164 | AN: { flag: 'netherlands-antilles', name: 'Netherlands Antilles' },
165 | NC: { flag: 'new-caledonia', name: 'New Caledonia' },
166 | NZ: { flag: 'new-zealand', name: 'New Zealand' },
167 | NI: { flag: 'nicaragua', name: 'Nicaragua' },
168 | NE: { flag: 'niger', name: 'Niger' },
169 | NG: { flag: 'nigeria', name: 'Nigeria' },
170 | NU: { flag: 'niue', name: 'Niue' },
171 | NF: { flag: 'norfolk-island', name: 'Norfolk Island' },
172 | MP: { flag: 'northern-mariana-islands', name: 'Northern Mariana Islands' },
173 | NO: { flag: 'norway', name: 'Norway' },
174 | OM: { flag: 'oman', name: 'Oman' },
175 | PK: { flag: 'pakistan', name: 'Pakistan' },
176 | PW: { flag: 'palau', name: 'Palau' },
177 | PS: { flag: 'palestine', name: 'Palestine' },
178 | PA: { flag: 'panama', name: 'Panama' },
179 | PG: { flag: 'papua-new-guinea', name: 'Papua New Guinea' },
180 | PY: { flag: 'paraguay', name: 'Paraguay' },
181 | PE: { flag: 'peru', name: 'Peru' },
182 | PH: { flag: 'philippines', name: 'Philippines' },
183 | PN: { flag: 'pitcairn', name: 'Pitcairn' },
184 | PL: { flag: 'poland', name: 'Poland' },
185 | PT: { flag: 'portugal', name: 'Portugal' },
186 | PR: { flag: 'puerto-rico', name: 'Puerto Rico' },
187 | QA: { flag: 'qatar', name: 'Qatar' },
188 | RE: { flag: 'reunion', name: 'Reunion' },
189 | RO: { flag: 'romania', name: 'Romania' },
190 | RU: { flag: 'russian-federation', name: 'Russian Federation' },
191 | RW: { flag: 'rwanda', name: 'Rwanda' },
192 | KN: { flag: 'saint-kitts-and-nevis', name: 'Saint Kitts and Nevis' },
193 | LC: { flag: 'saint-lucia', name: 'Saint Lucia' },
194 | VC: { flag: 'saint-vincent-and-the-grenadines', name: 'Saint Vincent and the Grenadines' },
195 | WS: { flag: 'samoa', name: 'Samoa' },
196 | SM: { flag: 'san-marino', name: 'San Marino' },
197 | ST: { flag: 'sao-tome-and-principe', name: 'Sao Tome and Principe' },
198 | SA: { flag: 'saudi-arabia', name: 'Saudi Arabia' },
199 | SN: { flag: 'senegal', name: 'Senegal' },
200 | RS: { flag: 'serbia', name: 'Serbia' },
201 | SC: { flag: 'seychelles', name: 'Seychelles' },
202 | SL: { flag: 'sierra-leone', name: 'Sierra Leone' },
203 | SG: { flag: 'singapore', name: 'Singapore' },
204 | SK: { flag: 'slovakia', name: 'Slovakia' },
205 | SI: { flag: 'slovenia', name: 'Slovenia' },
206 | SB: { flag: 'solomon-islands', name: 'Solomon Islands' },
207 | SO: { flag: 'somalia', name: 'Somalia' },
208 | ZA: { flag: 'south-africa', name: 'South Africa' },
209 | GS: { flag: 'south-georgia-south-sandwich-islands', name: 'South Georgia South Sandwich Islands' },
210 | ES: { flag: 'spain', name: 'Spain' },
211 | LK: { flag: 'sri-lanka', name: 'Sri Lanka' },
212 | SH: { flag: 'st-helena', name: 'St. Helena' },
213 | PM: { flag: 'st-pierre-and-miquelon', name: 'St. Pierre and Miquelon' },
214 | SD: { flag: 'sudan', name: 'Sudan' },
215 | SR: { flag: 'suriname', name: 'Suriname' },
216 | SJ: { flag: 'svalbard-and-jan-mayen-islands', name: 'Svalbard and Jan Mayen Islands' },
217 | SZ: { flag: 'swaziland', name: 'Swaziland' },
218 | SE: { flag: 'sweden', name: 'Sweden' },
219 | CH: { flag: 'switzerland', name: 'Switzerland' },
220 | SY: { flag: 'syria', name: 'Syria' },
221 | TW: { flag: 'taiwan', name: 'Taiwan' },
222 | TJ: { flag: 'tajikistan', name: 'Tajikistan' },
223 | TZ: { flag: 'tanzania', name: 'Tanzania' },
224 | TH: { flag: 'thailand', name: 'Thailand' },
225 | TG: { flag: 'togo', name: 'Togo' },
226 | TK: { flag: 'tokelau', name: 'Tokelau' },
227 | TO: { flag: 'tonga', name: 'Tonga' },
228 | TT: { flag: 'trinidad-and-tobago', name: 'Trinidad and Tobago' },
229 | TN: { flag: 'tunisia', name: 'Tunisia' },
230 | TR: { flag: 'turkey', name: 'Turkey' },
231 | TM: { flag: 'turkmenistan', name: 'Turkmenistan' },
232 | TC: { flag: 'turks-and-caicos-islands', name: 'Turks and Caicos Islands' },
233 | TV: { flag: 'tuvalu', name: 'Tuvalu' },
234 | UG: { flag: 'uganda', name: 'Uganda' },
235 | UA: { flag: 'ukraine', name: 'Ukraine' },
236 | AE: { flag: 'united-arab-emirates', name: 'United Arab Emirates' },
237 | GB: { flag: 'united-kingdom', name: 'United Kingdom' },
238 | US: { flag: 'united-states', name: 'United States' },
239 | UM: { flag: 'united-states-minor-outlying-islands', name: 'United States minor outlying islands' },
240 | UY: { flag: 'uruguay', name: 'Uruguay' },
241 | UZ: { flag: 'uzbekistan', name: 'Uzbekistan' },
242 | VU: { flag: 'vanuatu', name: 'Vanuatu' },
243 | VA: { flag: 'vatican-city-state', name: 'Vatican City State' },
244 | VE: { flag: 'venezuela', name: 'Venezuela' },
245 | VN: { flag: 'vietnam', name: 'Vietnam' },
246 | VG: { flag: 'virgin-islands-british', name: 'Virgin Islands (British)' },
247 | VI: { flag: 'virgin-islands-us', name: 'Virgin Islands (U.S.)' },
248 | WF: { flag: 'wallis-and-futuna-islands', name: 'Wallis and Futuna Islands' },
249 | EH: { flag: 'western-sahara', name: 'Western Sahara' },
250 | YE: { flag: 'yemen', name: 'Yemen' },
251 | ZR: { flag: 'zaire', name: 'Zaire' },
252 | ZM: { flag: 'zambia', name: 'Zambia' },
253 | ZW: { flag: 'zimbabwe', name: 'Zimbabwe' },
254 | AS: { flag: 'american-samoa', name: 'American Samoa' },
255 | AX: { flag: 'aland-islands', name: 'Aland Islands' },
256 | BQ: { flag: 'bonaire-sint-eustatius-saba', name: 'Bonaire, Sint Eustatius, Saba' },
257 | CD: { flag: 'congo-the-democratic-republic-of-the', name: 'Congo The Democratic Republic of The' },
258 | CW: { flag: 'curacao', name: 'Curacao' },
259 | EU: { flag: 'european-union', name: 'European Union' },
260 | GG: { flag: 'guernsey', name: 'Guernsey' },
261 | MF: { flag: 'saint-martin', name: 'Saint Martin' },
262 | SS: { flag: 'south-sudan', name: 'South Sudan' },
263 | SX: { flag: 'sint-maarten', name: 'Sint Maarten' },
264 | TL: { flag: 'timor-leste', name: 'Timor-leste' },
265 | YT: { flag: 'mayotte', name: 'Mayotte' },
266 | ZZ: { flag: 'unknown', name: 'Unknown' }
267 | };
268 |
269 | export const lookup = ip => {
270 | try {
271 | return reader.lookup(ip).country.iso_code;
272 | } catch (error) {
273 | return 'ZZ';
274 | }
275 | };
276 |
--------------------------------------------------------------------------------
/src/core/links.js:
--------------------------------------------------------------------------------
1 | import rp from 'request-promise';
2 | import store from '../store';
3 | import { uniq } from '../misc/other';
4 | import { addCrawlLevel, updateCrawlLevelStatus } from '../actions/ProcessActions';
5 | import { isURL } from '../misc/regexes';
6 | import { EventEmitter } from 'events';
7 |
8 | EventEmitter.defaultMaxListeners = 0;
9 |
10 | export default class Links {
11 | constructor(config, links) {
12 | this.queue = links;
13 | this.config = config;
14 | this.nextLinks = new Set([]);
15 | this.passedLinks = {
16 | // 'site.com': new Set(['https://site.com/one', 'https://site.com/two']);
17 | };
18 |
19 | this.counter = {
20 | links: {
21 | all: links.length,
22 | passed: 0
23 | },
24 | level: {
25 | max: config.level,
26 | current: 0
27 | }
28 | };
29 |
30 | return new Promise((resolve, reject) => {
31 | this.resolve = resolve;
32 | this.reject = reject;
33 | this.launch();
34 | }).catch(error => alert(error));
35 | }
36 |
37 | async crawl(url, retry) {
38 | try {
39 | const html = await rp.get({ url, timeout: 5000 });
40 | this.onResponse(url, html);
41 | } catch (error) {
42 | this.onError(url, retry);
43 | }
44 | }
45 |
46 | grabLinks(html) {
47 | try {
48 | return html.match(new RegExp(/ item.match(/href=(?:(?:\'|")(.*?)(?:\'|"))/i)[1]);
49 | } catch (error) {
50 | return [];
51 | }
52 | }
53 |
54 | getUrlProps(url) {
55 | const match = url.match(/^(http|https)?(?:[\:\/]*)([a-z0-9\.-]*)(?:\:([0-9]+))?(\/[^?#]*)?(?:\?([^#]*))?(?:#(.*))?$/i);
56 |
57 | return {
58 | protocol: match[1],
59 | host: match[2],
60 | path: match[4]
61 | };
62 | }
63 |
64 | onResponse(url, html) {
65 | const links = this.grabLinks(html);
66 | const urlProps = this.getUrlProps(url);
67 |
68 | if (this.passedLinks[urlProps.host] == undefined) {
69 | this.passedLinks[urlProps.host] = new Set([url]);
70 | } else {
71 | this.passedLinks[urlProps.host].add(url);
72 | }
73 |
74 | let nextLinks = uniq(
75 | links.map(item => {
76 | if (isURL(item)) {
77 | return item;
78 | } else {
79 | if (item[0] == '/') {
80 | return urlProps.protocol + '://' + urlProps.host + item;
81 | } else {
82 | return urlProps.protocol + '://' + urlProps.host + '/' + item;
83 | }
84 | }
85 | })
86 | );
87 |
88 | if (!this.config.external) {
89 | nextLinks = nextLinks.filter(item => this.getUrlProps(item).host == urlProps.host);
90 | }
91 |
92 | nextLinks = nextLinks.filter(item => {
93 | const { host } = this.getUrlProps(item);
94 | return this.passedLinks[host] == undefined || !this.passedLinks[host].has(item);
95 | });
96 |
97 | this.nextLinks = new Set([...this.nextLinks, ...nextLinks]);
98 | this.isDone();
99 | }
100 |
101 | onError(url, retry) {
102 | if (!retry && this.config.retry) {
103 | return this.crawl(url, true);
104 | }
105 |
106 | this.isDone();
107 | }
108 |
109 | isDone() {
110 | this.counter.links.passed++;
111 | store.dispatch(updateCrawlLevelStatus(this.counter.level.current, this.counter.links.passed, this.nextLinks.size));
112 |
113 | if (this.counter.links.passed == this.counter.links.all) {
114 | this.counter.level.current++;
115 |
116 | if (this.counter.level.current == this.counter.level.max) {
117 | this.resolveResults();
118 | } else {
119 | this.queue = [...this.nextLinks];
120 |
121 | if (this.queue.length > 0) {
122 | this.counter.links = {
123 | all: this.queue.length,
124 | passed: 0
125 | };
126 |
127 | this.nextLinks = new Set([]);
128 | this.launch();
129 | } else {
130 | this.resolveResults();
131 | }
132 | }
133 | } else {
134 | this.run();
135 | }
136 | }
137 |
138 | run() {
139 | if (!this.queue.length) {
140 | return;
141 | }
142 |
143 | const url = this.queue.pop();
144 | this.crawl(url);
145 | }
146 |
147 | resolveResults() {
148 | const passedLinks = Object.keys(this.passedLinks).reduce((prev, curr) => [...prev, ...this.passedLinks[curr]], []);
149 | this.resolve(uniq([...passedLinks, ...this.nextLinks]));
150 | }
151 |
152 | launch() {
153 | store.dispatch(addCrawlLevel(this.counter.level.current, this.counter.links.passed, this.counter.links.all));
154 | const startThreadsCount = this.queue.length > this.config.threads ? this.config.threads : this.queue.length;
155 |
156 | setTimeout(() => {
157 | for (let index = 0; index < startThreadsCount; index++) {
158 | this.run();
159 | }
160 | }, 300);
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/core/parser.js:
--------------------------------------------------------------------------------
1 | import rp from 'request-promise';
2 | import store from '../store';
3 | import { updateParsingStatus } from '../actions/ProcessActions';
4 | import { ParseMethods } from '../misc/methods';
5 |
6 | export default class Parser {
7 | constructor(config, links) {
8 | this.queue = links;
9 | this.config = config;
10 |
11 | this.results = {
12 | proxies: []
13 | };
14 |
15 | this.counter = {
16 | done: 0,
17 | all: links.length
18 | };
19 |
20 | return new Promise((resolve, reject) => {
21 | this.resolve = resolve;
22 | this.reject = reject;
23 | this.launch();
24 | }).catch(error => alert(error));
25 | }
26 |
27 | async parse(url, retry) {
28 | try {
29 | const html = await rp.get({ url, timeout: 5000 });
30 | this.onResponse(url, html);
31 | } catch (error) {
32 | this.onError(url, retry);
33 | }
34 | }
35 |
36 | onResponse(url, html) {
37 | const table = ParseMethods.table(html);
38 | const primitive = ParseMethods.primitive(html);
39 | const proxies = table && primitive ? [...table, ...primitive] : primitive ? primitive : table;
40 |
41 | if (proxies.length > 0) {
42 | this.results.proxies.push(proxies);
43 | }
44 |
45 | this.isDone();
46 | }
47 |
48 | onError(url, retry) {
49 | if (!retry && this.config.retry) {
50 | return this.parse(url, true);
51 | }
52 |
53 | this.isDone();
54 | }
55 |
56 | isDone() {
57 | this.counter.done++;
58 | store.dispatch(updateParsingStatus(this.counter.done, this.results.length));
59 |
60 | if (this.counter.done == this.counter.all) {
61 | this.resolve(this.results);
62 | } else {
63 | this.run();
64 | }
65 | }
66 |
67 | run() {
68 | if (!this.queue.length) {
69 | return;
70 | }
71 |
72 | const url = this.queue.pop();
73 | this.parse(url);
74 | }
75 |
76 | launch() {
77 | const startThreadsCount = this.queue.length > this.config.threads ? this.config.threads : this.queue.length;
78 |
79 | setTimeout(() => {
80 | for (let index = 0; index < startThreadsCount; index++) {
81 | this.run();
82 | }
83 | }, 300);
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/core/settings.js:
--------------------------------------------------------------------------------
1 | import { writeFile, readFileSync, existsSync } from 'fs';
2 | import { SETTINGS_FILE_PATH, MERGED_DEFAULT_SETTINGS } from '../constants/SettingsConstants';
3 |
4 | export const saveSettings = setting => {
5 | writeFile(SETTINGS_FILE_PATH, JSON.stringify(setting, null, 4), () => null);
6 | };
7 |
8 | const getSettings = () => {
9 | if (existsSync(SETTINGS_FILE_PATH)) {
10 | try {
11 | return {
12 | ...MERGED_DEFAULT_SETTINGS,
13 | ...JSON.parse(readFileSync(SETTINGS_FILE_PATH, 'utf8'))
14 | };
15 | } catch (error) {
16 | return MERGED_DEFAULT_SETTINGS;
17 | }
18 | }
19 |
20 | return MERGED_DEFAULT_SETTINGS;
21 | };
22 |
23 | export const initial = getSettings();
24 |
--------------------------------------------------------------------------------
/src/core/updater.js:
--------------------------------------------------------------------------------
1 | import rp from 'request-promise';
2 | import { remote } from 'electron';
3 |
4 | const {
5 | app: { getVersion }
6 | } = remote;
7 |
8 | export const currentVersion = getVersion();
9 |
10 | export const getLatestVersionInfo = async () => {
11 | try {
12 | const releaseData = await rp.get({
13 | url: 'https://api.github.com/repos/assnctr/unfx-proxy-parser/releases/latest',
14 | json: true,
15 | timeout: 5000,
16 | headers: {
17 | 'User-Agent': 'Unfx Version Lookup'
18 | }
19 | });
20 |
21 | const version = releaseData.tag_name.slice(1);
22 |
23 | return version > currentVersion ? { version, releaseData } : false;
24 | } catch (error) {
25 | return false;
26 | }
27 | };
28 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import url from 'url';
3 | import { BrowserWindow, app } from 'electron';
4 | import installExtension, { REACT_DEVELOPER_TOOLS } from 'electron-devtools-installer';
5 |
6 | let window;
7 |
8 | const devWindow = () => {
9 | installExtension(REACT_DEVELOPER_TOOLS)
10 | .then(name => console.log('Added:', name))
11 | .catch(err => console.log('Error:', err));
12 |
13 | window = new BrowserWindow({
14 | width: 1650,
15 | height: 980,
16 | show: false
17 | });
18 |
19 | window.webContents.openDevTools();
20 | };
21 |
22 | const prodWindow = () => {
23 | window = new BrowserWindow({
24 | minWidth: 1000,
25 | minHeight: 680,
26 | width: 1180,
27 | height: 752,
28 | show: false,
29 | resizable: true
30 | });
31 |
32 | window.setMenu(null);
33 | };
34 |
35 | const createWindow = () => {
36 | process.env.NODE_ENV !== 'production' ? devWindow() : prodWindow();
37 |
38 | window.loadURL(
39 | process.env.NODE_ENV !== 'production'
40 | ? 'http://localhost:8080'
41 | : url.format({
42 | pathname: path.join(__dirname, 'index.html'),
43 | protocol: 'file:',
44 | slashes: true
45 | })
46 | );
47 |
48 | window.on('ready-to-show', () => {
49 | window.show();
50 | });
51 |
52 | window.on('closed', () => {
53 | window = null;
54 | });
55 | };
56 |
57 | app.on('ready', createWindow);
58 |
59 | app.on('window-all-closed', () => {
60 | if (process.platform !== 'darwin') {
61 | app.quit();
62 | }
63 | });
64 |
65 | app.on('activate', () => {
66 | if (window === null) {
67 | createWindow();
68 | }
69 | });
70 |
--------------------------------------------------------------------------------
/src/misc/methods.js:
--------------------------------------------------------------------------------
1 | export class ParseMethods {
2 | static primitive(content) {
3 | return content.match(new RegExp('[1-2]?[0-9]{1,3}[.][1-2]?[0-9]{1,3}[.][1-2]?[0-9]{1,3}[.][1-2]?[0-9]{1,3}[:][1-9]?[0-9]{1,5}', 'gi'));
4 | }
5 |
6 | static table(content) {
7 | try {
8 | return content
9 | .match(new RegExp('(.*?)', 'gi'))
10 | .map(item => {
11 | return item.match(new RegExp('([1-2]?[0-9]{1,3}[.][1-2]?[0-9]{1,3}[.][1-2]?[0-9]{1,3}[.][1-2]?[0-9]{1,3})(?:.*?)([1-9]?[0-9]{1,5})', 'i'));
12 | })
13 | .filter(item => {
14 | return item != null;
15 | })
16 | .map(item => {
17 | return item[1] + ':' + item[2];
18 | });
19 | } catch (error) {
20 | return [];
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/misc/other.js:
--------------------------------------------------------------------------------
1 | export const uniq = array => [...new Set([...array])];
2 |
3 | export const wait = ms => {
4 | return new Promise(resolve => setTimeout(resolve, ms));
5 | };
6 |
--------------------------------------------------------------------------------
/src/misc/regexes.js:
--------------------------------------------------------------------------------
1 | export const isURL = str => /^(http|https?:\/\/)?((([a-z\d]([a-z\d-]*[a-z\d])*)\.)+[a-z]{2,}|((\d{1,3}\.){3}\d{1,3}))(\:\d+)?(\/[-a-z\d%_.~+]*)*(\?[;&a-z\d%_.~+=-]*)?(\#[-a-z\d_]*)?$/i.test(str);
2 |
--------------------------------------------------------------------------------
/src/misc/text.js:
--------------------------------------------------------------------------------
1 | export const splitByKK = content => content.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
2 |
3 | export const bytesToSize = bytes => {
4 | if (bytes == 0) return '0 B';
5 |
6 | const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
7 | const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
8 | return `${Math.round(bytes / Math.pow(1024, i))} ${sizes[i]}`;
9 | };
10 |
--------------------------------------------------------------------------------
/src/renderer.dev.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Root from './components/Root';
4 | import { AppContainer } from 'react-hot-loader';
5 | import { Provider } from 'react-redux';
6 | import store from './store/index';
7 |
8 | const root = document.getElementById('root');
9 |
10 | const render = () => {
11 | ReactDOM.render(
12 |
13 |
14 |
15 |
16 | ,
17 | root
18 | );
19 | };
20 |
21 | render();
22 |
23 | if (module.hot) {
24 | module.hot.accept('./components/Root', render);
25 | }
26 |
--------------------------------------------------------------------------------
/src/renderer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Root from './components/Root';
4 | import { Provider } from 'react-redux';
5 | import store from './store/index';
6 |
7 | const root = document.getElementById('root');
8 |
9 | const render = () => {
10 | ReactDOM.render(
11 |
12 |
13 | ,
14 | root
15 | );
16 | };
17 |
18 | render();
19 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import rootReducer from './reducers/';
2 | import { createStore, applyMiddleware } from 'redux';
3 | import thunkMiddleware from 'redux-thunk';
4 |
5 | const createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore);
6 | const store = createStoreWithMiddleware(rootReducer);
7 |
8 | export default store;
9 |
--------------------------------------------------------------------------------
/src/store/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import main from './main';
3 | import process from './process';
4 | import result from './result';
5 | import update from './update';
6 |
7 | const rootReducer = combineReducers({
8 | main,
9 | process,
10 | result,
11 | update
12 | });
13 |
14 | export default rootReducer;
15 |
--------------------------------------------------------------------------------
/src/store/reducers/main.js:
--------------------------------------------------------------------------------
1 | import { initial } from '../../core/settings';
2 | import { MAIN_CHANGE_OPTION, MAIN_TOGGLE_OPTION } from '../../constants/ActionTypes';
3 |
4 | const main = (state = initial.main, action) => {
5 | switch (action.type) {
6 | case MAIN_CHANGE_OPTION:
7 | return {
8 | ...state,
9 | [action.target]: action.value
10 | };
11 | case MAIN_TOGGLE_OPTION:
12 | return {
13 | ...state,
14 | [action.target]: !state[action.target]
15 | };
16 | default:
17 | return state;
18 | }
19 | };
20 |
21 | export default main;
22 |
--------------------------------------------------------------------------------
/src/store/reducers/process.js:
--------------------------------------------------------------------------------
1 | import {
2 | PROCESS_START_CRAWLING,
3 | PROCESS_UPDATE_CRAWLING_LEVEL_STATUS,
4 | PROCESS_ADD_CRAWLING_LEVEL,
5 | PROCESS_START_PARSING,
6 | PROCESS_UPDATE_PARSING_STATUS,
7 | CLOSE
8 | } from '../../constants/ActionTypes';
9 |
10 | const initial = {
11 | active: false,
12 | stage: 0,
13 | crawling: [],
14 | parsing: {
15 | all: 0,
16 | done: 0
17 | }
18 | };
19 |
20 | const process = (state = initial, action) => {
21 | switch (action.type) {
22 | case PROCESS_START_CRAWLING:
23 | return {
24 | ...state,
25 | active: true,
26 | stage: 1
27 | };
28 | case PROCESS_START_PARSING:
29 | return {
30 | ...state,
31 | active: true,
32 | stage: 2,
33 | parsing: {
34 | all: action.all,
35 | done: 0
36 | }
37 | };
38 | case PROCESS_UPDATE_PARSING_STATUS:
39 | return {
40 | ...state,
41 | parsing: {
42 | all: state.parsing.all,
43 | done: action.done
44 | }
45 | };
46 | case PROCESS_ADD_CRAWLING_LEVEL:
47 | return {
48 | ...state,
49 | crawling: [
50 | ...state.crawling,
51 | {
52 | level: action.level,
53 | links: {
54 | all: action.all,
55 | passed: action.passed,
56 | found: 0
57 | }
58 | }
59 | ]
60 | };
61 | case PROCESS_UPDATE_CRAWLING_LEVEL_STATUS:
62 | return {
63 | ...state,
64 | crawling: state.crawling.map(item => {
65 | if (item.level == action.level) {
66 | return {
67 | ...item,
68 | links: {
69 | ...item.links,
70 | passed: action.passed,
71 | found: action.found
72 | }
73 | };
74 | }
75 |
76 | return item;
77 | })
78 | };
79 | case CLOSE:
80 | return initial;
81 | default:
82 | return state;
83 | }
84 | };
85 |
86 | export default process;
87 |
--------------------------------------------------------------------------------
/src/store/reducers/result.js:
--------------------------------------------------------------------------------
1 | import { RESULT_SHOW, RESULT_TOGGLE_COUNTRY, CLOSE } from '../../constants/ActionTypes';
2 |
3 | const initial = {
4 | active: false,
5 | linksWithProxies: [],
6 | proxiesByCountries: []
7 | };
8 |
9 | const result = (state = initial, action) => {
10 | switch (action.type) {
11 | case RESULT_SHOW:
12 | return {
13 | active: true,
14 | linksWithProxies: action.linksWithProxies,
15 | proxiesByCountries: action.proxiesByCountries
16 | };
17 | case RESULT_TOGGLE_COUNTRY:
18 | if (action.all) {
19 | return {
20 | ...state,
21 | proxiesByCountries: state.proxiesByCountries.map(item => {
22 | return {
23 | ...item,
24 | active: action.state
25 | };
26 | })
27 | };
28 | }
29 |
30 | return {
31 | ...state,
32 | proxiesByCountries: state.proxiesByCountries.map(item => {
33 | if (item.code == action.code) {
34 | return {
35 | ...item,
36 | active: action.state
37 | };
38 | }
39 |
40 | return item;
41 | })
42 | };
43 | case CLOSE:
44 | return initial;
45 | default:
46 | return state;
47 | }
48 | };
49 |
50 | export default result;
51 |
--------------------------------------------------------------------------------
/src/store/reducers/update.js:
--------------------------------------------------------------------------------
1 | import { UPDATE_CHANGE_STATE, UPDATE_UP_DOWNLOAD_PROGRESS, UPDATE_CLOSE } from '../../constants/ActionTypes';
2 |
3 | const initialState = {
4 | active: true,
5 | isAvailable: false,
6 | isChecking: true,
7 | onDownloading: false,
8 | downloadProgress: 0,
9 | info: null
10 | };
11 |
12 | const update = (state = initialState, action) => {
13 | switch (action.type) {
14 | case UPDATE_CHANGE_STATE:
15 | return {
16 | ...state,
17 | ...action.nextState
18 | };
19 | case UPDATE_UP_DOWNLOAD_PROGRESS:
20 | return {
21 | ...state,
22 | downloadProgress: action.percent
23 | };
24 | case UPDATE_CLOSE:
25 | return {
26 | ...state,
27 | active: false
28 | };
29 | default:
30 | return state;
31 | }
32 | };
33 |
34 | export default update;
35 |
--------------------------------------------------------------------------------
/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 |
4 | const getBabelLoader = () => {
5 | const baseOptions = JSON.parse(fs.readFileSync(path.join(__dirname, '.babelrc'), 'utf-8'));
6 | const options = {
7 | ...baseOptions,
8 | presets: baseOptions.presets.map(preset => (preset === '@babel/env' ? ['@babel/env', { modules: false }] : preset)),
9 | babelrc: false
10 | };
11 |
12 | return {
13 | loader: 'babel-loader',
14 | options
15 | };
16 | };
17 |
18 | export default {
19 | resolve: {
20 | extensions: ['.js', '.jsx', '.json'],
21 | modules: [path.join(__dirname, 'src'), 'node_modules']
22 | },
23 | output: {
24 | path: path.join(__dirname, 'public'),
25 | filename: '[name].js',
26 | publicPath: '/'
27 | },
28 | node: {
29 | __dirname: false,
30 | __filename: false
31 | },
32 | module: {
33 | rules: [
34 | {
35 | test: /\.jsx?$/,
36 | include: path.join(__dirname, 'src'),
37 | loader: getBabelLoader()
38 | },
39 | {
40 | test: /\.(png|woff|woff2|eot|ttf|svg)$/,
41 | loader: 'url-loader',
42 | options: {
43 | name: "[path][name].[ext]",
44 | }
45 | },
46 | {
47 | test: /\.(postcss|css)?$/,
48 | use: ['style-loader', {
49 | loader: 'css-loader',
50 | options: {
51 | importLoaders: 1
52 | }
53 | }, 'postcss-loader']
54 | }
55 | ]
56 | }
57 | };
58 |
--------------------------------------------------------------------------------
/webpack.config.main.babel.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import baseConfig from './webpack.config.base';
3 |
4 | export default {
5 | ...baseConfig,
6 | entry: {
7 | main: path.join(__dirname, 'src/index')
8 | },
9 | target: 'electron-main'
10 | };
11 |
--------------------------------------------------------------------------------
/webpack.config.renderer.babel.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import webpack from 'webpack';
3 | import baseConfig from './webpack.config.base';
4 |
5 | export default {
6 | ...baseConfig,
7 | entry: {
8 | renderer: ['react-hot-loader/patch', path.join(__dirname, process.env.NODE_ENV === 'production' ? 'src/renderer' : 'src/renderer.dev')]
9 | },
10 | devServer: {
11 | headers: {
12 | 'Access-Control-Allow-Origin': '*'
13 | },
14 | contentBase: baseConfig.output.path,
15 | publicPath: baseConfig.output.publicPath,
16 | historyApiFallback: true,
17 | hotOnly: true
18 | },
19 | ...(process.env.NODE_ENV === 'production'
20 | ? {}
21 | : {
22 | plugins: [new webpack.HotModuleReplacementPlugin(), new webpack.NamedModulesPlugin()]
23 | }),
24 | target: 'electron-renderer'
25 | };
26 |
--------------------------------------------------------------------------------