block-proxy 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +33 -1
- package/.claude/skills/build-client/skill.md +24 -0
- package/.claude/skills/release-client/skill.md +68 -0
- package/CLAUDE.md +69 -67
- package/Dockerfile +1 -1
- package/README.md +38 -24
- package/build/asset-manifest.json +6 -6
- package/build/index.html +1 -1
- package/build/static/css/main.3f317ce6.css +2 -0
- package/build/static/css/main.3f317ce6.css.map +1 -0
- package/build/static/js/{main.2247fb80.js → main.af1923ea.js} +3 -3
- package/build/static/js/main.af1923ea.js.map +1 -0
- package/client/app.py +312 -0
- package/client/build.sh +84 -0
- package/client/config.py +49 -0
- package/client/config_window.py +155 -0
- package/client/icons/app.icns +0 -0
- package/client/icons/app_example.png +0 -0
- package/client/icons/app_icon.png +0 -0
- package/client/icons/backup/app_example.png +0 -0
- package/client/icons/backup/christmas-sock_dark.png +0 -0
- package/client/icons/backup/christmas-sock_light.png +0 -0
- package/client/icons/backup/socks_on_G.png +0 -0
- package/client/icons/backup/socks_on_M.png +0 -0
- package/client/icons/christmas-sock_dark.png +0 -0
- package/client/icons/christmas-sock_light.png +0 -0
- package/client/icons/christmas-sock_light_bar.png +0 -0
- package/client/icons/socks_on_G.png +0 -0
- package/client/icons/socks_on_G_bar.png +0 -0
- package/client/icons/socks_on_M.png +0 -0
- package/client/icons/socks_on_M_bar.png +0 -0
- package/client/main.py +28 -0
- package/client/proxy_core.py +475 -0
- package/client/requirements.txt +3 -0
- package/client/scripts/download_xray.sh +30 -0
- package/client/setup.py +30 -0
- package/client/system_proxy.py +94 -0
- package/client/tests/__init__.py +0 -0
- package/client/tests/test_config.py +72 -0
- package/client/tests/test_system_proxy.py +69 -0
- package/client/watch-icons.js +31 -0
- package/config.json +4 -199
- package/docs/superpowers/plans/2026-05-27-blockproxyclient.md +1274 -0
- package/docs/superpowers/specs/2026-05-27-blockproxyclient-design.md +264 -0
- package/package.json +11 -5
- package/proxy/proxy.js +19 -34
- package/server/express.js +17 -1
- package/src/App.css +596 -276
- package/src/App.js +25 -32
- package/src/index.css +3 -4
- package/test/lib/mock-server.js +133 -0
- package/test/proxy-tests.js +708 -0
- package/test/run.js +330 -0
- package/build/static/css/main.8bfa3d5f.css +0 -2
- package/build/static/css/main.8bfa3d5f.css.map +0 -1
- package/build/static/js/main.2247fb80.js.map +0 -1
- package/hack-of-anyproxy/lib/requestHandler.js +0 -1060
- /package/build/static/js/{main.2247fb80.js.LICENSE.txt → main.af1923ea.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import tempfile
|
|
4
|
+
import pytest
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
8
|
+
from config import Config, DEFAULT_CONFIG
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestConfig:
|
|
12
|
+
def setup_method(self):
|
|
13
|
+
self.tmp_dir = tempfile.mkdtemp()
|
|
14
|
+
self.config_path = os.path.join(self.tmp_dir, "config.json")
|
|
15
|
+
self.config = Config(config_path=self.config_path)
|
|
16
|
+
|
|
17
|
+
def test_default_config_created_on_first_load(self):
|
|
18
|
+
data = self.config.load()
|
|
19
|
+
assert data["server"]["port"] == 8002
|
|
20
|
+
assert data["server"]["tls"] is True
|
|
21
|
+
assert data["server"]["allowInsecure"] is True
|
|
22
|
+
assert data["local"]["socks_port"] == 1080
|
|
23
|
+
assert data["local"]["http_port"] == 1087
|
|
24
|
+
assert data["local"]["udp"] is True
|
|
25
|
+
assert data["mode"] == "global"
|
|
26
|
+
assert os.path.exists(self.config_path)
|
|
27
|
+
|
|
28
|
+
def test_save_and_load_roundtrip(self):
|
|
29
|
+
self.config.load()
|
|
30
|
+
self.config.data["server"]["address"] = "10.0.0.1"
|
|
31
|
+
self.config.data["server"]["port"] = 9002
|
|
32
|
+
self.config.data["server"]["username"] = "user1"
|
|
33
|
+
self.config.data["server"]["password"] = "pass1"
|
|
34
|
+
self.config.save()
|
|
35
|
+
|
|
36
|
+
config2 = Config(config_path=self.config_path)
|
|
37
|
+
data = config2.load()
|
|
38
|
+
assert data["server"]["address"] == "10.0.0.1"
|
|
39
|
+
assert data["server"]["port"] == 9002
|
|
40
|
+
assert data["server"]["username"] == "user1"
|
|
41
|
+
assert data["server"]["password"] == "pass1"
|
|
42
|
+
|
|
43
|
+
def test_load_existing_config(self):
|
|
44
|
+
existing = {
|
|
45
|
+
"server": {
|
|
46
|
+
"address": "example.com",
|
|
47
|
+
"port": 443,
|
|
48
|
+
"username": "abc",
|
|
49
|
+
"password": "def",
|
|
50
|
+
"tls": False,
|
|
51
|
+
"allowInsecure": False,
|
|
52
|
+
},
|
|
53
|
+
"local": {"socks_port": 2080, "http_port": 2087, "udp": False},
|
|
54
|
+
"mode": "manual",
|
|
55
|
+
}
|
|
56
|
+
with open(self.config_path, "w") as f:
|
|
57
|
+
json.dump(existing, f)
|
|
58
|
+
|
|
59
|
+
data = self.config.load()
|
|
60
|
+
assert data["server"]["address"] == "example.com"
|
|
61
|
+
assert data["server"]["tls"] is False
|
|
62
|
+
assert data["local"]["socks_port"] == 2080
|
|
63
|
+
assert data["mode"] == "manual"
|
|
64
|
+
|
|
65
|
+
def test_is_configured_false_when_no_address(self):
|
|
66
|
+
self.config.load()
|
|
67
|
+
assert self.config.is_configured() is False
|
|
68
|
+
|
|
69
|
+
def test_is_configured_true_when_address_set(self):
|
|
70
|
+
self.config.load()
|
|
71
|
+
self.config.data["server"]["address"] = "10.0.0.1"
|
|
72
|
+
assert self.config.is_configured() is True
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
from unittest.mock import patch, call, MagicMock
|
|
5
|
+
|
|
6
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
7
|
+
from system_proxy import SystemProxy
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestSystemProxy:
|
|
11
|
+
def setup_method(self):
|
|
12
|
+
self.proxy = SystemProxy()
|
|
13
|
+
|
|
14
|
+
@patch("system_proxy.SystemProxy._get_active_interfaces")
|
|
15
|
+
@patch("system_proxy.subprocess.run")
|
|
16
|
+
def test_enable_sets_all_proxy_types(self, mock_run, mock_interfaces):
|
|
17
|
+
mock_interfaces.return_value = ["Wi-Fi"]
|
|
18
|
+
mock_run.return_value = MagicMock(returncode=0, stdout="Enabled: Yes\n", stderr="")
|
|
19
|
+
|
|
20
|
+
self.proxy.enable(socks_port=1080, http_port=1087)
|
|
21
|
+
|
|
22
|
+
expected_calls = [
|
|
23
|
+
call(["networksetup", "-setsocksfirewallproxy", "Wi-Fi", "127.0.0.1", "1080"], capture_output=True, text=True),
|
|
24
|
+
call(["networksetup", "-setsocksfirewallproxystate", "Wi-Fi", "on"], capture_output=True, text=True),
|
|
25
|
+
call(["networksetup", "-setwebproxy", "Wi-Fi", "127.0.0.1", "1087"], capture_output=True, text=True),
|
|
26
|
+
call(["networksetup", "-setwebproxystate", "Wi-Fi", "on"], capture_output=True, text=True),
|
|
27
|
+
call(["networksetup", "-setsecurewebproxy", "Wi-Fi", "127.0.0.1", "1087"], capture_output=True, text=True),
|
|
28
|
+
call(["networksetup", "-setsecurewebproxystate", "Wi-Fi", "on"], capture_output=True, text=True),
|
|
29
|
+
]
|
|
30
|
+
mock_run.assert_has_calls(expected_calls, any_order=True)
|
|
31
|
+
|
|
32
|
+
@patch("system_proxy.SystemProxy._get_active_interfaces")
|
|
33
|
+
@patch("system_proxy.subprocess.run")
|
|
34
|
+
def test_disable_clears_all_proxy_types(self, mock_run, mock_interfaces):
|
|
35
|
+
mock_interfaces.return_value = ["Wi-Fi"]
|
|
36
|
+
mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
|
|
37
|
+
|
|
38
|
+
self.proxy.disable()
|
|
39
|
+
|
|
40
|
+
expected_calls = [
|
|
41
|
+
call(["networksetup", "-setsocksfirewallproxystate", "Wi-Fi", "off"], capture_output=True, text=True),
|
|
42
|
+
call(["networksetup", "-setwebproxystate", "Wi-Fi", "off"], capture_output=True, text=True),
|
|
43
|
+
call(["networksetup", "-setsecurewebproxystate", "Wi-Fi", "off"], capture_output=True, text=True),
|
|
44
|
+
]
|
|
45
|
+
mock_run.assert_has_calls(expected_calls, any_order=True)
|
|
46
|
+
|
|
47
|
+
@patch("system_proxy.SystemProxy._get_active_interfaces")
|
|
48
|
+
@patch("system_proxy.subprocess.run")
|
|
49
|
+
def test_enable_multiple_interfaces(self, mock_run, mock_interfaces):
|
|
50
|
+
mock_interfaces.return_value = ["Wi-Fi", "Ethernet"]
|
|
51
|
+
mock_run.return_value = MagicMock(returncode=0, stdout="Enabled: Yes\n", stderr="")
|
|
52
|
+
|
|
53
|
+
self.proxy.enable(socks_port=1080, http_port=1087)
|
|
54
|
+
|
|
55
|
+
# 6 set calls per interface x 2 + 3 verify calls per interface x 2 = 18
|
|
56
|
+
assert mock_run.call_count == 18
|
|
57
|
+
|
|
58
|
+
@patch("system_proxy.subprocess.run")
|
|
59
|
+
def test_get_active_interfaces(self, mock_run):
|
|
60
|
+
mock_run.return_value = MagicMock(
|
|
61
|
+
stdout="An asterisk (*) denotes that a network service is disabled.\nWi-Fi\n*Bluetooth PAN\nEthernet\n",
|
|
62
|
+
returncode=0,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
interfaces = self.proxy._get_active_interfaces()
|
|
66
|
+
|
|
67
|
+
assert "Wi-Fi" in interfaces
|
|
68
|
+
assert "Ethernet" in interfaces
|
|
69
|
+
assert "Bluetooth PAN" not in interfaces
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const { execSync } = require("child_process");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
const ICONS_DIR = path.join(__dirname, "icons");
|
|
6
|
+
const SOURCES = ["socks_on_G.png", "socks_on_M.png"];
|
|
7
|
+
|
|
8
|
+
function resize(source) {
|
|
9
|
+
const name = path.basename(source, ".png");
|
|
10
|
+
const dest = path.join(ICONS_DIR, `${name}_bar.png`);
|
|
11
|
+
execSync(`sips -z 40 39 "${source}" --out "${dest}"`, { stdio: "ignore" });
|
|
12
|
+
console.log(`${new Date().toLocaleTimeString()} regenerated ${name}_bar.png`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function regenAll() {
|
|
16
|
+
SOURCES.forEach((f) => resize(path.join(ICONS_DIR, f)));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const cmd = process.argv[2];
|
|
20
|
+
if (cmd === "--watch") {
|
|
21
|
+
console.log("Watching icons for changes...");
|
|
22
|
+
regenAll();
|
|
23
|
+
SOURCES.forEach((f) => {
|
|
24
|
+
const filePath = path.join(ICONS_DIR, f);
|
|
25
|
+
fs.watch(filePath, (eventType) => {
|
|
26
|
+
if (eventType === "change") resize(filePath);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
} else {
|
|
30
|
+
regenAll();
|
|
31
|
+
}
|
package/config.json
CHANGED
|
@@ -1,202 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
"progress_time_stamp": "1766571511705",
|
|
4
|
-
"block_hosts": [
|
|
5
|
-
{
|
|
6
|
-
"filter_host": "bilibili.com",
|
|
7
|
-
"filter_match_rule": "",
|
|
8
|
-
"filter_start_time": "00:00",
|
|
9
|
-
"filter_end_time": "23:59",
|
|
10
|
-
"filter_weekday": [
|
|
11
|
-
1,
|
|
12
|
-
2,
|
|
13
|
-
3,
|
|
14
|
-
4,
|
|
15
|
-
5,
|
|
16
|
-
6,
|
|
17
|
-
7
|
|
18
|
-
],
|
|
19
|
-
"filter_mac": "6E:FB:18:4D:9C:3E"
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"filter_host": "doubao.com",
|
|
23
|
-
"filter_match_rule": "",
|
|
24
|
-
"filter_start_time": "00:00",
|
|
25
|
-
"filter_end_time": "23:59",
|
|
26
|
-
"filter_weekday": [
|
|
27
|
-
1,
|
|
28
|
-
2,
|
|
29
|
-
3,
|
|
30
|
-
4,
|
|
31
|
-
5,
|
|
32
|
-
6,
|
|
33
|
-
7
|
|
34
|
-
],
|
|
35
|
-
"filter_mac": "6E:FB:18:4D:9C:3E"
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"filter_host": "dburl.net",
|
|
39
|
-
"filter_match_rule": "",
|
|
40
|
-
"filter_start_time": "00:00",
|
|
41
|
-
"filter_end_time": "23:59",
|
|
42
|
-
"filter_weekday": [
|
|
43
|
-
1,
|
|
44
|
-
2,
|
|
45
|
-
3,
|
|
46
|
-
4,
|
|
47
|
-
5,
|
|
48
|
-
6,
|
|
49
|
-
7
|
|
50
|
-
],
|
|
51
|
-
"filter_mac": "6E:FB:18:4D:9C:3E"
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
"filter_host": "baidu.com",
|
|
55
|
-
"filter_match_rule": "",
|
|
56
|
-
"filter_start_time": "00:00",
|
|
57
|
-
"filter_end_time": "23:59",
|
|
58
|
-
"filter_weekday": [
|
|
59
|
-
1,
|
|
60
|
-
2,
|
|
61
|
-
3,
|
|
62
|
-
4,
|
|
63
|
-
5,
|
|
64
|
-
6,
|
|
65
|
-
7
|
|
66
|
-
],
|
|
67
|
-
"filter_mac": "6E:FB:18:4D:9C:3E"
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
"filter_host": "youtube.com",
|
|
71
|
-
"filter_match_rule": "^https?:\\/\\/(www|s)\\.youtube\\.com\\/(pagead|ptracking)",
|
|
72
|
-
"filter_start_time": "00:00",
|
|
73
|
-
"filter_end_time": "23:59",
|
|
74
|
-
"filter_weekday": [
|
|
75
|
-
1,
|
|
76
|
-
2,
|
|
77
|
-
3,
|
|
78
|
-
4,
|
|
79
|
-
5,
|
|
80
|
-
6,
|
|
81
|
-
7
|
|
82
|
-
],
|
|
83
|
-
"compiledFilterRegexp": {}
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
"filter_host": "youtube.com",
|
|
87
|
-
"filter_match_rule": "^https?:\\/\\/s\\.youtube\\.com\\/api\\/stats\\/qoe\\?adcontext",
|
|
88
|
-
"filter_start_time": "00:00",
|
|
89
|
-
"filter_end_time": "23:59",
|
|
90
|
-
"filter_weekday": [
|
|
91
|
-
1,
|
|
92
|
-
2,
|
|
93
|
-
3,
|
|
94
|
-
4,
|
|
95
|
-
5,
|
|
96
|
-
6,
|
|
97
|
-
7
|
|
98
|
-
],
|
|
99
|
-
"compiledFilterRegexp": {}
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
"filter_host": "youtube.com",
|
|
103
|
-
"filter_match_rule": "^https?:\\/\\/(www|s)\\.youtube\\.com\\/api\\/stats\\/ads",
|
|
104
|
-
"filter_start_time": "00:00",
|
|
105
|
-
"filter_end_time": "23:59",
|
|
106
|
-
"filter_weekday": [
|
|
107
|
-
1,
|
|
108
|
-
2,
|
|
109
|
-
3,
|
|
110
|
-
4,
|
|
111
|
-
5,
|
|
112
|
-
6,
|
|
113
|
-
7
|
|
114
|
-
],
|
|
115
|
-
"compiledFilterRegexp": {}
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
"filter_host": "googlevideo.com",
|
|
119
|
-
"filter_match_rule": "^https?:\\/\\/[\\w-]+\\.googlevideo\\.com\\/(?!(dclk_video_ads|videoplayback\\?)).+&oad",
|
|
120
|
-
"filter_start_time": "00:00",
|
|
121
|
-
"filter_end_time": "23:59",
|
|
122
|
-
"filter_weekday": [
|
|
123
|
-
1,
|
|
124
|
-
2,
|
|
125
|
-
3,
|
|
126
|
-
4,
|
|
127
|
-
5,
|
|
128
|
-
6,
|
|
129
|
-
7
|
|
130
|
-
],
|
|
131
|
-
"compiledFilterRegexp": {}
|
|
132
|
-
}
|
|
133
|
-
],
|
|
2
|
+
"block_hosts": [],
|
|
134
3
|
"proxy_port": 8001,
|
|
135
|
-
"web_interface_port": 8003,
|
|
136
|
-
"your_domain": "yui.cool",
|
|
137
|
-
"vpn_proxy": "",
|
|
138
|
-
"auth_username": "lijing",
|
|
139
|
-
"auth_password": "lijing",
|
|
140
|
-
"enable_express": "1",
|
|
141
|
-
"enable_socks5": "1",
|
|
142
4
|
"socks5_port": 8002,
|
|
143
|
-
"
|
|
144
|
-
"
|
|
145
|
-
|
|
146
|
-
"ip": "192.168.124.1",
|
|
147
|
-
"mac": "FA:27:3C:E5:31:5F"
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
"ip": "192.168.124.2",
|
|
151
|
-
"mac": "7C:DE:78:A9:83:A0"
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
"ip": "192.168.124.6",
|
|
155
|
-
"mac": "0:DD:B6:EB:26:5C"
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
"ip": "192.168.124.10",
|
|
159
|
-
"mac": "FA:27:3C:E5:31:5F"
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
"ip": "192.168.124.34",
|
|
163
|
-
"mac": "D6:A0:61:69:67:F6"
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
"ip": "192.168.124.37",
|
|
167
|
-
"mac": "DE:1E:87:ED:17:8B"
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
"ip": "192.168.124.49",
|
|
171
|
-
"mac": "62:A1:CC:42:55:E4"
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
"ip": "192.168.124.107",
|
|
175
|
-
"mac": "6:26:EA:5A:9E:6C"
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
"ip": "192.168.124.111",
|
|
179
|
-
"mac": "74:3F:C2:67:74:98"
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
"ip": "192.168.124.125",
|
|
183
|
-
"mac": "14:C0:50:14:6E:A5"
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
"ip": "192.168.124.128",
|
|
187
|
-
"mac": "48:F3:F3:CA:1D:E"
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
"ip": "192.168.124.200",
|
|
191
|
-
"mac": "54:52:84:95:DF:5E"
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
"ip": "192.168.124.240",
|
|
195
|
-
"mac": "F4:6B:8C:90:29:5"
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
"ip": "192.168.124.251",
|
|
199
|
-
"mac": "6E:FB:18:4D:9C:3E"
|
|
200
|
-
}
|
|
201
|
-
]
|
|
202
|
-
}
|
|
5
|
+
"auth_username": "admin",
|
|
6
|
+
"auth_password": "admin"
|
|
7
|
+
}
|