├── .gitignore ├── DOCUMENT.md ├── LICENSE ├── LICENSE.html ├── LICENSE.md ├── README.md ├── src └── bridgehouse │ ├── __init__.py │ ├── bridge.py │ ├── build.py │ ├── editMap │ ├── __init__.py │ ├── apiTAB.py │ ├── cafeteriaTAB.py │ ├── dnsTAB.py │ ├── inbound │ │ ├── __init__.py │ │ ├── dokodemodoorPanel.py │ │ ├── httpPanel.py │ │ ├── mtPanel.py │ │ ├── shadowsocksPanel.py │ │ ├── socksPanel.py │ │ └── vmessPanel.py │ ├── logTAB.py │ ├── nauticalChartPanel.py │ ├── outbound │ │ ├── __init__.py │ │ ├── blackholePanel.py │ │ ├── freedomPanel.py │ │ ├── shadowsocksPanel.py │ │ ├── socksPanel.py │ │ └── vmessPanel.py │ ├── policyTAB.py │ ├── port │ │ ├── __init__.py │ │ ├── inboundPanel.py │ │ ├── openV2rayJSONFile.py │ │ ├── outboundPanel.py │ │ ├── treasureChest.py │ │ └── updateListSignal.py │ ├── router │ │ ├── __init__.py │ │ ├── codegen.py │ │ ├── geoSite.proto │ │ ├── geoSiteEditorPanel.py │ │ ├── geoSite_pb2.py │ │ ├── geoSite_pb2_grpc.py │ │ └── readgfwlist.py │ ├── routingTAB.py │ ├── statsTAB.py │ ├── toolbox │ │ ├── __init__.py │ │ └── toolbox.py │ ├── transport │ │ ├── __init__.py │ │ ├── certificatesPanel.py │ │ ├── dsPanel.py │ │ ├── http2Panel.py │ │ ├── mkcpPanel.py │ │ ├── muxPanel.py │ │ ├── tcpPanel.py │ │ ├── transportPanel.py │ │ └── wsPanel.py │ └── transportTAB.py │ ├── extension │ ├── __init__.py │ ├── bridgePreference.py │ ├── bridgetreasureChest.py │ ├── bugReport.py │ ├── proxyTest.py │ ├── runV2raycore.py │ └── updatePanel.py │ ├── icons │ ├── rocket.psd │ ├── start.ico │ ├── start.png │ └── stop.png │ └── v2ray-shell.spec ├── translations ├── en_US.qm ├── en_US.ts ├── tr.py ├── zh_CN.qm └── zh_CN.ts ├── v2ray-shell.pyw └── v2rayshell.pro /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | .static_storage/ 57 | .media/ 58 | local_settings.py 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # Environments 86 | .env 87 | .venv 88 | env/ 89 | venv/ 90 | ENV/ 91 | env.bak/ 92 | venv.bak/ 93 | venv_linux/ 94 | .venv_linux/ 95 | 96 | # Spyder project settings 97 | .spyderproject 98 | .spyproject 99 | 100 | # Rope project settings 101 | .ropeproject 102 | 103 | # mkdocs documentation 104 | /site 105 | 106 | # mypy 107 | .mypy_cache/ 108 | 109 | # kde 110 | .directory 111 | 112 | # simplegitserver 113 | os 114 | .project 115 | .pydevproject 116 | .settings/ 117 | simplegitweb.conf 118 | -------------------------------------------------------------------------------- /DOCUMENT.md: -------------------------------------------------------------------------------- 1 | ## [帮助文档](https://github.com/v2ray/V2Ray-shell_alpha/wiki/%E5%B8%AE%E5%8A%A9%E6%96%87%E6%A1%A3) 2 | 3 | **注意**:此脚本还属于内测中。很多功能都没有经过严格测试。
4 | **测试前请备份好V2Ray-core配置文件**
5 | **测试前请备份好V2Ray-core配置文件**
6 | **测试前请备份好V2Ray-core配置文件** 7 | 8 | ### 目标人群 9 | 由于脚本还在内测中。所以目前脚本针对的目标人群不是所有人。
10 | 如果你是下列人群,欢迎测试这个脚本帮助改进。 11 | * 对技术感觉兴趣,愿意学习新技术 12 | * 熟悉Qt程序,可以指正脚本中的错误地方 13 | * 脚本使用过程中出错,知道如何修改相应的代码解决问题 14 | * 脚本使用过程中出错,知道如何复现出错问题,定位出错原因 15 | * 脚本导入导出JSON配置错误,并知道如何修改成正确配置 16 | * 熟习[V2Ray官方文档](https://www.v2ray.com/chapter_02/01_overview.html),并且知道如何配置v2ray-core JSON配置文件 17 | * 不怕被冷落 18 | 19 | ### BUG提交 20 | 如果你[提交一个BUG](https://www.chiark.greenend.org.uk/~sgtatham/bugs-cn.html),但无法提供复现方法与步骤或者解决方法。不会得到优先处理。
21 | 任何要求增加新功能的要求将不会被优生处理。
22 | 复现步骤时,translations, config.v2rayshell这个三个文件放到src/bridgehouse/目录中。
23 | 在命令行运行`python3 bridge.py`,以便获得Python Traceback数据。
24 | 25 | * 编辑器不支持编辑带有注释的JSON文件。请自行移除注释。 [原因](https://plus.google.com/+DouglasCrockfordEsq/posts/RK8qyGVaGSr) 26 | * 编辑器生成的配置只能保证符合文档规范,无法保证配置是否有用。 27 | * 使用编辑器生成的配置文件之前请使用如下命令检查配置是否正确 `v2ray -test config.json` (暂无计划将此功能集成在脚本中) 28 | * 编辑JSON文档暂无错误提示。 29 | * 编辑器不对输入的数据做合法验证。比如IP地址可以输入IPv4,IPv6,域名,或者其他任意数据 30 | * 如果想直接使用编辑器,可以在src/bridgehouse/editMap/ 目录找到 nauticalChartPanel.py 31 | * 代理服务器延迟测试,并非为本地计算机到VPS的延迟。这个延迟是访问http://www.google.com的延迟。 32 | * 如果频繁测试google.com服务器可能会引起DDOS。VPS有被拉黑的风险。最小测试时间间隔为60秒。 33 | 对此,你可以在自己的VPS开启http协议用作代理连接状态检查。(当然检查V2Ray-core的日志文件也是个有效的方法,同样暂无计划将此功能集成在脚本中。) 34 | * 编辑JSON文档时,不会自动分析in/outbound(Detour)已有的policy level。
在面板中添加新的in/outbound(Detour)时,所有的policy level会出现在PolicyTAB列表中(V2Ray-core V3.1之前的配置文件会有此问题)。 35 | * 测试服务器连接状态功能关闭,服务器延迟测试也会自己关闭。 36 | * 编译二进制文件,使用二进制文件的一切问题无法处理。 37 | * build.py文件为开发人员使用,可以删除。 38 | 39 | ### 参与开发 40 | 欢迎任何对此脚本感兴趣的开发者参与开发。
41 | 但请知道,参与这个脚本的开发者必须放弃对脚本的一切权利(开发者同样也包括原始作者)。
42 | 如果你无法接受这个条件,你可以自由复制这个脚本单独发行。
43 | 但请注意如果使用PyQt请遵循Riverbank公司的[协议](https://www.riverbankcomputing.com/commercial/license-faq)。
44 | 或者使用PySide请遵循Qt公司[相应协议](http://code.qt.io/cgit/pyside/pyside-setup.git/tree/?h=5.9)
45 | 为了兼顾两家公司的协议,所以此脚本才会使用[公有领域协议](https://zh.wikipedia.org/wiki/%E5%85%AC%E6%9C%89%E9%A2%86%E5%9F%9F)
46 | 如果你购买了Riverbank或Qt的商业授权。闭源是被允许。
47 | PySide2可以在这[下载](http://download.qt.io/snapshots/ci/pyside/5.9/latest/pyside2/)。
48 | PyQt5可以直接用pip安装。 49 | 50 | ### 维护 51 | 由于作者还有其他工作。只能用业余时间去维护这个项目。
52 | 有一年的有效维护保证。
53 | 到2018年12月会视下一年安排,会作出如何接着维护这个项目的决定。
54 | 一但项目稳定版本发行,需要维护的地方将很少。 55 | 56 | ### 新功能增加 57 | 由于这个脚本支持多平台运行。为了减少维护成本,很多功能不增加。
58 | 如设置系统代理,向导生成配置,自动生成配置,配置模板,二维码等等...
59 | 也无法提供[负载均衡](https://zh.wikipedia.org/wiki/%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1),[服务质量](https://zh.wikipedia.org/wiki/%E6%9C%8D%E5%8A%A1%E8%B4%A8%E9%87%8F)等等...
60 | 脚本中路由配置暂时支持 GeoSite.dat 的编辑,还没有完善...
61 | 学习golang中... 62 | 63 | ### 二进制文件 64 | 此脚本不直接提供二进制文件。请自行编译。
65 | 编译方法:
66 | 先安装pyinstaller,安装方法之一 `pip install pyinstaller`
67 | 然后到/src/bridgehouse/目录,在命令行中执行如下编译命令:
68 | 69 | `pyinstaller -F --noconsole -name v2ray-shell bridge.py`
70 | 或者使用 `pyinstaller v2ray-shell.spec` 生成可独立运行的二进制文件可在dist目录中找到。
71 | windows平台亦可直接使用build.py直接生成二进制文件,有可能失败。 72 | windows平台确保有libeay32.dll与ssleay32.dll与安装包同目录。
73 | 这两个文件可以在Python PyQt5安装目录中找到(../Lib/site-packages/PyQt5/Qt/bin/)
74 | 请确保icons与translations与二进制文件同一个目录,以便使用相应功能。
75 | 76 | ubuntu-17.10测试时,pyinstaller使用的是python2.7。请改为python3.6运行。
77 | 命令行中使用which pyinstaller找到pyinstaller文件。
78 | 修改 `#!/usr/bin/python` 为 `#!/usr/bin/python3`
79 | GNOME桌面可以试着用下面代码制作一个桌面程序的启动器,[相关文档](https://standards.freedesktop.org/desktop-entry-spec/latest/index.html)。
80 | V2Ray-shell_path修改为相应的V2Ray-shell存放的绝对路径。 81 | ```Desktop Entry 82 | [Desktop Entry] 83 | Version=1.0 84 | Name=v2rayshell 85 | Comment=This is my v2ray-shell 86 | Exec=V2Ray-shell_path/v2ray-shell.pyw 87 | Icon=V2Ray-shell_path/src/bridgehouse/icons/start.png 88 | Path=V2Ray-shell_path/ 89 | Terminal=false 90 | Type=Application 91 | Categories=Utility;Application; 92 | Name[en_US]=v2rayshell 93 | ``` 94 | 95 | 发布二进制文件时,请附带此脚本的源代码与相应协议。
96 | **备注** :*这个测试在windows 10 平台通过,其他平台未知。对于编译二进制文件, 与其出现的一切问题无法处理*
97 | **谨慎使用第三方发布的二进制文件**
98 | **谨慎使用第三方发布的二进制文件**
99 | **谨慎使用第三方发布的二进制文件** 100 | 101 | ### 翻译 102 | 欢迎有能力与时间的朋友参与脚本的翻译与修正。
103 | 或者帮助设计无版权图标(带Alpha通道的PSD或者TIF文件受欢迎)。 104 | 翻译方法:
105 | > 1. [下载QT开发包](https://download.qt.io/archive/qt/)
106 | > 2. 打开v2rayshell.pro文件,在TRANSLATIONS项目里增加相应语言。
107 | > 3. 命令行中找到v2ray-shell的v2rayshell.pro文件。执行此命令`pylupdate5 v2rayshell.pro `生成ts文件。
108 | > 4. 到translations目录找到相应的ts目标文件。
109 | > 5. 找到QT开发包的Qt Linguist,使用Linguist打开ts文件并开始翻译。Qt Linguist的使用方法可以查看帮助文档Qt Linguist Manual。
110 | > 6. 翻译好后文件后,在Qt Linguist中找到File->Release As 发布翻译好的qm文件。
111 | > 7. 最后打开v2ray-shell脚本,在Option->Preference设置相应的翻译文件并重新启动脚本测试。 112 | 113 | ### PyQt5学习 114 | 此脚本是自底向上开发,几乎所有的脚本都可以单独运行并测试。
115 | 开发者也是第一次接触QT编程。学习过程并不困难。欢迎大家一起来学习,交流指正代码中的错误地方。
116 | 在学习的过程中请善用[搜索引擎](https://en.wikipedia.org/wiki/Category:Internet_search_engines),与使用[stackoverflow](https://stackoverflow.com/) [提问](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/master/README-zh_CN.md)。
117 | 其中QT开发文档是最有力的助手。虽然使用的编程语言不同,但并不影响文档的使用。
118 | [PyQt5的Demo代码参考](https://riverbankcomputing.com/software/pyqt/download5)。
119 | 一些学习网站: 120 | * [Pythonspot](https://pythonspot.com/en/pyqt5/) 121 | * [zetcode](http://zetcode.com/gui/pyqt5/) 122 | * [PythonBlogs](http://pythonthusiast.pythonblogs.com/index.php?op=Search&blogId=230&searchTerms=pyqt) 123 | * [Python wiki](https://wiki.python.org/moin/PyQt) 124 | * [PySide for Android](http://wiki.qt.io/PySide_for_Android_guide) 125 | * [Archi的中文教程](http://www.cnblogs.com/archisama/tag/PyQt5/) 126 | * [皮皮blog](http://blog.csdn.net/column/details/py-qt.html) 127 | 128 | ### 其他 129 | 这个脚本在2017年10月1日开始编写。使用PyQt5的原因是比较熟悉Python语言。选择QT是因为跨平台友好。
130 | 原计划只做个v2ray-core的JSON配置编辑器,写着写着又写了个启动器,现在变成坑了。精力有限,不想搞太深...
131 | 在将来的版本中可能会改写成C++与QML结合(有点渺茫)...... -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | V2Ray-shell License 2 | 3 | WARNING, ANY PROBLEMS (INCLUDING, BUT NOT LIMITED TO, NATIONAL SECURITY, LEGAL, HARDWARE, SOFTWARE, COPYRIGHT, ETHICS, PERSONAL INJURIES) FOR THE USE OF THE V2RAY-SHELL. V2RAY-SHELL AUTHORS DO NOT TAKE ANY RESPONSIBILITY. 4 | 5 | V2Ray-shell License is public domain. 6 | All of the code and documentation in V2Ray-shell has been dedicated to the public domain by the authors(however, the authors are anonymous). 7 | 8 | If you want to release again, you need to know PyQt5 is released under the GPL v3 license and under a commercial license that allows for the development of proprietary applications. 9 | Of course if you do not want to publish this script in GPL v3. You can use PySide to publish this script using LGPL. 10 | If you use the two companies (QT or Riverbank Computing) any one of the commercial license, closed source is allowed. -------------------------------------------------------------------------------- /LICENSE.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

V2Ray-shell License

8 |

9 | WARNING, ANY PROBLEMS (INCLUDING, BUT NOT LIMITED TO, NATIONAL SECURITY, LEGAL, HARDWARE, SOFTWARE, COPYRIGHT, ETHICS, PERSONAL INJURIES) FOR THE USE OF THE V2RAY-SHELL. V2RAY-SHELL AUTHORS DO NOT TAKE ANY RESPONSIBILITY. 10 |

11 |

V2Ray-shell License is 12 | public domain.
13 | All of the code and documentation in V2Ray-shell has been dedicated to the public domain by the authors(however, the authors are anonymous).
14 | If you want to release again, you need to know 15 | PyQt5 is released under the GPL v3 license and under a commercial license that allows for the development of proprietary applications.
16 | Of course if you do not want to publish this script under the GPL v3. You can use 17 | PySide to publish this script using LGPL.
18 | If you use the two companies (QT or Riverbank Computing) any one of the commercial license, closed source is allowed.
19 |

20 | 21 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # V2Ray-shell License 2 | 3 | **WARNING, ANY PROBLEMS (INCLUDING, BUT NOT LIMITED TO, NATIONAL SECURITY, LEGAL, HARDWARE, SOFTWARE, COPYRIGHT, ETHICS, PERSONAL INJURIES) FOR THE USE OF THE V2RAY-SHELL. V2RAY-SHELL AUTHORS DO NOT TAKE ANY RESPONSIBILITY.** 4 | 5 | V2Ray-shell License is [public domain](https://en.wikipedia.org/wiki/Public_domain).
6 | All of the code and documentation in V2Ray-shell has been dedicated to the public domain by the authors(however, the authors are anonymous).
7 | If you want to release again, you need to know [PyQt5](https://www.riverbankcomputing.com/commercial/license-faq) is released under the GPL v3 license and under a commercial license that allows for the development of proprietary applications.
8 | Of course if you do not want to publish this script under the GPL v3. You can use [PySide](https://wiki.qt.io/PySide2) to publish this script using LGPL.
9 | If you use the two companies (QT or Riverbank Computing) any one of the commercial license, closed source is allowed.
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## V2Ray-shell [alpha-version](https://en.wiktionary.org/wiki/alpha_version) 2 | 3 | ### Project description 4 | A [GUI](https://en.wikipedia.org/wiki/Graphical_user_interface) for [V2Ray-core](https://github.com/v2ray/v2ray-core).
5 | This [script](https://en.wikipedia.org/wiki/Scripting_language) is written in [PyQt5](https://www.riverbankcomputing.com/software/pyqt/intro). It is still under test and the basic functions have been completed.
And PyQt is available for Windows, UNIX/Linux, Mac OS X and the Sharp Zaurus.
That means you can use this script on many platforms. 6 | 7 | ### Before use V2Ray-shell 8 | 1. [install Python](https://tutorial.djangogirls.org/en/python_installation/) with pip. ([pip](https://pip.pypa.io/en/stable/), which can download and install other Python packages.)
Python version higher than 3.5 is recommended. 9 | 2. [install PyQt5](http://pyqt.sourceforge.net/Docs/PyQt5/installation.html) in [command line](https://tutorial.djangogirls.org/en/intro_to_command_line/) as [root](http://www.linfo.org/root.html) or [administrator](https://technet.microsoft.com/en-us/library/cc947813%28v%3Dws.10%29.aspx).(Of course you can also use this script on the command line to install PyQt5 as root or administrator permission.) 10 | > `pip3 install PyQt5` 11 | 3. in UNIX/Linux, Mac OS X make sure scripts have [execute permission](https://superuser.com/questions/117704/what-does-the-execute-permission-do). such like below: 12 | > `chmod +x v2ray-shell.pyw ./src/bridgehouse/bridge.py` 13 | 14 | ### How use it 15 | 1. run v2ray-shell.pyw 16 | 2. in the system tray find v2ray-shell icon, right click show panel. 17 | 3. in the v2ray-shell panel, find the menu "options->Preferences", set v2ray file path. and click "aplly and close" button 18 | 4. in the v2ray-shell panel, find the menu "File->Add V2Ray-core config file", and add the v2ray-core's config.json file to panel. 19 | 5. in the v2ray-shell panel, click the v2ray-shell icon (a darkgreen rocket), enable this config.json. 20 | 6. and find the black triangle, click it to run v2ray-core. 21 | 7. in the v2ray-shell panel, find the menu "File->Save V2Ray-shell config file", click it to save v2ray-shell config file. 22 | 23 | ### GeoSite.dat File Edit Support (but not perfect complete) 24 | 1. python -m pip install protobuf grpcio grpcio-tools 25 | 2. this new feature is unstable, you can try another preject https://github.com/onplus/v2ray-SiteDAT 26 | 27 | ### Document and Help 28 | [see the wiki page](https://github.com/v2ray/V2Ray-shell_alpha/wiki/%E5%B8%AE%E5%8A%A9%E6%96%87%E6%A1%A3) 29 | 30 | ### LICENSE 31 | see the LICENSE file details. -------------------------------------------------------------------------------- /src/bridgehouse/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from bridgehouse.editMap import logTAB 4 | logbook = logTAB.logBook() 5 | -------------------------------------------------------------------------------- /src/bridgehouse/build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from pathlib import Path 4 | import shutil, os, sys 5 | from PyInstaller.__main__ import run 6 | 7 | is_win = sys.platform.startswith('win') 8 | spec = "v2ray-shell.spec" 9 | 10 | if (Path("bridge.py").exists() and Path(spec).exists()): 11 | """ 12 | DEVELOPER USE ONLY, NO ANY SUPPORT OR BUG REPORT!! 13 | """ 14 | file = "v2ray-shell" 15 | if is_win: 16 | import site 17 | file = "v2ray-shell.exe" 18 | packages = site.getsitepackages() 19 | libeay32 = packages[1] + "/PyQt5/Qt/bin/libeay32.dll" 20 | ssleay32 = packages[1] + "/PyQt5/Qt/bin/ssleay32.dll" 21 | if (Path(libeay32).exists() and Path(ssleay32).exists()): 22 | try: 23 | shutil.copy(libeay32, "./") 24 | shutil.copy(ssleay32, "./") 25 | except Exception: pass 26 | 27 | if len(sys.argv) == 1: 28 | sys.argv.append(spec) 29 | run() 30 | 31 | if (Path(file).exists()):os.remove(file) 32 | try: 33 | shutil.rmtree("./build") 34 | shutil.move("./dist/" + file, "./") 35 | shutil.rmtree("./dist") 36 | shutil.rmtree("./__pycache__") 37 | except Exception: pass 38 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/apiTAB.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QWidget, QTextEdit, QVBoxLayout, QApplication, 4 | QGroupBox, QLineEdit, QPushButton, QHBoxLayout, QLabel) 5 | from PyQt5.QtCore import QFileInfo, QCoreApplication 6 | import sys, json, copy, re 7 | 8 | v2rayshellDebug = False 9 | 10 | if __name__ == "__main__": 11 | v2rayshellDebug = True 12 | # this for debug test 13 | path = QFileInfo(sys.argv[0]) 14 | srcPath = path.absoluteFilePath().split("/") 15 | sys.path.append("/".join(srcPath[:-3])) 16 | 17 | from bridgehouse.editMap.port import treasureChest 18 | 19 | 20 | class apiTAB(QWidget): 21 | def __init__(self, CaptainstreasureChest=False): 22 | super(apiTAB, self).__init__() 23 | self.apiJSONFILE = { 24 | "tag": "api", 25 | "services": [ 26 | "HandlerService", 27 | "LoggerService" 28 | ] 29 | } 30 | 31 | self.translate = QCoreApplication.translate 32 | if (CaptainstreasureChest): 33 | self.treasureChest = CaptainstreasureChest 34 | else: 35 | self.treasureChest = treasureChest.treasureChest() 36 | 37 | def createapiTAB(self): 38 | self.groupBoxAPI = QGroupBox("API") 39 | self.groupBoxAPI.setCheckable(True) 40 | self.groupBoxAPI.setChecked(False) 41 | 42 | labelAPI = QLabel(self.translate("apiTAB","API's Tag: ")) 43 | self.lineEditTagName = QLineEdit("api") 44 | hboxApi = QHBoxLayout() 45 | hboxApi.addWidget(labelAPI) 46 | hboxApi.addWidget(self.lineEditTagName) 47 | hboxApi.addStretch() 48 | 49 | hboxServices = QHBoxLayout() 50 | labelServices = QLabel(self.translate("apiTAB", "Services: ")) 51 | self.lineEditServices = QLineEdit("HandlerService, LoggerService, StatsService") 52 | hboxServices.addWidget(labelServices) 53 | hboxServices.addWidget(self.lineEditServices) 54 | 55 | vbox = QVBoxLayout() 56 | vbox.addLayout(hboxApi) 57 | vbox.addLayout(hboxServices) 58 | vbox.addStretch() 59 | 60 | self.groupBoxAPI.setLayout(vbox) 61 | self.lineEditTagName.editingFinished.connect( 62 | lambda:self.treasureChest.setApitag([x.strip() for x in re.split(r"[,;]", self.lineEditTagName.text())])) 63 | 64 | if v2rayshellDebug: 65 | self.__debugBtn = QPushButton("__debugTest", self) 66 | vbox.addWidget(self.__debugBtn) 67 | self.__debugBtn.clicked.connect(self.__debugTest) 68 | 69 | return self.groupBoxAPI 70 | 71 | def settingAPITabFromJSONFile(self, apiJSONFile): 72 | if not apiJSONFile: 73 | apiJSONFile = {} 74 | self.groupBoxAPI.setChecked(False) 75 | else: 76 | self.groupBoxAPI.setChecked(True) 77 | 78 | try: 79 | apiJSONFile['tag'] 80 | except Exception: 81 | apiJSONFile['tag'] = 'api' 82 | 83 | try: 84 | apiJSONFile["services"] 85 | except Exception: 86 | apiJSONFile["services"] = "HandlerService, LoggerService, StatsService" 87 | 88 | self.lineEditServices.clear() 89 | self.lineEditTagName.clear() 90 | self.lineEditTagName.setText(apiJSONFile['tag']) 91 | try: 92 | if isinstance(apiJSONFile["services"], list): 93 | self.lineEditServices.setText(", ".join(apiJSONFile["services"])) 94 | else: 95 | self.lineEditServices.setText(apiJSONFile["services"]) 96 | except Exception: 97 | pass 98 | 99 | def createApiJSONFile(self): 100 | if self.groupBoxAPI: 101 | api = {} 102 | api["tag"] = self.lineEditTagName.text() 103 | api["services"] = [x.strip() for x in re.split(r"[,;]", self.lineEditServices.text())] 104 | return api 105 | return None 106 | 107 | def __debugTest(self): 108 | print(json.dumps(self.createApiJSONFile(), indent=4, sort_keys=False)) 109 | 110 | if __name__ == "__main__": 111 | app = QApplication(sys.argv) 112 | ex = apiTAB() 113 | v = QVBoxLayout() 114 | v.addWidget(ex.createapiTAB()) 115 | ex.setLayout(v) 116 | ex.setGeometry(200, 100, 400, 300) 117 | ex.show() 118 | sys.exit(app.exec_()) 119 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/cafeteriaTAB.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/inbound/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from bridgehouse.editMap import logTAB 4 | 5 | logbook = logTAB.logBook() 6 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/inbound/dokodemodoorPanel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QWidget, QLabel, QLineEdit, QSpinBox, 4 | QCheckBox, QGroupBox, QHBoxLayout, QVBoxLayout, 5 | QToolTip, QPushButton) 6 | from PyQt5.QtGui import QCursor 7 | from PyQt5.QtCore import QFileInfo, QCoreApplication 8 | import sys, re, platform 9 | 10 | v2rayshellDebug = False 11 | 12 | if __name__ == "__main__": 13 | v2rayshellDebug = True 14 | # this for debug test 15 | path = QFileInfo(sys.argv[0]) 16 | srcPath = path.absoluteFilePath().split("/") 17 | sys.path.append("/".join(srcPath[:-4])) 18 | 19 | from bridgehouse.editMap.inbound import logbook 20 | 21 | 22 | class DokodemodoorPanel(QWidget): 23 | 24 | def __init__(self): 25 | super().__init__() 26 | self.dokodemodoorJSONFile = { 27 | "address": "", 28 | "port": 443, 29 | "network": "", 30 | "timeout": 300, 31 | "followRedirect": False, 32 | "userLevel": 0 33 | } 34 | self.translate = QCoreApplication.translate 35 | 36 | def createDokodemodoorSettingPanel(self): 37 | labelAddress = QLabel( 38 | self.translate("DokodemodoorPanel", "Address: "), self) 39 | self.lineEditDokodemodoorAddress = QLineEdit() 40 | labelPort = QLabel( 41 | self.translate("DokodemodoorPanel", "Port: "), self) 42 | self.spinBoxDokodemodoorPort = QSpinBox() 43 | labelNetwork = QLabel( 44 | self.translate("DokodemodoorPanel", "Network: "), self) 45 | self.checkBoxDokodemodoorTCP = QCheckBox("TCP", self) 46 | self.checkBoxDokodemodoorUDP = QCheckBox("UDP", self) 47 | labelTimeout = QLabel( 48 | self.translate("DokodemodoorPanel", "Timeout: "), self) 49 | self.spinBoxDokodemodoorTimeout = QSpinBox() 50 | self.checkBoxDokodemodoorFollowRedirect = QCheckBox( 51 | self.translate("DokodemodoorPanel", "Follow Redirect"), self) 52 | 53 | labeluserLevel = QLabel( 54 | self.translate("DokodemodoorPanel", "User Level: ")) 55 | self.spinBoxDokodemodooruserLevel = QSpinBox() 56 | self.spinBoxDokodemodooruserLevel.setRange(0, 65535) 57 | self.spinBoxDokodemodooruserLevel.setValue(0) 58 | 59 | hboxuserLevel = QHBoxLayout() 60 | hboxuserLevel.addWidget(labeluserLevel) 61 | hboxuserLevel.addWidget(self.spinBoxDokodemodooruserLevel) 62 | hboxuserLevel.addStretch() 63 | 64 | self.spinBoxDokodemodoorPort.setRange(0, 65535) 65 | self.spinBoxDokodemodoorTimeout.setRange(0, 999) 66 | self.spinBoxDokodemodoorTimeout.setValue(300) 67 | self.spinBoxDokodemodoorPort.setValue(443) 68 | self.checkBoxDokodemodoorFollowRedirect.setChecked(False) 69 | 70 | hboxAdress = QHBoxLayout() 71 | hboxAdress.addWidget(labelAddress) 72 | hboxAdress.addWidget(self.lineEditDokodemodoorAddress) 73 | hboxAdress.addWidget(labelPort) 74 | hboxAdress.addWidget(self.spinBoxDokodemodoorPort) 75 | hboxAdress.addStretch() 76 | 77 | hboxNetwork = QHBoxLayout() 78 | hboxNetwork.addWidget(labelNetwork) 79 | hboxNetwork.addWidget(self.checkBoxDokodemodoorTCP) 80 | hboxNetwork.addWidget(self.checkBoxDokodemodoorUDP) 81 | hboxNetwork.addStretch() 82 | 83 | hboxTimeout = QHBoxLayout() 84 | hboxTimeout.addWidget(labelTimeout) 85 | hboxTimeout.addWidget(self.spinBoxDokodemodoorTimeout) 86 | hboxTimeout.addWidget(self.checkBoxDokodemodoorFollowRedirect) 87 | hboxTimeout.addStretch() 88 | 89 | groupBoxDokodemodoor = QGroupBox( 90 | self.translate("DokodemodoorPanel", "Dokodemo-door"), self) 91 | vboxDokodemodoor = QVBoxLayout() 92 | vboxDokodemodoor.addLayout(hboxAdress) 93 | vboxDokodemodoor.addLayout(hboxTimeout) 94 | vboxDokodemodoor.addLayout(hboxNetwork) 95 | vboxDokodemodoor.addLayout(hboxuserLevel) 96 | 97 | groupBoxDokodemodoor.setLayout(vboxDokodemodoor) 98 | 99 | if (v2rayshellDebug): 100 | self.__debugBtn = QPushButton("__debugTest", self) 101 | self.__debugBtn.clicked.connect(self.__debugTest) 102 | vboxDokodemodoor.addWidget(self.__debugBtn) 103 | self.settingdokodemodoorPanelFromJSONFile(self.dokodemodoorJSONFile, True) 104 | 105 | self.createDokodemodoorSignals() 106 | return groupBoxDokodemodoor 107 | 108 | def createDokodemodoorSignals(self): 109 | self.checkBoxDokodemodoorFollowRedirect.clicked.connect(self.oncheckBoxDokodemodoorFollowRedirect) 110 | 111 | def oncheckBoxDokodemodoorFollowRedirect(self): 112 | if (platform.system() == "Linux"): 113 | self.checkBoxDokodemodoorFollowRedirect.setCheckable(True) 114 | else: 115 | QToolTip.showText(QCursor.pos(), 116 | self.translate("DokodemodoorPanel", "Only suport Linux System."), 117 | self.checkBoxDokodemodoorFollowRedirect) 118 | 119 | def settingdokodemodoorPanelFromJSONFile(self, dokodemodoorJSONFile={}, openFromJSONFile=False): 120 | logbook.setisOpenJSONFile(openFromJSONFile) 121 | 122 | if (not dokodemodoorJSONFile): dokodemodoorJSONFile = {} 123 | 124 | try: 125 | dokodemodoorJSONFile["address"] 126 | except KeyError as e: 127 | logbook.writeLog("dokodemodoor", "KeyError", e) 128 | dokodemodoorJSONFile["address"] = "" 129 | 130 | try: 131 | dokodemodoorJSONFile["port"] 132 | except KeyError as e: 133 | logbook.writeLog("dokodemodoor", "KeyError", e) 134 | dokodemodoorJSONFile["port"] = 443 135 | 136 | try: 137 | dokodemodoorJSONFile["network"] 138 | except KeyError as e: 139 | logbook.writeLog("dokodemodoor", "KeyError", e) 140 | dokodemodoorJSONFile["network"] = "" 141 | 142 | try: 143 | dokodemodoorJSONFile["timeout"] 144 | except KeyError as e: 145 | logbook.writeLog("dokodemodoor", "KeyError", e) 146 | dokodemodoorJSONFile["timeout"] = 300 147 | 148 | try: 149 | dokodemodoorJSONFile["followRedirect"] 150 | except KeyError as e: 151 | logbook.writeLog("dokodemodoor", "KeyError", e) 152 | dokodemodoorJSONFile["followRedirect"] = False 153 | 154 | try: 155 | dokodemodoorJSONFile["userLevel"] 156 | except KeyError as e: 157 | logbook.writeLog("dokodemodoor", "KeyError", e) 158 | dokodemodoorJSONFile["userLevel"] = 0 159 | 160 | self.lineEditDokodemodoorAddress.setText(str(dokodemodoorJSONFile["address"])) 161 | try: 162 | self.spinBoxDokodemodoorPort.setValue(int(dokodemodoorJSONFile["port"])) 163 | except (ValueError, TypeError) as e: 164 | logbook.writeLog("dokodemodoor", "ValueError or TypeError", e) 165 | self.spinBoxDokodemodoorPort.setValue(443) 166 | 167 | try: 168 | self.spinBoxDokodemodoorTimeout.setValue(int(dokodemodoorJSONFile["timeout"])) 169 | except (ValueError, TypeError) as e: 170 | logbook.writeLog("dokodemodoor", "ValueError or TypeError", e) 171 | self.spinBoxDokodemodoorTimeout.setValue(300) 172 | self.checkBoxDokodemodoorFollowRedirect.setChecked(bool(dokodemodoorJSONFile["followRedirect"])) 173 | 174 | try: 175 | self.spinBoxDokodemodooruserLevel.setValue(int(dokodemodoorJSONFile["userLevel"])) 176 | except (ValueError, TypeError) as e: 177 | logbook.writeLog("dokodemodoor", "ValueError or TypeError", e) 178 | self.spinBoxDokodemodooruserLevel.setValue(0) 179 | 180 | try: 181 | self.treasureChest.addLevel(self.spinBoxDokodemodooruserLevel.value()) 182 | except Exception:pass 183 | 184 | udp = re.search("udp", dokodemodoorJSONFile["network"].lower()) 185 | tcp = re.search("tcp", dokodemodoorJSONFile["network"].lower()) 186 | if (bool(tcp)):self.checkBoxDokodemodoorTCP.setChecked(True) 187 | if (bool(udp)):self.checkBoxDokodemodoorUDP.setChecked(True) 188 | 189 | def createDokodemodorrJSONFile(self): 190 | dokodemodoorJSONFile = {} 191 | tcp = self.checkBoxDokodemodoorTCP.isChecked() 192 | udp = self.checkBoxDokodemodoorUDP.isChecked() 193 | port = "" # defaut 194 | if (tcp): 195 | port = "tcp" 196 | if (udp): 197 | port = "udp" 198 | if (tcp and udp): 199 | port = "tcp,udp" 200 | dokodemodoorJSONFile["address"] = self.lineEditDokodemodoorAddress.text() 201 | dokodemodoorJSONFile["port"] = self.spinBoxDokodemodoorPort.value() 202 | dokodemodoorJSONFile["network"] = port 203 | dokodemodoorJSONFile["timeout"] = self.spinBoxDokodemodoorTimeout.value() 204 | dokodemodoorJSONFile["followRedirect"] = self.checkBoxDokodemodoorFollowRedirect.isChecked() 205 | dokodemodoorJSONFile["userLevel"] = self.spinBoxDokodemodooruserLevel.value() 206 | 207 | try: 208 | self.treasureChest.addLevel(self.spinBoxDokodemodooruserLevel.value()) 209 | except Exception: 210 | pass 211 | return dokodemodoorJSONFile 212 | 213 | def cleardokodemodoorPanel(self): 214 | self.lineEditDokodemodoorAddress.clear() 215 | self.spinBoxDokodemodoorPort.setValue(443) 216 | self.spinBoxDokodemodoorTimeout.setValue(300) 217 | self.checkBoxDokodemodoorFollowRedirect.setChecked(False) 218 | self.spinBoxDokodemodooruserLevel.setValue(0) 219 | self.checkBoxDokodemodoorTCP.setChecked(False) 220 | self.checkBoxDokodemodoorUDP.setChecked(False) 221 | 222 | def __debugTest(self): 223 | import json 224 | print(json.dumps(self.createDokodemodorrJSONFile(), indent=4, sort_keys=False)) 225 | 226 | 227 | if __name__ == "__main__": 228 | from PyQt5.QtWidgets import QApplication 229 | app = QApplication(sys.argv) 230 | ex = DokodemodoorPanel() 231 | ex.createDokodemodoorSettingPanel() 232 | ex.setGeometry(300, 300, 680, 230) 233 | ex.show() 234 | sys.exit(app.exec_()) 235 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/inbound/httpPanel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QWidget, QLabel, QSpinBox, 4 | QHBoxLayout, QGroupBox, QPushButton, 5 | QTableWidget, QAbstractItemView, QButtonGroup, 6 | QVBoxLayout, QTableWidgetItem, 7 | QCheckBox) 8 | from PyQt5.QtCore import QFileInfo, QCoreApplication 9 | 10 | import sys, copy 11 | 12 | v2rayshellDebug = False 13 | 14 | if __name__ == "__main__": 15 | v2rayshellDebug = True 16 | # this for debug test 17 | path = QFileInfo(sys.argv[0]) 18 | srcPath = path.absoluteFilePath().split("/") 19 | sys.path.append("/".join(srcPath[:-4])) 20 | 21 | from bridgehouse.editMap.inbound import logbook 22 | 23 | 24 | class HttpPanel(QWidget): 25 | 26 | def __init__(self): 27 | super().__init__() 28 | self.httpJSONFile = { 29 | "timeout": 300, 30 | "accounts": [ 31 | { 32 | "user": "my-username", 33 | "pass": "my-password" 34 | } 35 | ], 36 | "allowTransparent": False, 37 | "userLevel": 0 38 | } 39 | self.translate = QCoreApplication.translate 40 | 41 | self.labelUserHttpPanel = (self.translate("HttpPanel", "User"), 42 | self.translate("HttpPanel", "Password")) 43 | 44 | def createHttpSettingPanel(self): 45 | labelTimeout = QLabel(self.translate("HttpPanel", "Timeout: "), self) 46 | self.spinBoxHttpTimeout = QSpinBox() 47 | 48 | self.spinBoxHttpTimeout.setRange(0, 999) 49 | self.spinBoxHttpTimeout.setValue(300) 50 | 51 | self.checkBoxallowTransparent = QCheckBox( 52 | self.translate("HttpPanel", "Allow Transparent"), self) 53 | self.checkBoxallowTransparent.setChecked(False) 54 | 55 | hboxTimeout = QHBoxLayout() 56 | hboxTimeout.addWidget(labelTimeout) 57 | hboxTimeout.addWidget(self.spinBoxHttpTimeout) 58 | hboxTimeout.addStretch() 59 | 60 | labeluserLevel = QLabel(self.translate("HttpPanel", "User Level: ")) 61 | self.spinBoxHttpuserLevel = QSpinBox() 62 | self.spinBoxHttpuserLevel.setRange(0, 65535) 63 | self.spinBoxHttpuserLevel.setValue(0) 64 | 65 | hboxuserLevel = QHBoxLayout() 66 | hboxuserLevel.addWidget(labeluserLevel) 67 | hboxuserLevel.addWidget(self.spinBoxHttpuserLevel) 68 | hboxuserLevel.addStretch() 69 | 70 | btnHttpNew = QPushButton( 71 | self.translate("HttpPanel", "New"), self) 72 | btnHttpDelete = QPushButton( 73 | self.translate("HttpPanel", "Delete"), self) 74 | 75 | self.groupButtonHttp = QButtonGroup() 76 | self.groupButtonHttp.addButton(btnHttpNew) 77 | self.groupButtonHttp.addButton(btnHttpDelete) 78 | 79 | vboxButtonHttp = QVBoxLayout() 80 | vboxButtonHttp.addWidget(QLabel()) 81 | vboxButtonHttp.addWidget(QLabel()) 82 | vboxButtonHttp.addWidget(QLabel()) 83 | vboxButtonHttp.addWidget(QLabel()) 84 | vboxButtonHttp.addWidget(btnHttpNew) 85 | vboxButtonHttp.addWidget(btnHttpDelete) 86 | 87 | self.tableWidgetHttp = QTableWidget() 88 | self.tableWidgetHttp.setColumnCount(2) 89 | self.tableWidgetHttp.adjustSize() 90 | self.tableWidgetHttp.setHorizontalHeaderLabels(self.labelUserHttpPanel) 91 | self.tableWidgetHttp.setSelectionMode(QAbstractItemView.SingleSelection) 92 | self.tableWidgetHttp.setSelectionBehavior(QAbstractItemView.SelectRows) 93 | #self.tableWidgetHttp.setEditTriggers(QAbstractItemView.NoEditTriggers) 94 | self.tableWidgetHttp.horizontalHeader().setStretchLastSection(True) 95 | 96 | hboxHttpTableWidget = QHBoxLayout() 97 | hboxHttpTableWidget.addWidget(self.tableWidgetHttp) 98 | hboxHttpTableWidget.addLayout(vboxButtonHttp) 99 | 100 | vboxHttpTableWidget = QVBoxLayout() 101 | vboxHttpTableWidget.addLayout(hboxHttpTableWidget) 102 | 103 | self.groupBoxHttpAuth = QGroupBox( 104 | self.translate("HttpPanel", "Requires Authentication: "), self) 105 | self.groupBoxHttpAuth.setCheckable(True) 106 | self.groupBoxHttpAuth.setChecked(False) 107 | self.groupBoxHttpAuth.setLayout(vboxHttpTableWidget) 108 | 109 | vboxHttp = QVBoxLayout() 110 | vboxHttp.addLayout(hboxTimeout) 111 | vboxHttp.addLayout(hboxuserLevel) 112 | vboxHttp.addWidget(self.checkBoxallowTransparent) 113 | vboxHttp.addWidget(self.groupBoxHttpAuth) 114 | 115 | self.createHttpPanelSignals() 116 | 117 | if (v2rayshellDebug): 118 | self.__debugBtn = QPushButton("__debugTest", self) 119 | self.__debugBtn.clicked.connect(self.__debugTest) 120 | vboxHttp.addWidget(self.__debugBtn) 121 | self.settinghttpPanelFromJSONFile(self.httpJSONFile, True) 122 | 123 | groupBoxHttp = QGroupBox(self.translate("HttpPanel", "Http"), self) 124 | groupBoxHttp.setLayout(vboxHttp) 125 | 126 | return groupBoxHttp 127 | 128 | def createHttpPanelSignals(self): 129 | self.groupButtonHttp.buttonClicked.connect(self.ongroupButtonHttp) 130 | 131 | def ongroupButtonHttp(self, e): 132 | if (e.text() == self.translate("HttpPanel", "Delete")): 133 | self.onbtnHttpDelete() 134 | if (e.text() == self.translate("HttpPanel", "New")): 135 | self.onbtnHttpNew() 136 | 137 | def onbtnHttpNew(self): 138 | row = self.tableWidgetHttp.rowCount() 139 | if (not row): 140 | self.tableWidgetHttp.setRowCount(row + 1) 141 | else: 142 | user = self.tableWidgetHttp.item(row-1, 0) 143 | password = self.tableWidgetHttp.item(row-1, 1) 144 | if (user and password): 145 | self.tableWidgetHttp.setRowCount(row + 1) 146 | 147 | def onbtnHttpDelete(self): 148 | if (not self.tableWidgetHttp.rowCount()): return 149 | row = self.tableWidgetHttp.currentRow() 150 | self.tableWidgetHttp.removeRow(row) 151 | 152 | def settinghttpPanelFromJSONFile(self, httpJSONFile={}, openFromJSONFile=False): 153 | logbook.setisOpenJSONFile(openFromJSONFile) 154 | self.tableWidgetHttp.setRowCount(0) 155 | 156 | if (not httpJSONFile): httpJSONFile = {} 157 | 158 | try: 159 | httpJSONFile["timeout"] 160 | except KeyError as e: 161 | logbook.writeLog("http", "KeyError", e) 162 | httpJSONFile["timeout"] = 300 163 | 164 | try: 165 | httpJSONFile["allowTransparent"] 166 | except KeyError as e: 167 | logbook.writeLog("http", "KeyError", e) 168 | httpJSONFile["allowTransparent"] = False 169 | 170 | try: 171 | httpJSONFile["accounts"] 172 | except KeyError as e: 173 | logbook.writeLog("http", "KeyError", e) 174 | httpJSONFile["accounts"] = {} 175 | 176 | try: 177 | httpJSONFile["userLevel"] 178 | except KeyError as e: 179 | logbook.writeLog("http", "KeyError", e) 180 | httpJSONFile["userLevel"] = 0 181 | 182 | try: 183 | self.spinBoxHttpTimeout.setValue(int(self.httpJSONFile["timeout"])) 184 | except (ValueError, TypeError) as e: 185 | logbook.writeLog("http", "ValueError or TypeError", e) 186 | self.spinBoxHttpTimeout.setValue(300) 187 | 188 | try: 189 | self.checkBoxallowTransparent.setChecked(bool(httpJSONFile["allowTransparent"])) 190 | except (ValueError, TypeError) as e: 191 | logbook.writeLog("http", "ValueError or TypeError", e) 192 | self.checkBoxallowTransparent.setChecked(False) 193 | 194 | try: 195 | self.spinBoxHttpuserLevel.setValue(int(httpJSONFile["userLevel"])) 196 | except (ValueError, TypeError) as e: 197 | logbook.writeLog("http", "ValueError or TypeError", e) 198 | self.spinBoxHttpuserLevel.setValue(0) 199 | try: 200 | self.treasureChest.addLevel(self.spinBoxHttpuserLevel.value()) 201 | except Exception:pass 202 | 203 | accountsNumber = len(httpJSONFile["accounts"]) 204 | if (accountsNumber): 205 | accounts = httpJSONFile["accounts"] 206 | self.tableWidgetHttp.setRowCount(accountsNumber) 207 | self.groupBoxHttpAuth.setChecked(True) 208 | for i in range(accountsNumber): 209 | try:user = accounts[i]["user"] 210 | except Exception: user = "" 211 | try:password = accounts[i]["pass"] 212 | except Exception: password = "" 213 | 214 | self.tableWidgetHttp.setItem(i, 0, QTableWidgetItem(str(user))) 215 | self.tableWidgetHttp.setItem(i, 1, QTableWidgetItem(str(password))) 216 | self.tableWidgetHttp.resizeColumnsToContents() 217 | else:self.groupBoxHttpAuth.setChecked(False) 218 | 219 | def createHttpJSONFile(self): 220 | httpJSONFile = {} 221 | httpJSONFile["timeout"] = self.spinBoxHttpTimeout.value() 222 | 223 | if (self.groupBoxHttpAuth.isChecked()): 224 | httpJSONFile["accounts"] = [] 225 | accountsNumber = self.tableWidgetHttp.rowCount() 226 | if (accountsNumber): 227 | account = {} 228 | for i in range(accountsNumber): 229 | account["user"] = self.tableWidgetHttp.item(i, 0).text() 230 | account["pass"] = self.tableWidgetHttp.item(i, 1).text() 231 | httpJSONFile["accounts"].append(copy.deepcopy(account)) 232 | else:del httpJSONFile["accounts"] 233 | 234 | httpJSONFile["allowTransparent"] = self.checkBoxallowTransparent.isChecked() 235 | httpJSONFile["userLevel"] = self.spinBoxHttpuserLevel.value() 236 | 237 | try:self.treasureChest.addLevel(self.spinBoxHttpuserLevel.value()) 238 | except Exception:pass 239 | 240 | return httpJSONFile 241 | 242 | def clearinboundHttpPanel(self): 243 | self.tableWidgetHttp.setRowCount(0) 244 | self.spinBoxHttpTimeout.setValue(300) 245 | self.spinBoxHttpuserLevel.setValue(0) 246 | self.checkBoxallowTransparent.setChecked(False) 247 | self.groupBoxHttpAuth.setChecked(False) 248 | self.tableWidgetHttp.setRowCount(0) 249 | 250 | def __debugTest(self): 251 | import json 252 | print(json.dumps(self.createHttpJSONFile(), indent=4, sort_keys=False)) 253 | 254 | 255 | if __name__ == "__main__": 256 | from PyQt5.QtWidgets import QApplication 257 | app = QApplication(sys.argv) 258 | ex = HttpPanel() 259 | ex.createHttpSettingPanel() 260 | ex.setGeometry(300, 300, 600, 420) 261 | ex.show() 262 | sys.exit(app.exec_()) 263 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/inbound/mtPanel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QWidget, QLabel, QTableView, 4 | QHBoxLayout, QGroupBox, QPushButton, 5 | QTableWidget, QAbstractItemView, QButtonGroup, 6 | QVBoxLayout, QTableWidgetItem) 7 | 8 | from PyQt5.QtCore import QFileInfo, QCoreApplication 9 | from PyQt5.Qt import QStandardItemModel, QModelIndex 10 | 11 | import sys, copy 12 | 13 | v2rayshellDebug = False 14 | 15 | if __name__ == "__main__": 16 | v2rayshellDebug = True 17 | # this for debug test 18 | path = QFileInfo(sys.argv[0]) 19 | srcPath = path.absoluteFilePath().split("/") 20 | sys.path.append("/".join(srcPath[:-4])) 21 | 22 | from bridgehouse.editMap.toolbox import toolbox 23 | 24 | class InboundMtPanel(QWidget): 25 | def __init__(self): 26 | super().__init__() 27 | self.mtJSONFile = { 28 | "users": [{ 29 | "email": "love@v2ray.com", 30 | "level": 0, 31 | "secret": "b0cbcef5a486d9636472ac27f8e11a9d" 32 | }] 33 | } 34 | self.translate = QCoreApplication.translate 35 | 36 | self.labelUsermtPanel = (self.translate("InboundMtPanel", "Email"), 37 | self.translate("InboundMtPanel", "Level"), 38 | self.translate("InboundMtPanel", "Password")) 39 | 40 | def createmtSettingPanel(self): 41 | UUIDdelegate = toolbox.UUIDLineEditDelegate( 42 | self.translate("InboundMtPanel", "Gerate UUID")) 43 | 44 | self.model = QStandardItemModel(0, 3) 45 | self.tableViewInMtUser = tableViewUser = QTableView(self) 46 | tableViewUser.setModel(self.model) 47 | self.model.setHorizontalHeaderLabels(self.labelUsermtPanel) 48 | tableViewUser.setSelectionMode(QAbstractItemView.SingleSelection) 49 | tableViewUser.setSelectionBehavior(QAbstractItemView.SelectRows) 50 | 51 | tableViewUser.setItemDelegateForColumn(2, UUIDdelegate) 52 | 53 | self.btnInMtNew = QPushButton( 54 | self.translate("InboundMtPanel", "New"), self) 55 | self.btnInMtDelete = QPushButton( 56 | self.translate("InboundMtPanel", "Delete"), self) 57 | 58 | self.btnGroup = QButtonGroup() 59 | self.btnGroup.addButton(self.btnInMtNew) 60 | self.btnGroup.addButton(self.btnInMtDelete) 61 | 62 | vboxBtn = QVBoxLayout() 63 | vboxBtn.addWidget(QLabel()) 64 | vboxBtn.addWidget(QLabel()) 65 | vboxBtn.addWidget(QLabel()) 66 | vboxBtn.addWidget(self.btnInMtNew) 67 | vboxBtn.addWidget(self.btnInMtDelete) 68 | 69 | hbox = QHBoxLayout() 70 | hbox.addWidget(tableViewUser) 71 | hbox.addLayout(vboxBtn) 72 | 73 | self.groupInboudnMtPanel = QGroupBox( 74 | self.translate("InboundMtPanel", "MTProto"), self) 75 | self.groupInboudnMtPanel.setLayout(hbox) 76 | 77 | self.createInboundMtPanelSignals() 78 | 79 | if (v2rayshellDebug): 80 | self.__debugBtn = QPushButton("__debugTest", self) 81 | vboxBtn.addWidget(self.__debugBtn) 82 | self.__debugBtn.clicked.connect(self.__debugTest) 83 | self.settingInboundMtPanelFromJSONFile(self.mtJSONFile, True) 84 | 85 | return self.groupInboudnMtPanel 86 | 87 | def createInboundMtPanelSignals(self): 88 | self.btnGroup.buttonClicked.connect(self.onInboundMtbtnGroup) 89 | 90 | def onInboundMtbtnGroup(self, e): 91 | if e.text() == self.translate("InboundMtPanel", "New"): 92 | self.onbtnInboundMtNew() 93 | if e.text() == self.translate("InboundMtPanel", "Delete"): 94 | self.onbtnInboundMtDelete() 95 | 96 | def onbtnInboundMtNew(self): 97 | row = self.model.rowCount() 98 | if not row: 99 | self.model.setRowCount(row+1) 100 | self.setRowData(row) 101 | else: 102 | if (self.model.index(row-1, 2, QModelIndex()).data()): 103 | self.model.setRowCount(row+1) 104 | self.setRowData(row) 105 | 106 | def setRowData(self, row, email=None, level=None, secret=None): 107 | indexEmail = self.model.index(row, 0, QModelIndex()) 108 | indexLevel = self.model.index(row, 1, QModelIndex()) 109 | indexSecret = self.model.index(row, 2, QModelIndex()) 110 | 111 | self.model.setData(indexEmail, "" if not email else email) 112 | self.model.setData(indexLevel, 0 if not level else level) 113 | self.model.setData(indexSecret, 0 if not secret else secret) 114 | 115 | try: 116 | if level: self.treasureChest.addLevel(int(level)) 117 | if email: self.treasureChest.addEmail(str(email)) 118 | except Exception: pass 119 | 120 | def onbtnInboundMtDelete(self): 121 | row = self.tableViewInMtUser.selectedIndexes() 122 | if row: 123 | self.model.removeRow(row[0].row()) 124 | 125 | def settingInboundMtPanelFromJSONFile(self, inboundMtJSONFile=None, openFromJSONFile=False): 126 | if not inboundMtJSONFile: 127 | inboundMtJSONFile = {} 128 | 129 | try: 130 | inboundMtJSONFile['users'] 131 | except KeyError: 132 | inboundMtJSONFile['users'] = list() 133 | 134 | if inboundMtJSONFile['users']: 135 | for row, user in enumerate(inboundMtJSONFile['users']): 136 | email = level = secret = None 137 | try: 138 | email = user['email'] 139 | except KeyError: 140 | pass 141 | try: 142 | level = user['level'] 143 | except KeyError: 144 | pass 145 | try: 146 | secret = user['secret'] 147 | except KeyError: 148 | pass 149 | self.model.setRowCount(row+1) 150 | self.setRowData(row, email, level, secret) 151 | 152 | def createInboundMtJSONFile(self): 153 | inboundMtJSONFile = {} 154 | inboundMtJSONFile['users'] = list() 155 | 156 | usersNumber = self.model.rowCount() 157 | for row in range(usersNumber): 158 | userData = {} 159 | userData['email'] = self.model.index(row, 0, QModelIndex()).data() 160 | userData['level'] = self.model.index(row, 1, QModelIndex()).data() 161 | userData['secret'] = ''.join(str(self.model.index(row, 2, QModelIndex()).data()).split('-')) 162 | inboundMtJSONFile['users'].append(copy.deepcopy(userData)) 163 | 164 | return inboundMtJSONFile 165 | 166 | def clearinboundMtPanel(self): 167 | self.model.setRowCount(0) 168 | 169 | def __debugTest(self): 170 | import json 171 | print(json.dumps(self.createInboundMtJSONFile(), indent=4, sort_keys=False)) 172 | 173 | 174 | if __name__ == "__main__": 175 | from PyQt5.QtWidgets import QApplication 176 | app = QApplication(sys.argv) 177 | ex = InboundMtPanel() 178 | ex.createmtSettingPanel() 179 | ex.setGeometry(300, 300, 600, 420) 180 | ex.show() 181 | sys.exit(app.exec_()) 182 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/inbound/shadowsocksPanel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QWidget, QLabel, QLineEdit, QComboBox, QSpinBox, 4 | QCheckBox, QGridLayout, QVBoxLayout, 5 | QGroupBox, QPushButton, QHBoxLayout) 6 | from PyQt5.QtCore import QFileInfo, QCoreApplication 7 | 8 | import sys 9 | 10 | v2rayshellDebug = False 11 | 12 | if __name__ == "__main__": 13 | v2rayshellDebug = True 14 | # this for debug test 15 | path = QFileInfo(sys.argv[0]) 16 | srcPath = path.absoluteFilePath().split("/") 17 | sys.path.append("/".join(srcPath[:-4])) 18 | 19 | from bridgehouse.editMap.inbound import logbook 20 | 21 | 22 | class InboundShadowsocksPanel(QWidget): 23 | 24 | def __init__(self): 25 | super().__init__() 26 | self.inboundShadowsocksJSONFile = { 27 | "email": "", 28 | "method": "", 29 | "password": "", 30 | "udp": False, 31 | "level": 1, 32 | "ota": True, 33 | "network": "tcp,udp" 34 | } 35 | self.listMethodShadowsocksPanel = "aes-256-cfb", "aes-128-cfb", "chacha20", "chacha20-ietf", "aes-256-gcm", "aes-128-gcm", "chacha20-poly1305" 36 | self.translate = QCoreApplication.translate 37 | 38 | def createShadowsocksSettingPanel(self): 39 | labelEmail = QLabel( 40 | self.translate("InboundShadowsocksPanel", "Email: "), self) 41 | self.lineEditInboundShadowsocksEmail = QLineEdit() 42 | labelMethod = QLabel( 43 | self.translate("InboundShadowsocksPanel", "Method: "), self) 44 | self.comboBoxInboundShadowsocksMethod = QComboBox() 45 | labelPassowrd = QLabel( 46 | self.translate("InboundShadowsocksPanel", "Password: "), self) 47 | self.lineEditInboundShadowsocksPassowrd = QLineEdit() 48 | self.checkBoxInboundShadowsocksUDP = QCheckBox( 49 | self.translate("InboundShadowsocksPanel", "open UDP forwarding"), self) 50 | labelLevel = QLabel( 51 | self.translate("InboundShadowsocksPanel", "User Level: "), self) 52 | self.spinBoxInboundShadowsocksLevel = QSpinBox() 53 | self.checkBoxInboundShadowsocksOTA = QCheckBox( 54 | self.translate("InboundShadowsocksPanel", "One Time Auth (OTA)"), self) 55 | labelNetwork = QLabel(self.translate("InboundShadowsocksPanel", "Network: "), self) 56 | self.checkBoxNewtworkTCP = QCheckBox("TCP", self) 57 | self.checkBoxNewtworkUDP = QCheckBox("UDP", self) 58 | hboxNetwork = QHBoxLayout() 59 | hboxNetwork.addWidget(self.checkBoxNewtworkTCP) 60 | hboxNetwork.addWidget(self.checkBoxNewtworkUDP) 61 | hboxNetwork.addStretch() 62 | 63 | self.comboBoxInboundShadowsocksMethod.addItems(self.listMethodShadowsocksPanel) 64 | self.checkBoxInboundShadowsocksUDP.setChecked(False) 65 | self.spinBoxInboundShadowsocksLevel.setRange(0, 65535) 66 | self.spinBoxInboundShadowsocksLevel.setValue(0) 67 | self.checkBoxNewtworkTCP.setChecked(True) 68 | self.checkBoxNewtworkUDP.setChecked(False) 69 | self.checkBoxInboundShadowsocksUDP.setVisible(False) 70 | self.checkBoxInboundShadowsocksUDP.setChecked(False) 71 | 72 | gridBoxInboundShadowsocks = QGridLayout(self) 73 | gridBoxInboundShadowsocks.addWidget(labelEmail, 0, 0) 74 | gridBoxInboundShadowsocks.addWidget(self.lineEditInboundShadowsocksEmail, 0, 1) 75 | gridBoxInboundShadowsocks.addWidget(labelMethod, 1, 0) 76 | gridBoxInboundShadowsocks.addWidget(self.comboBoxInboundShadowsocksMethod, 1, 1) 77 | gridBoxInboundShadowsocks.addWidget(labelPassowrd, 2, 0) 78 | gridBoxInboundShadowsocks.addWidget(self.lineEditInboundShadowsocksPassowrd, 2, 1) 79 | gridBoxInboundShadowsocks.addWidget(labelLevel, 3, 0) 80 | gridBoxInboundShadowsocks.addWidget(labelNetwork, 6, 0) 81 | gridBoxInboundShadowsocks.addLayout(hboxNetwork, 6, 1) 82 | 83 | hboxLevel = QHBoxLayout() 84 | hboxLevel.addWidget(self.spinBoxInboundShadowsocksLevel) 85 | hboxLevel.addStretch() 86 | gridBoxInboundShadowsocks.addLayout(hboxLevel, 3, 1) 87 | 88 | gridBoxInboundShadowsocks.addWidget(self.checkBoxInboundShadowsocksUDP, 4, 0) 89 | gridBoxInboundShadowsocks.addWidget(self.checkBoxInboundShadowsocksOTA, 5, 0) 90 | 91 | if (v2rayshellDebug): 92 | self.__debugBtn = QPushButton("__debugTest", self) 93 | gridBoxInboundShadowsocks.addWidget(self.__debugBtn, 7, 0) 94 | self.__debugBtn.clicked.connect(self.__debugTest) 95 | self.settingInboundShadowsocksPanelFromJSONFile(self.inboundShadowsocksJSONFile, True) 96 | 97 | groupBoxInboundShadowsocks = QGroupBox( 98 | self.translate("InboundShadowsocksPanel", "Shadowsocks"), self) 99 | groupBoxInboundShadowsocks.setLayout(gridBoxInboundShadowsocks) 100 | 101 | self.createShadowsocksPanelSignals() 102 | 103 | return groupBoxInboundShadowsocks 104 | 105 | def createShadowsocksPanelSignals(self): 106 | pass 107 | 108 | def settingInboundShadowsocksPanelFromJSONFile(self, inboundShadowsocksJSONFile=None, openFromJSONFile=False): 109 | logbook.setisOpenJSONFile(openFromJSONFile) 110 | 111 | if (not inboundShadowsocksJSONFile): inboundShadowsocksJSONFile = {} 112 | 113 | try: 114 | inboundShadowsocksJSONFile["email"] 115 | except KeyError as e: 116 | logbook.writeLog("InboundShadowsocks", "KeyError", e) 117 | inboundShadowsocksJSONFile["email"] = "" 118 | try: 119 | inboundShadowsocksJSONFile["method"] 120 | except KeyError as e: 121 | logbook.writeLog("InboundShadowsocks", "KeyError", e) 122 | inboundShadowsocksJSONFile["method"] = "" 123 | 124 | try: 125 | inboundShadowsocksJSONFile["password"] 126 | except KeyError as e: 127 | logbook.writeLog("InboundShadowsocks", "KeyError", e) 128 | inboundShadowsocksJSONFile["password"] = "" 129 | 130 | try: 131 | inboundShadowsocksJSONFile["udp"] 132 | except KeyError as e: 133 | logbook.writeLog("InboundShadowsocks", "KeyError", e) 134 | inboundShadowsocksJSONFile["udp"] = False 135 | 136 | try: 137 | inboundShadowsocksJSONFile["level"] 138 | except KeyError as e: 139 | logbook.writeLog("InboundShadowsocks", "KeyError", e) 140 | inboundShadowsocksJSONFile["level"] = 1 141 | 142 | try: 143 | inboundShadowsocksJSONFile["ota"] 144 | except KeyError as e: 145 | logbook.writeLog("InboundShadowsocks", "KeyError", e) 146 | inboundShadowsocksJSONFile["ota"] = True 147 | 148 | try: 149 | inboundShadowsocksJSONFile["network"] 150 | except KeyError as e: 151 | logbook.writeLog("InboundShadowsocks", "KeyError", e) 152 | inboundShadowsocksJSONFile["network"] = "tcp" 153 | 154 | self.lineEditInboundShadowsocksEmail.setText(str(inboundShadowsocksJSONFile["email"])) 155 | self.comboBoxInboundShadowsocksMethod.setCurrentText(str(inboundShadowsocksJSONFile["method"])) 156 | self.lineEditInboundShadowsocksPassowrd.setText(str(inboundShadowsocksJSONFile["password"])) 157 | self.checkBoxInboundShadowsocksUDP.setChecked(bool(inboundShadowsocksJSONFile["udp"])) 158 | self.checkBoxInboundShadowsocksOTA.setChecked(bool(inboundShadowsocksJSONFile["ota"])) 159 | 160 | try: 161 | self.spinBoxInboundShadowsocksLevel.setValue(int(inboundShadowsocksJSONFile["level"])) 162 | except (TypeError, ValueError) as e: 163 | logbook.writeLog("InboundShadowsocks", "KeyError", e) 164 | self.spinBoxInboundShadowsocksLevel.setValue(1) 165 | 166 | try: 167 | self.treasureChest.addLevel(self.spinBoxInboundShadowsocksLevel.value()) 168 | self.treasureChest.addEmail(self.lineEditInboundShadowsocksEmail.text()) 169 | except Exception: 170 | pass 171 | 172 | network = [x.strip() for x in str(inboundShadowsocksJSONFile["network"]).split(",")] 173 | 174 | if (network): 175 | if "tcp" in network: 176 | self.checkBoxNewtworkTCP.setChecked(True) 177 | else: 178 | self.checkBoxNewtworkTCP.setChecked(False) 179 | if "udp" in network: 180 | self.checkBoxNewtworkUDP.setChecked(True) 181 | else: 182 | self.checkBoxNewtworkUDP.setChecked(False) 183 | 184 | def createInboundShadowsocksJSONFile(self): 185 | inboundShadowsocksJSONFile = {} 186 | inboundShadowsocksJSONFile["email"] = self.lineEditInboundShadowsocksEmail.text() 187 | inboundShadowsocksJSONFile["method"] = self.comboBoxInboundShadowsocksMethod.currentText() 188 | inboundShadowsocksJSONFile["password"] = self.lineEditInboundShadowsocksPassowrd.text() 189 | inboundShadowsocksJSONFile["udp"] = self.checkBoxInboundShadowsocksUDP.isChecked() 190 | inboundShadowsocksJSONFile["level"] = self.spinBoxInboundShadowsocksLevel.value() 191 | inboundShadowsocksJSONFile["ota"] = self.checkBoxInboundShadowsocksOTA.isChecked() 192 | 193 | network = None 194 | tcp = "tcp" if self.checkBoxNewtworkTCP.isChecked() else None 195 | udp = "udp" if self.checkBoxInboundShadowsocksUDP.isChecked() else None 196 | udp = "udp" if self.checkBoxNewtworkUDP.isChecked() else None 197 | 198 | if (not tcp and udp): 199 | network = udp 200 | if (tcp and not udp): 201 | network = tcp 202 | if (tcp and udp): 203 | network = None 204 | network = tcp + "," + udp 205 | 206 | inboundShadowsocksJSONFile["network"] = network 207 | if not network: 208 | inboundShadowsocksJSONFile["network"] = "tcp" # V2Ray 3.16+ default is "tcp" 209 | 210 | try: 211 | self.treasureChest.addLevel(self.spinBoxInboundShadowsocksLevel.value()) 212 | self.treasureChest.addEmail(self.lineEditInboundShadowsocksEmail.text()) 213 | except Exception: 214 | pass 215 | 216 | return inboundShadowsocksJSONFile 217 | 218 | def clearinboundShadowsocksPanel(self): 219 | self.lineEditInboundShadowsocksEmail.clear() 220 | self.comboBoxInboundShadowsocksMethod.setCurrentIndex(0) 221 | self.lineEditInboundShadowsocksPassowrd.clear() 222 | self.checkBoxInboundShadowsocksUDP.setChecked(False) 223 | self.checkBoxInboundShadowsocksOTA.setChecked(False) 224 | self.checkBoxNewtworkTCP.setChecked(False) 225 | self.checkBoxNewtworkUDP.setChecked(False) 226 | self.spinBoxInboundShadowsocksLevel.setValue(0) 227 | 228 | def __debugTest(self): 229 | import json 230 | print(json.dumps(self.createInboundShadowsocksJSONFile(), indent=4, sort_keys=False)) 231 | 232 | 233 | if __name__ == "__main__": 234 | from PyQt5.QtWidgets import QApplication 235 | app = QApplication(sys.argv) 236 | ex = InboundShadowsocksPanel() 237 | v = QVBoxLayout() 238 | v.addWidget(ex.createShadowsocksSettingPanel()) 239 | ex.setLayout(v) 240 | ex.setGeometry(300, 300, 680, 260) 241 | ex.show() 242 | sys.exit(app.exec_()) 243 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/logTAB.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QLabel, QWidget, QHBoxLayout, QVBoxLayout, 4 | QPushButton, QButtonGroup, QLineEdit, 5 | QFileDialog, QComboBox, QGroupBox) 6 | from PyQt5.QtCore import QDate, QTime, qWarning, QFileInfo, QCoreApplication 7 | 8 | import sys, json 9 | v2rayshellDebug = False 10 | 11 | if __name__ == "__main__": 12 | v2rayshellDebug = True 13 | # this for debug test 14 | path = QFileInfo(sys.argv[0]) 15 | srcPath = path.absoluteFilePath().split("/") 16 | sys.path.append("/".join(srcPath[:-3])) 17 | 18 | 19 | class logBook(): 20 | """ 21 | Check the status of the v2ray before departure 22 | """ 23 | 24 | def __init__(self, openFromJSONFile=False): 25 | # only record the debug message when use open conf.json file 26 | self.openFile = openFromJSONFile 27 | 28 | def setisOpenJSONFile(self, openFromJSONFile): 29 | self.openFile = openFromJSONFile 30 | 31 | def getTime(self): 32 | return QDate.currentDate().toString() + " " + QTime.currentTime().toString("HH:mm:ss") 33 | 34 | def writeLog(self, cabin, situation, log="unkonw"): 35 | if (self.openFile): 36 | if (situation == "KeyError"): 37 | qWarning("Date: {} ---> {} Panel analysis of JSON file error. can not find the key {}, use default value.".format(self.getTime(), cabin, log)) 38 | elif (situation == "ValueError or TypeError" or 39 | situation == "ValueError" or 40 | situation == "TypeError"): 41 | qWarning("Date: {} ---> {} Panel analysis of JSON file error. can not use this value {}, use default value.".format(self.getTime(), cabin, log)) 42 | else: 43 | qWarning("Date: {} ---> {} Panel analysis of JSON file error. have a error:--> {} <--".format(self.getTime(), cabin, log)) 44 | 45 | 46 | class logTab(QWidget): 47 | 48 | def __init__(self): 49 | super().__init__() 50 | self.logJSONFile = { 51 | "access": "", 52 | "error": "", 53 | "loglevel": "warning" 54 | } 55 | self.translate = QCoreApplication.translate 56 | 57 | def createLogTab(self): 58 | labelAccess = QLabel( 59 | self.translate("logTab", "Access File: ")) 60 | labelError = QLabel( 61 | self.translate("logTab", "Error File: ")) 62 | self.lineEditAccess = QLineEdit() 63 | self.lineEditError = QLineEdit() 64 | 65 | btnSaveAccess = QPushButton( 66 | self.translate("logTab", "&Save"), self) 67 | btnSaveError = QPushButton( 68 | self.translate("logTab", "S&ave"), self) 69 | 70 | labelLogLevel = QLabel(self.translate("logTab", "Log Level: "), self) 71 | self.comboxLogLevel = QComboBox() 72 | self.comboxLogLevel.addItems(("warning", "debug", "info", "error" , "none")) 73 | 74 | self.buttonGroupSave = QButtonGroup() 75 | self.buttonGroupSave.addButton(btnSaveAccess) 76 | self.buttonGroupSave.addButton(btnSaveError) 77 | 78 | hboxAccess = QHBoxLayout() 79 | hboxAccess.addWidget(labelAccess) 80 | hboxAccess.addWidget(self.lineEditAccess) 81 | hboxAccess.addWidget(btnSaveAccess) 82 | 83 | hboxError = QHBoxLayout() 84 | hboxError.addWidget(labelError) 85 | hboxError.addWidget(self.lineEditError) 86 | hboxError.addWidget(btnSaveError) 87 | 88 | hboxLogLevel = QHBoxLayout() 89 | hboxLogLevel.addWidget(labelLogLevel) 90 | hboxLogLevel.addWidget(self.comboxLogLevel) 91 | hboxLogLevel.addStretch() 92 | 93 | vboxLogPanel = QVBoxLayout() 94 | vboxLogPanel.addLayout(hboxAccess) 95 | vboxLogPanel.addLayout(hboxError) 96 | vboxLogPanel.addLayout(hboxLogLevel) 97 | vboxLogPanel.addStretch() 98 | 99 | self.setLayout(vboxLogPanel) 100 | 101 | groupBoxLogTAB = QGroupBox("", self) 102 | groupBoxLogTAB.setLayout(vboxLogPanel) 103 | 104 | self.createLogTabSignals() 105 | 106 | if (v2rayshellDebug): 107 | self.__debugBtn = QPushButton("__debugTest", self) 108 | vboxLogPanel.addWidget(self.__debugBtn) 109 | self.__debugBtn.clicked.connect(self.__debugTest) 110 | self.settingLogTabFromJSONFile(self.logJSONFile, True) 111 | 112 | return groupBoxLogTAB 113 | 114 | def createLogTabSignals(self): 115 | self.buttonGroupSave.buttonClicked.connect(self.onbuttonGroupSave) 116 | 117 | def onbuttonGroupSave(self, e): 118 | options = QFileDialog.Options() 119 | if (e.text() == self.translate("logTab", "&Save")): 120 | fileName, _ = QFileDialog.getSaveFileName(self, 121 | self.translate("logTab", "Save V2ray Access log file"), 122 | "_access", 123 | "Log Files (*.log)", 124 | options=options) 125 | self.lineEditAccess.setText(fileName) 126 | 127 | if (e.text() == self.translate("logTab", "S&ave")): 128 | fileName, _ = QFileDialog.getSaveFileName(self, 129 | self.translate("logTab", "Save V2ray Error log file"), 130 | "_error", 131 | "Log Files (*.log)", 132 | options=options) 133 | self.lineEditError.setText(fileName) 134 | 135 | def settingLogTabFromJSONFile(self, logJSONFile={}, openFromJSONFile=False): 136 | logTagslog = logBook(openFromJSONFile) 137 | 138 | if (not logJSONFile): logJSONFile = {} 139 | 140 | try: 141 | logJSONFile["access"] 142 | except KeyError as e: 143 | logTagslog.writeLog("LogTAB", "KeyError", e) 144 | logJSONFile["access"] = "" 145 | 146 | try: 147 | logJSONFile["error"] 148 | except KeyError as e: 149 | logTagslog.writeLog("LogTAB", "KeyError", e) 150 | logJSONFile["error"] = "" 151 | 152 | try: 153 | logJSONFile["loglevel"] 154 | except KeyError as e: 155 | logTagslog.writeLog("LogTAB", "KeyError", e) 156 | logJSONFile["loglevel"] = "warning" 157 | 158 | self.lineEditAccess.setText(str(logJSONFile["access"])) 159 | self.lineEditError.setText(str(logJSONFile["error"])) 160 | try: 161 | self.comboxLogLevel.setCurrentText(str(logJSONFile["loglevel"])) 162 | except (ValueError, TypeError) as e: 163 | logTagslog.writeLog("LogTAB", "ValueError or TypeError", e) 164 | 165 | def createLogJSONFile(self): 166 | logJSONFile = {} 167 | logJSONFile["access"] = self.lineEditAccess.text() 168 | logJSONFile["error"] = self.lineEditError.text() 169 | logJSONFile["loglevel"] = self.comboxLogLevel.currentText() 170 | 171 | return logJSONFile 172 | 173 | def __debugTest(self): 174 | print(json.dumps(self.createLogJSONFile(), indent=4, sort_keys=False)) 175 | 176 | 177 | if __name__ == "__main__": 178 | from PyQt5.QtWidgets import QApplication 179 | app = QApplication(sys.argv) 180 | ex = logTab() 181 | ex.createLogTab() 182 | ex.setGeometry(200, 100, 380, 180) 183 | ex.show() 184 | sys.exit(app.exec_()) 185 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/nauticalChartPanel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QWidget, QButtonGroup, QVBoxLayout, QApplication, 4 | QTabWidget, QScrollArea, QHBoxLayout, QPushButton, 5 | QDialog, QFileDialog) 6 | from PyQt5.QtCore import QFileInfo, QCoreApplication 7 | import sys, json 8 | 9 | v2rayshellDebug = False 10 | 11 | if __name__ == "__main__": 12 | v2rayshellDebug = True 13 | # this for debug test 14 | path = QFileInfo(sys.argv[0]) 15 | srcPath = path.absoluteFilePath().split("/") 16 | sys.path.append("/".join(srcPath[:-3])) 17 | 18 | from bridgehouse.editMap.port import (inboundPanel, outboundPanel, logbook, openV2rayJSONFile, treasureChest) 19 | from bridgehouse.editMap import (logTAB, dnsTAB, transportTAB, routingTAB, policyTAB, apiTAB) 20 | 21 | 22 | class nauticalChartPanel(QDialog): 23 | 24 | def __init__(self, filePath=False): 25 | super().__init__() 26 | self.configJSONFile = { 27 | "log": {}, 28 | "dns": {}, 29 | "api": {}, 30 | "stats": {}, 31 | "routing": {}, 32 | "policy": {}, 33 | "inbound": {}, 34 | "outbound": {}, 35 | "inboundDetour": [], 36 | "outboundDetour": [], 37 | "transport": {} 38 | } 39 | self.filePath = filePath 40 | self.treasureChest = treasureChest.treasureChest() 41 | self.translate = QCoreApplication.translate 42 | 43 | def createPanel(self): 44 | hboxButton = QHBoxLayout() 45 | btnSave = QPushButton(self.translate("nauticalChartPanel", "Save")) 46 | btnExit = QPushButton(self.translate("nauticalChartPanel", "Exit")) 47 | self.groupButtonConfigure = QButtonGroup() 48 | self.groupButtonConfigure.addButton(btnSave) 49 | self.groupButtonConfigure.addButton(btnExit) 50 | 51 | hboxButton.addStretch() 52 | hboxButton.addWidget(btnSave) 53 | hboxButton.addWidget(btnExit) 54 | 55 | self.inbound = inboundPanel.InboundPanel(self.treasureChest) 56 | self.outbound = outboundPanel.OutboundPanel(self.treasureChest) 57 | 58 | tabWidgetConfigurePanel = QTabWidget() 59 | tabWidgetConfigurePanel.addTab( 60 | self.inbound.createInboundPanel(), 61 | self.translate("nauticalChartPanel", "Inbound")) 62 | tabWidgetConfigurePanel.addTab( 63 | self.outbound.createOutboundPanel(), 64 | self.translate("nauticalChartPanel", "Outbound")) 65 | 66 | self.transportTAB = transportTAB.transportTab() 67 | tabWidgetConfigurePanel.addTab( 68 | self.transportTAB.createTransportPanel(), 69 | self.translate("nauticalChartPanel", "Transport Setting")) 70 | 71 | self.dnsTAB = dnsTAB.dnsTab() 72 | tabWidgetConfigurePanel.addTab( 73 | self.dnsTAB.createDnsTab(), 74 | self.translate("nauticalChartPanel", "DNS Server")) 75 | 76 | self.routingTAB = routingTAB.routingTab() 77 | tabWidgetConfigurePanel.addTab( 78 | self.routingTAB.createRoutingTab(), 79 | self.translate("nauticalChartPanel", "Router Setting")) 80 | 81 | self.policyTAB = policyTAB.policyTab(self.treasureChest) 82 | tabWidgetConfigurePanel.addTab( 83 | self.policyTAB.createPolicyTab(), 84 | self.translate("nauticalChartPanel", "Policy")) 85 | 86 | self.logTAB = logTAB.logTab() 87 | tabWidgetConfigurePanel.addTab( 88 | self.logTAB.createLogTab(), 89 | self.translate("nauticalChartPanel", "Log Files")) 90 | 91 | self.apiTAB = apiTAB.apiTAB(self.treasureChest) 92 | tabWidgetConfigurePanel.addTab(self.apiTAB.createapiTAB(), "Api") 93 | 94 | vboxConfigure = QVBoxLayout() 95 | vboxConfigure.addWidget(tabWidgetConfigurePanel) 96 | vboxConfigure.addLayout(hboxButton) 97 | self.ScrollLayout(vboxConfigure) 98 | 99 | if (v2rayshellDebug): 100 | self.__debugBtn = QPushButton("__debugTest", self) 101 | self.__debugRefresh = QPushButton("__RefreshTest", self) 102 | self.__printTags = QPushButton("__PrintTags", self) 103 | self.__printAllLevels = QPushButton("__PrintAllLevels", self) 104 | self.__printAllEmails = QPushButton("__PrintAllEmails", self) 105 | 106 | hboxBtn = QHBoxLayout(self) 107 | hboxBtn.addWidget(self.__debugBtn) 108 | hboxBtn.addWidget(self.__printTags) 109 | hboxBtn.addWidget(self.__printAllLevels) 110 | hboxBtn.addWidget(self.__printAllEmails) 111 | hboxBtn.addWidget(self.__debugRefresh) 112 | vboxConfigure.addLayout(hboxBtn) 113 | 114 | self.__debugBtn.clicked.connect(self.__debugTest) 115 | self.__debugRefresh.clicked.connect(self.__debugRefreshTest) 116 | self.__printTags.clicked.connect(lambda: print(self.treasureChest.getAllTags())) 117 | self.__printAllLevels.clicked.connect(lambda: print(self.treasureChest.getLevels())) 118 | self.__printAllEmails.clicked.connect(lambda: print(self.treasureChest.getEmails())) 119 | 120 | self.editV2rayJSONFile = openV2rayJSONFile.editV2rayJSONFile(self.treasureChest) 121 | tabWidgetConfigurePanel.addTab(self.editV2rayJSONFile.createPanel(), "open V2ray File") 122 | self.settingv2rayshellPanelFromJSONFile(True) 123 | 124 | if (self.filePath): 125 | openV2rayJSONFile.openV2rayJSONFile(self.filePath, self.treasureChest).initboundJSONData() 126 | self.settingv2rayshellPanelFromJSONFile(openFromJSONFile=True) 127 | 128 | self.createnauticalChartPanelSignals() 129 | 130 | def createnauticalChartPanelSignals(self): 131 | self.groupButtonConfigure.buttonClicked.connect(self.ongroupButtonConfigureclicked) 132 | 133 | def ongroupButtonConfigureclicked(self, e): 134 | if e.text() == self.translate("nauticalChartPanel", "Exit"): 135 | self.close() 136 | elif e.text() == self.translate("nauticalChartPanel", "Save"): 137 | self.savenauticalChart(self.createv2rayJSONFile()) 138 | 139 | def savenauticalChart(self, JSONData): 140 | JSONData = json.dumps(JSONData, indent=4, sort_keys=False) 141 | options = QFileDialog.Options() 142 | filePath, _ = QFileDialog.getSaveFileName(self, 143 | self.translate("nauticalChartPanel", "Save V2Ray config.json File"), 144 | "config.json", 145 | """ 146 | json file (*.json);; 147 | All Files (*) 148 | """, 149 | options=options) 150 | if (filePath): 151 | openV2rayJSONFile.openV2rayJSONFile().saveTextdata(filePath=filePath, 152 | data=JSONData) 153 | 154 | def settingv2rayshellPanelFromJSONFile(self, openFromJSONFile=False): 155 | logbook.setisOpenJSONFile(openFromJSONFile) 156 | if (openFromJSONFile): 157 | self.inbound.refreshInboundPaneltableWidget() 158 | self.outbound.refreshOutboundPaneltableWidget() 159 | self.routingTAB.settingRoutingTABFromJSONFile( 160 | routingJSONFile=self.treasureChest.getRouting(), openFromJSONFile=True) 161 | self.logTAB.settingLogTabFromJSONFile( 162 | logJSONFile=self.treasureChest.getLog(), openFromJSONFile=True) 163 | tansportJSONData = self.treasureChest.getTransport() 164 | if (tansportJSONData): 165 | self.transportTAB.settingtransportPanelFromJSONFile( 166 | transportJSONFile=tansportJSONData, openFromJSONFile=True) 167 | self.transportTAB.groupTransportPanel.setChecked(True) 168 | else: 169 | pass 170 | self.dnsTAB.settingDnsTabFromJSONFile( 171 | dnsJSONFile=self.treasureChest.getDns(), openFromJSONFile=True) 172 | self.policyTAB.settingPolicyTabFromJSONFile( 173 | policyJSONFile=self.treasureChest.getPolicy()) 174 | self.apiTAB.settingAPITabFromJSONFile(self.treasureChest.getApi()) 175 | 176 | def createv2rayJSONFile(self): 177 | self.treasureChest.setDns(self.dnsTAB.createDnsJSONFile()) 178 | self.treasureChest.setLog(self.logTAB.createLogJSONFile()) 179 | self.treasureChest.setRouting(self.routingTAB.createRoutingJSONFile()) 180 | self.treasureChest.setPolicy(JSONDataPolicy=self.policyTAB.createPolicyJSONFile()) 181 | if (self.transportTAB.groupTransportPanel.isChecked()): 182 | self.treasureChest.setTransport(self.transportTAB.createtransportSettingJSONFile()) 183 | else: 184 | self.treasureChest.setTransport(JSONDataTransport=False) 185 | 186 | if (self.apiTAB.groupBoxAPI.isChecked()): 187 | self.treasureChest.setApi(self.apiTAB.createApiJSONFile()) 188 | 189 | v2rayJSONFile = self.treasureChest.exportV2rayJSONFile() 190 | 191 | return v2rayJSONFile 192 | 193 | def __debugTest(self): 194 | print(json.dumps(self.createv2rayJSONFile(), indent=4, sort_keys=False)) 195 | 196 | def __debugRefreshTest(self): 197 | self.inbound.refreshInboundPaneltableWidget() 198 | self.outbound.refreshOutboundPaneltableWidget() 199 | self.routingTAB.settingRoutingTABFromJSONFile( 200 | routingJSONFile=self.treasureChest.getRouting(), openFromJSONFile=True) 201 | self.logTAB.settingLogTabFromJSONFile( 202 | logJSONFile=self.treasureChest.getLog(), openFromJSONFile=True) 203 | tansportJSONData = self.treasureChest.getTransport() 204 | if (tansportJSONData): 205 | self.transportTAB.settingtransportPanelFromJSONFile( 206 | transportJSONFile=tansportJSONData, openFromJSONFile=True) 207 | self.transportTAB.groupTransportPanel.setChecked(True) 208 | else: 209 | self.transportTAB.groupTransportPanel.setChecked(False) 210 | self.dnsTAB.settingDnsTabFromJSONFile( 211 | dnsJSONFile=self.treasureChest.getDns(), openFromJSONFile=True) 212 | self.policyTAB.settingPolicyTabFromJSONFile( 213 | policyJSONFile=self.treasureChest.getPolicy()) 214 | self.apiTAB.settingAPITabFromJSONFile(self.treasureChest.getApi()) 215 | 216 | 217 | def ScrollLayout(self, layout): 218 | box = QVBoxLayout(self) 219 | scroll = QScrollArea(self) 220 | box.addWidget(scroll) 221 | scroll.setWidgetResizable(True) 222 | scrollContent = QWidget(scroll) 223 | 224 | scrollLayout = QVBoxLayout(scrollContent) 225 | scrollContent.setLayout(scrollLayout) 226 | 227 | scrollLayout.addLayout(layout) 228 | scroll.setWidget(scrollContent) 229 | 230 | 231 | if __name__ == "__main__": 232 | app = QApplication(sys.argv) 233 | ex = nauticalChartPanel() 234 | ex.createPanel() 235 | ex.setGeometry(500, 40, 1024, 950) 236 | ex.show() 237 | sys.exit(app.exec_()) 238 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/outbound/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from bridgehouse.editMap import logTAB 4 | 5 | logbook = logTAB.logBook() 6 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/outbound/blackholePanel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QWidget, QGroupBox, QRadioButton, QHBoxLayout, QPushButton) 4 | from PyQt5.QtCore import QFileInfo, QCoreApplication 5 | import sys 6 | 7 | v2rayshellDebug = False 8 | 9 | if __name__ == "__main__": 10 | v2rayshellDebug = True 11 | # this for debug test 12 | path = QFileInfo(sys.argv[0]) 13 | srcPath = path.absoluteFilePath().split("/") 14 | sys.path.append("/".join(srcPath[:-4])) 15 | 16 | from bridgehouse.editMap.inbound import logbook 17 | 18 | 19 | class BlackholePanel(QWidget): 20 | 21 | def __init__(self): 22 | super().__init__() 23 | self.blackholeJSONFile = { 24 | "response": { 25 | "type": "none" 26 | } 27 | } 28 | self.translate = QCoreApplication.translate 29 | 30 | def createBlackholeSettingPanel(self): 31 | self.radioBtnBlackholeNone = QRadioButton("None", self) 32 | self.radioBtnBlackholeHttp = QRadioButton("Http", self) 33 | 34 | self.radioBtnBlackholeNone.setChecked(True) 35 | 36 | hboxBlackholeSetting = QHBoxLayout() 37 | hboxBlackholeSetting.addWidget(self.radioBtnBlackholeNone) 38 | hboxBlackholeSetting.addWidget(self.radioBtnBlackholeHttp) 39 | hboxBlackholeSetting.addStretch() 40 | 41 | self.groupBoxBlackhole = QGroupBox(self.translate("BlackholePanel", "Blackhole"), self) 42 | self.groupBoxBlackhole.setLayout(hboxBlackholeSetting) 43 | 44 | if (v2rayshellDebug): 45 | self.__debugBtn = QPushButton("__debugTest", self) 46 | hboxBlackholeSetting.addWidget(self.__debugBtn) 47 | self.__debugBtn.clicked.connect(self.__debugTest) 48 | self.settingblackholePanelFromJSONFile(self.blackholeJSONFile, True) 49 | 50 | return self.groupBoxBlackhole 51 | 52 | def settingblackholePanelFromJSONFile(self, blackholeJSONFile={}, openFromJSONFile=False): 53 | logbook.setisOpenJSONFile(openFromJSONFile) 54 | 55 | if (not blackholeJSONFile): blackholeJSONFile = {} 56 | 57 | try: 58 | blackholeJSONFile["response"] 59 | except KeyError as e: 60 | logbook.writeLog("blackhole", "KeyError", e) 61 | blackholeJSONFile["response"] = {} 62 | try: 63 | blackholeJSONFile["response"]["type"] 64 | except KeyError as e: 65 | logbook.writeLog("blackhole", "KeyError", e) 66 | blackholeJSONFile["response"]["type"] = "none" 67 | 68 | blackholetype = blackholeJSONFile["response"]["type"] 69 | if (blackholetype == "http"): 70 | self.radioBtnBlackholeHttp.setChecked(True) 71 | if (blackholetype == "none"): 72 | self.radioBtnBlackholeNone.setChecked(True) 73 | 74 | def createblackholeJSONFile(self): 75 | blackholeJSONFile = {} 76 | blackholeJSONFile["response"] = {} 77 | if (self.radioBtnBlackholeHttp.isChecked()): 78 | blackholeJSONFile["response"]["type"] = "http" 79 | if (self.radioBtnBlackholeNone.isChecked()): 80 | blackholeJSONFile["response"]["type"] = "none" 81 | 82 | return blackholeJSONFile 83 | 84 | def clearblackholePanel(self): 85 | self.radioBtnBlackholeNone.setChecked(True) 86 | self.radioBtnBlackholeHttp.setChecked(False) 87 | 88 | def __debugTest(self): 89 | import json 90 | print(json.dumps(self.createblackholeJSONFile(), indent=4, sort_keys=False)) 91 | 92 | 93 | if __name__ == "__main__": 94 | from PyQt5.QtWidgets import QApplication 95 | app = QApplication(sys.argv) 96 | ex = BlackholePanel() 97 | ex.createBlackholeSettingPanel() 98 | ex.setGeometry(300, 300, 680, 600) 99 | ex.show() 100 | sys.exit(app.exec_()) 101 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/outbound/freedomPanel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QGroupBox, QLabel, QRadioButton, 4 | QLineEdit, QWidget, QSpinBox, QGridLayout, 5 | QHBoxLayout, QPushButton) 6 | from PyQt5.QtCore import QFileInfo, QCoreApplication 7 | import sys 8 | 9 | v2rayshellDebug = False 10 | 11 | if __name__ == "__main__": 12 | v2rayshellDebug = True 13 | # this for debug test 14 | path = QFileInfo(sys.argv[0]) 15 | srcPath = path.absoluteFilePath().split("/") 16 | sys.path.append("/".join(srcPath[:-4])) 17 | 18 | from bridgehouse.editMap.inbound import logbook 19 | 20 | 21 | class FreedomPanel(QWidget): 22 | 23 | def __init__(self): 24 | super().__init__() 25 | self.freedomJSONFile = { 26 | "domainStrategy": "AsIs", 27 | "timeout": 0, 28 | "redirect": "", 29 | "userLevel": 0 30 | } 31 | self.translate = QCoreApplication.translate 32 | 33 | def createFreedomSettingPanel(self): 34 | labelDomainStrategy = QLabel(self.translate("FreedomPanel", "Domain Strategy: "), self) 35 | self.radioBtnFreedomAsIs = QRadioButton(self.translate("FreedomPanel", "AsIs"), self) 36 | self.radioBtnFreedomUseIP = QRadioButton(self.translate("FreedomPanel", "UseIP"), self) 37 | labelTimeout = QLabel(self.translate("FreedomPanel", "Timeout: "), self) 38 | self.spinBoxFreedomTime = QSpinBox(self) 39 | labelRedirect = QLabel(self.translate("FreedomPanel", "Redirect Address: "), self) 40 | self.lineEditFreedomRedirect = QLineEdit(self) 41 | 42 | labeluserLevel = QLabel( 43 | self.translate("FreedomPanel", "User Level: ")) 44 | self.spinBoxFreedomsuserLevel = QSpinBox() 45 | self.spinBoxFreedomsuserLevel.setRange(0, 65535) 46 | self.spinBoxFreedomsuserLevel.setValue(0) 47 | hboxuserLevel = QHBoxLayout() 48 | hboxuserLevel.addWidget(labeluserLevel) 49 | hboxuserLevel.addWidget(self.spinBoxFreedomsuserLevel) 50 | hboxuserLevel.addStretch() 51 | 52 | self.radioBtnFreedomAsIs.setChecked(True) 53 | self.spinBoxFreedomTime.setRange(0, 999) 54 | 55 | groupBoxFreedom = QGroupBox(self.translate("FreedomPanel", "Freedom"), self) 56 | 57 | hboxRdBtn = QHBoxLayout() 58 | hboxRdBtn.addWidget(labelDomainStrategy) 59 | hboxRdBtn.addWidget(self.radioBtnFreedomAsIs) 60 | hboxRdBtn.addWidget(self.radioBtnFreedomUseIP) 61 | hboxRdBtn.addStretch() 62 | 63 | hboxTimeout = QHBoxLayout() 64 | hboxRedirct = QHBoxLayout() 65 | hboxTimeout.addWidget(labelTimeout) 66 | hboxTimeout.addWidget(self.spinBoxFreedomTime) 67 | hboxTimeout.addStretch() 68 | 69 | hboxRedirct.addWidget(labelRedirect) 70 | hboxRedirct.addWidget(self.lineEditFreedomRedirect) 71 | hboxRedirct.addStretch() 72 | 73 | gridLayoutFreedom = QGridLayout() 74 | gridLayoutFreedom.addLayout(hboxRdBtn, 0, 0) 75 | gridLayoutFreedom.addLayout(hboxRedirct, 1, 0) 76 | gridLayoutFreedom.addLayout(hboxTimeout, 2, 0) 77 | gridLayoutFreedom.addLayout(hboxuserLevel, 3, 0) 78 | 79 | groupBoxFreedom.setLayout(gridLayoutFreedom) 80 | 81 | if (v2rayshellDebug): 82 | self.__debugBtn = QPushButton("__debugTest", self) 83 | gridLayoutFreedom.addWidget(self.__debugBtn, 4, 0) 84 | self.__debugBtn.clicked.connect(self.__debugTest) 85 | 86 | self.settingfreedomPanelFromJSONFile(self.freedomJSONFile, True) 87 | 88 | return groupBoxFreedom 89 | 90 | def settingfreedomPanelFromJSONFile(self, freedomJSONFile=None, openFromJSONFile=False): 91 | logbook.setisOpenJSONFile(openFromJSONFile) 92 | 93 | if (not freedomJSONFile): freedomJSONFile = {} 94 | 95 | try: 96 | freedomJSONFile["domainStrategy"] 97 | except KeyError as e: 98 | logbook.writeLog("freedom", "KeyError", e) 99 | freedomJSONFile["domainStrategy"] = "AsIs" 100 | 101 | try: 102 | freedomJSONFile["timeout"] 103 | except KeyError as e: 104 | logbook.writeLog("freedom", "KeyError", e) 105 | freedomJSONFile["timeout"] = 0 106 | 107 | try: 108 | freedomJSONFile["redirect"] 109 | except KeyError as e: 110 | logbook.writeLog("freedom", "KeyError", e) 111 | freedomJSONFile["redirect"] = "" 112 | 113 | try: 114 | freedomJSONFile["userLevel"] 115 | except KeyError as e: 116 | logbook.writeLog("freedom", "KeyError", e) 117 | freedomJSONFile["userLevel"] = 0 118 | 119 | domainStrategy = freedomJSONFile["domainStrategy"] 120 | 121 | if (domainStrategy == "AsIs"): 122 | self.radioBtnFreedomAsIs.setChecked(True) 123 | if (domainStrategy == "UseIP"): 124 | self.radioBtnFreedomUseIP.setChecked(True) 125 | 126 | self.lineEditFreedomRedirect.setText(str(freedomJSONFile["redirect"])) 127 | 128 | try: 129 | self.spinBoxFreedomTime.setValue(int(freedomJSONFile["timeout"])) 130 | except (TypeError, ValueError) as e: 131 | logbook.writeLog("freedom", "ValueError or TypeError", e) 132 | self.spinBoxFreedomTime.setValue(0) 133 | 134 | try: 135 | self.spinBoxFreedomsuserLevel.setValue(int(freedomJSONFile["userLevel"])) 136 | except (TypeError, ValueError) as e: 137 | logbook.writeLog("freedom", "ValueError or TypeError", e) 138 | self.spinBoxFreedomsuserLevel.setValue(0) 139 | 140 | try: 141 | self.treasureChest.addLevel(int(freedomJSONFile["userLevel"])) 142 | except Exception: 143 | pass 144 | 145 | def createFreedomJSONFile(self): 146 | freedomJSONFile = {} 147 | if (self.radioBtnFreedomAsIs.isChecked()): 148 | freedomJSONFile["domainStrategy"] = "AsIs" 149 | if (self.radioBtnFreedomUseIP.isChecked()): 150 | freedomJSONFile["domainStrategy"] = "UseIP" 151 | freedomJSONFile["timeout"] = int(self.spinBoxFreedomTime.value()) 152 | freedomJSONFile["redirect"] = self.lineEditFreedomRedirect.text() 153 | freedomJSONFile["userLevel"] = int(self.spinBoxFreedomsuserLevel.value()) 154 | 155 | return freedomJSONFile 156 | 157 | def clearfreedomPanel(self): 158 | self.radioBtnFreedomAsIs.setChecked(True) 159 | self.radioBtnFreedomUseIP.setChecked(False) 160 | self.lineEditFreedomRedirect.clear() 161 | self.spinBoxFreedomsuserLevel.setValue(0) 162 | self.spinBoxFreedomTime.setValue(0) 163 | 164 | def __debugTest(self): 165 | import json 166 | print(json.dumps(self.createFreedomJSONFile(), indent=4, sort_keys=False)) 167 | 168 | 169 | if __name__ == "__main__": 170 | from PyQt5.QtWidgets import QApplication 171 | app = QApplication(sys.argv) 172 | ex = FreedomPanel() 173 | ex.createFreedomSettingPanel() 174 | ex.setGeometry(300, 350, 380, 160) 175 | ex.show() 176 | sys.exit(app.exec_()) 177 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/outbound/shadowsocksPanel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QTableWidget, QLabel, QLineEdit, QSpinBox, QComboBox, 4 | QCheckBox, QWidget, QGroupBox, QGridLayout, QPushButton, 5 | QHBoxLayout, QVBoxLayout, QAbstractItemView, 6 | QTableWidgetItem) 7 | from PyQt5.QtCore import QFileInfo, QCoreApplication 8 | import sys, copy 9 | from PyQt5.Qt import QStandardItem 10 | 11 | v2rayshellDebug = False 12 | 13 | if __name__ == "__main__": 14 | v2rayshellDebug = True 15 | # this for debug test 16 | path = QFileInfo(sys.argv[0]) 17 | srcPath = path.absoluteFilePath().split("/") 18 | sys.path.append("/".join(srcPath[:-4])) 19 | 20 | from bridgehouse.editMap.inbound import logbook 21 | 22 | 23 | class OutboundShadowsocksPanel(QWidget): 24 | 25 | def __init__(self): 26 | super().__init__() 27 | self.outboundShadowsocksJSONFile = { 28 | "servers": [ 29 | { 30 | "email": "love@v2ray.com", 31 | "address": "127.0.0.1", 32 | "port": 495, 33 | "method": "chacha20-poly1305", 34 | "password": "password", 35 | "ota": True, 36 | "level": 0 37 | } 38 | ] 39 | } 40 | self.translate = QCoreApplication.translate 41 | self.listMethodShadowsocksPanel = "aes-256-cfb", "aes-128-cfb", "chacha20", "chacha20-ietf", "aes-256-gcm", "aes-128-gcm", "chacha20-poly1305" 42 | self.labelHeaderShadowsocksPanel = (self.translate("OutboundShadowsocksPanel", "Adress"), 43 | self.translate("OutboundShadowsocksPanel", "Port"), 44 | self.translate("OutboundShadowsocksPanel", "Method"), 45 | self.translate("OutboundShadowsocksPanel", "Password"), 46 | self.translate("OutboundShadowsocksPanel", "Email"), 47 | self.translate("OutboundShadowsocksPanel", "User Level"), 48 | self.translate("OutboundShadowsocksPanel", "OTA")) 49 | 50 | def createShadowsocksSettingPanel(self): 51 | self.tableWidgetOutShadowsocks = tableWidget = QTableWidget(self) 52 | tableWidget.setRowCount(0) 53 | tableWidget.setColumnCount(7) 54 | tableWidget.setHorizontalHeaderLabels(self.labelHeaderShadowsocksPanel) 55 | tableWidget.setSelectionMode(QAbstractItemView.SingleSelection) 56 | tableWidget.setSelectionBehavior(QAbstractItemView.SelectRows) 57 | 58 | self.btnOutShadowsocksNew= QPushButton( 59 | self.translate("OutboundShadowsocksPanel", "New"), self) 60 | self.btnOutShadowsocksDelete = QPushButton( 61 | self.translate("OutboundShadowsocksPanel", "Delete"), self) 62 | 63 | vboxBtn = QVBoxLayout() 64 | vboxBtn.addWidget(QLabel()) 65 | vboxBtn.addWidget(QLabel()) 66 | vboxBtn.addWidget(QLabel()) 67 | vboxBtn.addWidget(QLabel()) 68 | vboxBtn.addWidget(self.btnOutShadowsocksNew) 69 | vboxBtn.addWidget(self.btnOutShadowsocksDelete) 70 | 71 | hboxTableWidgetUser = QHBoxLayout() 72 | hboxTableWidgetUser.addWidget(tableWidget) 73 | hboxTableWidgetUser.addLayout(vboxBtn) 74 | 75 | vboxShadowsocksSettingPanel = QVBoxLayout() 76 | vboxShadowsocksSettingPanel.addLayout(hboxTableWidgetUser) 77 | 78 | groupBoxShadowsocksSetting = QGroupBox( 79 | self.translate("OutboundShadowsocksPanel", "Shadowsocks"), self) 80 | groupBoxShadowsocksSetting.setLayout(vboxShadowsocksSettingPanel) 81 | 82 | if (v2rayshellDebug): 83 | self.__btnDebug = QPushButton("__DebugTest", self) 84 | vboxShadowsocksSettingPanel.addWidget(self.__btnDebug) 85 | self.__btnDebug.clicked.connect(self.__DebugTest) 86 | self.settingOutboundShadowsocksPanelFromJSONFile(self.outboundShadowsocksJSONFile, True) 87 | 88 | self.createOutShadowsocksSignales() 89 | 90 | return groupBoxShadowsocksSetting 91 | 92 | def createOutShadowsocksSignales(self): 93 | self.btnOutShadowsocksDelete.clicked.connect(self.onbtnOutShadowsocksDelete) 94 | self.btnOutShadowsocksNew.clicked.connect(self.onbtnOutShadowsocksNew) 95 | 96 | def tableWidgetNewRowItem( 97 | self, row, address=None, port=None, method=None, password=None, email=None, level=None, ota=False): 98 | spinBoxPort = QSpinBox() 99 | spinBoxLevel = QSpinBox() 100 | checkBoxOTA = QCheckBox() 101 | comboBoxMethod = QComboBox() 102 | comboBoxMethod.addItems(self.listMethodShadowsocksPanel) 103 | spinBoxPort.setMinimum(0) 104 | spinBoxPort.setMaximum(65535) 105 | 106 | spinBoxLevel.setMinimum(0) 107 | spinBoxLevel.setMaximum(65535) 108 | spinBoxLevel.setValue(0) 109 | checkBoxOTA.setCheckable(True) 110 | checkBoxOTA.setChecked(ota) 111 | if port: 112 | spinBoxPort.setValue(int(port)) 113 | if level: 114 | spinBoxLevel.setValue(int(level)) 115 | if method: 116 | comboBoxMethod.setCurrentText(method) 117 | self.tableWidgetOutShadowsocks.setItem(row, 0, QTableWidgetItem("" if not address else address)) 118 | self.tableWidgetOutShadowsocks.setCellWidget(row, 1, spinBoxPort) 119 | self.tableWidgetOutShadowsocks.setCellWidget(row, 2, comboBoxMethod) 120 | self.tableWidgetOutShadowsocks.setItem(row, 3, QTableWidgetItem("" if not password else password)) 121 | self.tableWidgetOutShadowsocks.setItem(row, 4, QTableWidgetItem("" if not email else email)) 122 | self.tableWidgetOutShadowsocks.setCellWidget(row, 5, spinBoxLevel) 123 | self.tableWidgetOutShadowsocks.setCellWidget(row, 6, checkBoxOTA) 124 | 125 | def onbtnOutShadowsocksNew(self): 126 | row = self.tableWidgetOutShadowsocks.rowCount() 127 | if (not row): 128 | self.tableWidgetOutShadowsocks.setRowCount(row+1) 129 | self.tableWidgetNewRowItem(row) 130 | else: 131 | password = self.tableWidgetOutShadowsocks.item(row-1, 3) 132 | address = self.tableWidgetOutShadowsocks.item(row-1, 0) 133 | if (address and address.text() and password and password.text()): 134 | self.tableWidgetOutShadowsocks.setRowCount(row+1) 135 | self.tableWidgetNewRowItem(row) 136 | 137 | def onbtnOutShadowsocksDelete(self): 138 | self.tableWidgetOutShadowsocks.removeRow(self.tableWidgetOutShadowsocks.currentRow()) 139 | 140 | def settingOutboundShadowsocksPanelFromJSONFile(self, outboundShadowsocksJSONFile=None, openFromJSONFile=False): 141 | logbook.setisOpenJSONFile(openFromJSONFile) 142 | self.tableWidgetOutShadowsocks.setRowCount(0) 143 | 144 | if (not outboundShadowsocksJSONFile): outboundShadowsocksJSONFile = {} 145 | 146 | try: 147 | outboundShadowsocksJSONFile["servers"] 148 | except KeyError as e: 149 | logbook.writeLog("OutboundShadowsocks", "KeyError", e) 150 | outboundShadowsocksJSONFile["servers"] = [] 151 | 152 | serverNumber = len(outboundShadowsocksJSONFile["servers"]) 153 | servers = outboundShadowsocksJSONFile["servers"] 154 | 155 | if (serverNumber): 156 | self.tableWidgetOutShadowsocks.setRowCount(serverNumber) 157 | try: 158 | for i in range(serverNumber): 159 | self.tableWidgetOutShadowsocks.setRowCount(i+1) 160 | address = servers[i]["address"] 161 | port = servers[i]["port"] 162 | method = servers[i]["method"] 163 | password = servers[i]["password"] 164 | email = servers[i]["email"] 165 | level = servers[i]["level"] 166 | ota = servers[i]["ota"] 167 | if method == "chacha20-ietf-poly1305": 168 | method = "chacha20-poly1305" 169 | self.tableWidgetNewRowItem(i, address, int(port), method, password, email, int(level), ota) 170 | except KeyError as e: 171 | logbook.writeLog("OutboundShadowsocks", "KeyError", e) 172 | 173 | def createOutboundShadowsocksJSONFile(self): 174 | serversNumber = self.tableWidgetOutShadowsocks.rowCount() 175 | outboundShadowsocksJSONFile = {} 176 | outboundShadowsocksJSONFile["servers"] = [] # clean default setting 177 | for i in range(serversNumber): 178 | servers = {} 179 | address = self.tableWidgetOutShadowsocks.item(i, 0) 180 | password = self.tableWidgetOutShadowsocks.item(i, 3) 181 | if not address.text() and not password.text(): 182 | continue 183 | port = self.tableWidgetOutShadowsocks.cellWidget(i, 1) 184 | method = self.tableWidgetOutShadowsocks.cellWidget(i, 2) 185 | 186 | email = self.tableWidgetOutShadowsocks.item(i, 4) 187 | level = self.tableWidgetOutShadowsocks.cellWidget(i, 5) 188 | ota = self.tableWidgetOutShadowsocks.cellWidget(i, 6) 189 | servers["email"] = email.text() 190 | servers["address"] = address.text() 191 | servers["port"] = int(port.value()) 192 | servers["method"] = method.currentText() 193 | servers["password"] = password.text() 194 | servers["level"] = int(level.value()) 195 | servers["ota"] = ota.isChecked() 196 | 197 | try:self.treasureChest.addLevel(level.value()) 198 | except Exception:pass 199 | try:self.treasureChest.addEmail(email.text()) 200 | except Exception:pass 201 | outboundShadowsocksJSONFile["servers"].append(copy.deepcopy(servers)) 202 | 203 | return outboundShadowsocksJSONFile 204 | 205 | def clearShadowsocksPanel(self): 206 | self.tableWidgetOutShadowsocks.setRowCount(0) 207 | 208 | def __DebugTest(self): 209 | import json 210 | print(json.dumps(self.createOutboundShadowsocksJSONFile(), indent=4, sort_keys=False)) 211 | 212 | 213 | if __name__ == "__main__": 214 | from PyQt5.QtWidgets import QApplication 215 | app = QApplication(sys.argv) 216 | ex = OutboundShadowsocksPanel() 217 | ex.createShadowsocksSettingPanel() 218 | ex.setGeometry(300, 300, 680, 500) 219 | ex.show() 220 | sys.exit(app.exec_()) 221 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/outbound/socksPanel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QLabel, QLineEdit, QSpinBox, 4 | QWidget, QGroupBox, QPushButton, 5 | QHBoxLayout, QVBoxLayout, QAbstractItemView, 6 | QTreeView) 7 | from PyQt5.QtGui import QStandardItemModel, QStandardItem 8 | from PyQt5.QtCore import QFileInfo, QCoreApplication 9 | 10 | import sys, copy 11 | 12 | 13 | v2rayshellDebug = False 14 | 15 | if __name__ == "__main__": 16 | v2rayshellDebug = True 17 | # this for debug test 18 | path = QFileInfo(sys.argv[0]) 19 | srcPath = path.absoluteFilePath().split("/") 20 | sys.path.append("/".join(srcPath[:-4])) 21 | 22 | from bridgehouse.editMap.inbound import logbook 23 | 24 | 25 | class OutboundSocksPanel(QWidget): 26 | 27 | def __init__(self): 28 | super().__init__() 29 | self.outboundSocksJSONFile = { 30 | "servers": [{ 31 | "address": "127.0.0.1", 32 | "port": 1080, 33 | "users": [{ 34 | "user": "test user", 35 | "pass": "test pass", 36 | "level": 0 37 | }] 38 | }] 39 | } 40 | self.translate = QCoreApplication.translate 41 | 42 | self.labeloutSocks = (self.translate("OutboundSocksPanel", "Address"), 43 | self.translate("OutboundSocksPanel", "Port"), 44 | self.translate("OutboundSocksPanel", "Level")) 45 | 46 | def createOutboundSocksSettingPanel(self): 47 | self.btnOutboundSocksNewUser= QPushButton( 48 | self.translate("OutboundSocksPanel", "New User")) 49 | self.btnOutboundSocksNewServer= QPushButton( 50 | self.translate("OutboundSocksPanel", "New Server")) 51 | self.btnOutboundSocksDelete = QPushButton( 52 | self.translate("OutboundSocksPanel", "Delete")) 53 | 54 | vboxBtnUser = QVBoxLayout() 55 | vboxBtnUser.addWidget(QLabel()) 56 | vboxBtnUser.addWidget(QLabel()) 57 | vboxBtnUser.addWidget(QLabel()) 58 | vboxBtnUser.addWidget(QLabel()) 59 | vboxBtnUser.addWidget(self.btnOutboundSocksNewUser) 60 | vboxBtnUser.addWidget(self.btnOutboundSocksNewServer) 61 | vboxBtnUser.addWidget(self.btnOutboundSocksDelete) 62 | 63 | self.treeViewoutSocksAddress = treeViewoutSocksAddress = QTreeView() 64 | treeViewoutSocksAddress.setSelectionMode(QAbstractItemView.SingleSelection) 65 | treeViewoutSocksAddress.setSelectionBehavior(QAbstractItemView.SelectRows) 66 | treeViewoutSocksAddress.setUniformRowHeights(True) 67 | 68 | self.treeViewoutSocksAddressMode = QStandardItemModel() 69 | self.treeViewoutSocksAddressMode.setHorizontalHeaderLabels(self.labeloutSocks) 70 | self.treeViewoutSocksAddress.setModel(self.treeViewoutSocksAddressMode) 71 | 72 | hboxtreeView = QHBoxLayout() 73 | hboxtreeView.addWidget(self.treeViewoutSocksAddress) 74 | hboxtreeView.addLayout(vboxBtnUser) 75 | 76 | vboxOutboundSocks = QVBoxLayout() 77 | vboxOutboundSocks.addLayout(hboxtreeView) 78 | 79 | groupBoxOutboundSocksPanel = QGroupBox( 80 | self.translate("OutboundSocksPanel", "Socks"), self) 81 | groupBoxOutboundSocksPanel.setLayout(vboxOutboundSocks) 82 | 83 | if (v2rayshellDebug): 84 | self.__btnDebug = QPushButton("__DebugTest", self) 85 | vboxOutboundSocks.addWidget(self.__btnDebug) 86 | self.__btnDebug.clicked.connect(self.__DebugTest) 87 | self.settingOutboundSocksPanelFromJSONFile(self.outboundSocksJSONFile, True) 88 | 89 | self.createOutboundSocksPanelSignals() 90 | 91 | return groupBoxOutboundSocksPanel 92 | 93 | def createOutboundSocksPanelSignals(self): 94 | self.btnOutboundSocksDelete.clicked.connect(self.onbtnOutboundSocksDelete) 95 | self.btnOutboundSocksNewUser.clicked.connect(self.onbtnOutboundSocksNewUser) 96 | self.btnOutboundSocksNewServer.clicked.connect(self.onbtnOutboundSocksNewServer) 97 | 98 | def onbtnOutboundSocksNewUser(self): 99 | rowCount = self.treeViewoutSocksAddressMode.rowCount() 100 | if not rowCount: 101 | self.newRowOutSocks(rowCount) 102 | self.treeViewoutSocksAddress.setCurrentIndex(self.treeViewoutSocksAddressMode.index(rowCount, 0)) 103 | return 104 | 105 | itemSelection = self.treeViewoutSocksAddress.selectionModel() 106 | rowCurrent = itemSelection.selectedIndexes()[0] 107 | root = rowCurrent.parent().row() 108 | row = rowCurrent.row() 109 | if root == -1: 110 | self.treeViewoutSocksAddressMode.item(row).appendRow(( 111 | QStandardItem("user_name"), 112 | QStandardItem("********"), 113 | QStandardItem("0"))) 114 | self.treeViewoutSocksAddress.expand(self.treeViewoutSocksAddressMode.index(row, 0)) 115 | else: 116 | self.treeViewoutSocksAddressMode.item(root).appendRow(( 117 | QStandardItem("user_name"), 118 | QStandardItem("********"), 119 | QStandardItem("0"))) 120 | 121 | def onbtnOutboundSocksNewServer(self): 122 | row = self.treeViewoutSocksAddressMode.rowCount() 123 | if not row: 124 | self.newRowOutSocks(row) 125 | self.treeViewoutSocksAddress.setCurrentIndex(self.treeViewoutSocksAddressMode.index(row, 0)) 126 | else: 127 | r = self.treeViewoutSocksAddressMode.item(row-1) 128 | if r.hasChildren(): 129 | self.newRowOutSocks(row) 130 | 131 | def newRowOutSocks(self, row): 132 | self.treeViewoutSocksAddressMode.setRowCount(row+1) 133 | address = self.treeViewoutSocksAddressMode.index(row, 0) 134 | port = self.treeViewoutSocksAddressMode.index(row, 1) 135 | self.treeViewoutSocksAddressMode.setData(address, "127.0.0.1") 136 | self.treeViewoutSocksAddressMode.setData(port, "1080") 137 | 138 | def onbtnOutboundSocksDelete(self): 139 | if (not self.treeViewoutSocksAddressMode.rowCount()): return 140 | 141 | itemSelection = self.treeViewoutSocksAddress.selectionModel() 142 | rowCurrent = itemSelection.selectedIndexes()[0] 143 | root = rowCurrent.parent().row() 144 | row = rowCurrent.row() 145 | if root == -1: 146 | self.treeViewoutSocksAddressMode.removeRow(row) 147 | else: 148 | self.treeViewoutSocksAddressMode.item(root).removeRow(row) 149 | 150 | def settingOutboundSocksPanelFromJSONFile(self, outboundSocksJSONFile={}, openFromJSONFile=True): 151 | logbook.setisOpenJSONFile(openFromJSONFile) 152 | self.treeViewoutSocksAddressMode.setRowCount(0) 153 | 154 | if (not outboundSocksJSONFile): outboundSocksJSONFile = {} 155 | 156 | try: 157 | outboundSocksJSONFile["servers"] 158 | except KeyError as e: 159 | logbook.writeLog("OutboundSocks", "KeyError", e) 160 | outboundSocksJSONFile["servers"] = [] 161 | 162 | servers = outboundSocksJSONFile["servers"] 163 | serversNumber = len(servers) 164 | 165 | # just show the first server detail in TabelWidget 166 | if (serversNumber): 167 | 168 | for i in range(serversNumber): 169 | try: 170 | usersNumber = len(servers[i]["users"]) 171 | except KeyError as e: 172 | logbook.writeLog("OutboundSocks", "KeyError", e) 173 | usersNumber = 0 174 | try: 175 | users = servers[i]["users"] 176 | except KeyError as e: 177 | logbook.writeLog("OutboundSocks", "KeyError", e) 178 | try: 179 | serverAddress = QStandardItem(str(servers[i]["address"])) 180 | except KeyError as e: 181 | logbook.writeLog("OutboundSocks", "KeyError", e) 182 | 183 | try: 184 | serverPort = QStandardItem(str(servers[i]["port"])) 185 | except KeyError as e: 186 | logbook.writeLog("OutboundSocks", "KeyError", e) 187 | 188 | for j in range(usersNumber): 189 | try: 190 | user = QStandardItem(str(users[j]["user"])) 191 | password = QStandardItem(str(users[j]["pass"])) 192 | try: 193 | level = QStandardItem(str(users[j]["level"])) 194 | except Exception: 195 | level = QStandardItem("0") 196 | 197 | try: 198 | self.treasureChest.addLevel(users[j]["level"]) 199 | except Exception: 200 | pass 201 | serverAddress.appendRow((user, password, level)) 202 | except KeyError as e: 203 | logbook.writeLog("OutboundSocks set user", "KeyError", e) 204 | except TypeError as e: 205 | logbook.writeLog("OutboundSocks set user", "TypeError", e) 206 | except ValueError as e: 207 | logbook.writeLog("OutboundSocks set user", "ValueError", e) 208 | except: 209 | logbook.writeLog("OutboundSocks set user", "unkonw") 210 | self.treeViewoutSocksAddressMode.appendRow((serverAddress, serverPort)) 211 | # make a sure add items success 212 | if (self.treeViewoutSocksAddressMode.rowCount()): 213 | # if there no any selectItem, "Add, Modify, Delete" button's slot will crash 214 | self.treeViewoutSocksAddress.setCurrentIndex(self.treeViewoutSocksAddressMode.index(0, 0)) 215 | 216 | def createOutboundSocksJSONFile(self): 217 | outboundSocksJSONFile = {} 218 | outboundSocksJSONFile["servers"] = [] 219 | serversNumber = self.treeViewoutSocksAddressMode.rowCount() 220 | if (not serversNumber): return 221 | print(serversNumber) 222 | for i in range(serversNumber): 223 | server = {} 224 | user = {} 225 | server["address"] = self.treeViewoutSocksAddressMode.item(i, 0).text() 226 | server["port"] = int(self.treeViewoutSocksAddressMode.item(i, 1).text()) 227 | server["users"] = [] 228 | if (self.treeViewoutSocksAddressMode.hasChildren()): 229 | usersNumber = self.treeViewoutSocksAddressMode.item(i).rowCount() 230 | for j in range(usersNumber): 231 | user["user"] = self.treeViewoutSocksAddressMode.item(i).child(j, 0).text() 232 | user["pass"] = self.treeViewoutSocksAddressMode.item(i).child(j, 1).text() 233 | user["level"] = int(self.treeViewoutSocksAddressMode.item(i).child(j, 2).text()) 234 | try: 235 | self.treasureChest.addLevel(user["level"]) 236 | except Exception: 237 | pass 238 | server["users"].append(copy.deepcopy(user)) 239 | else: 240 | server["users"] = [] 241 | outboundSocksJSONFile["servers"].append(copy.deepcopy(server)) 242 | 243 | return outboundSocksJSONFile 244 | 245 | def clearSocksPanel(self): 246 | self.treeViewoutSocksAddressMode.setRowCount(0) 247 | 248 | def __DebugTest(self): 249 | import json 250 | print(json.dumps(self.createOutboundSocksJSONFile(), indent=4, sort_keys=False)) 251 | 252 | 253 | if __name__ == "__main__": 254 | from PyQt5.QtWidgets import QApplication 255 | app = QApplication(sys.argv) 256 | ex = OutboundSocksPanel() 257 | ex.createOutboundSocksSettingPanel() 258 | ex.setGeometry(300, 300, 800, 350) 259 | ex.show() 260 | sys.exit(app.exec_()) 261 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/outbound/vmessPanel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QLabel, QLineEdit, QSpinBox, QComboBox, 4 | QWidget, QGroupBox, QPushButton, 5 | QHBoxLayout, QVBoxLayout, QAbstractItemView, QTreeView) 6 | from PyQt5.QtGui import QStandardItemModel, QStandardItem 7 | from PyQt5.QtCore import QFileInfo, QCoreApplication 8 | 9 | import sys, uuid, copy 10 | 11 | v2rayshellDebug = False 12 | 13 | if __name__ == "__main__": 14 | v2rayshellDebug = True 15 | # this for debug test 16 | path = QFileInfo(sys.argv[0]) 17 | srcPath = path.absoluteFilePath().split("/") 18 | sys.path.append("/".join(srcPath[:-4])) 19 | 20 | from bridgehouse.editMap.inbound import logbook 21 | from bridgehouse.editMap.toolbox import toolbox 22 | 23 | class OutboundVmessSettingPanel(QWidget): 24 | 25 | def __init__(self): 26 | super().__init__() 27 | self.outboundVmessJSONFile = { 28 | "vnext": [ 29 | { 30 | "address": "127.0.0.1", 31 | "port": 443, 32 | "users": [ 33 | { 34 | "id": "27848739-7e62-4138-9fd3-098a63964b6b", 35 | "alterId": 10, 36 | "security": "aes-128-cfb", 37 | "level": 0 38 | } 39 | ] 40 | } 41 | ] 42 | } 43 | self.translate = QCoreApplication.translate 44 | self.labeloutVmess = (self.translate("OutboundVmessSettingPanel", "Address/AlterID"), 45 | self.translate("OutboundVmessSettingPanel", "Port/Method"), 46 | self.translate("OutboundVmessSettingPanel", "UUID"), 47 | self.translate("OutboundVmessSettingPanel", "Level")) 48 | 49 | self.listMethodoutVmess = "auto", "aes-128-cfb", "aes-128-gcm", "chacha20-poly1305", "none" 50 | 51 | def createOutboundVmessPanel(self): 52 | self.treeViewOutboundVmessAddress = treeViewOutboundVmessAddress = QTreeView() 53 | treeViewOutboundVmessAddress.setSelectionMode(QAbstractItemView.SingleSelection) 54 | treeViewOutboundVmessAddress.setSelectionBehavior(QAbstractItemView.SelectRows) 55 | treeViewOutboundVmessAddress.setUniformRowHeights(True) 56 | 57 | self.treeViewOutboundVmessAddressMode = QStandardItemModel() 58 | self.treeViewOutboundVmessAddressMode.setHorizontalHeaderLabels(self.labeloutVmess) 59 | treeViewOutboundVmessAddress.setModel(self.treeViewOutboundVmessAddressMode) 60 | 61 | treeViewOutboundVmessAddress.setItemDelegateForColumn(1, toolbox.ComboBoxSpinBoxDelegate(self.listMethodoutVmess)) 62 | 63 | self.btnoutVmessNewUser = QPushButton( 64 | self.translate("OutboundVmessSettingPanel", "New User")) 65 | self.btnoutVmessNewServer= QPushButton( 66 | self.translate("OutboundVmessSettingPanel", "New Sever")) 67 | self.btnoutVmessDelete = QPushButton( 68 | self.translate("OutboundVmessSettingPanel", "Delete")) 69 | 70 | vboxBtn = QVBoxLayout() 71 | vboxBtn.addWidget(QLabel()) 72 | vboxBtn.addWidget(QLabel()) 73 | vboxBtn.addWidget(QLabel()) 74 | vboxBtn.addWidget(QLabel()) 75 | vboxBtn.addWidget(self.btnoutVmessNewUser) 76 | vboxBtn.addWidget(self.btnoutVmessNewServer) 77 | vboxBtn.addWidget(self.btnoutVmessDelete) 78 | 79 | hboxTreeView = QHBoxLayout() 80 | hboxTreeView.addWidget(treeViewOutboundVmessAddress) 81 | hboxTreeView.addLayout(vboxBtn) 82 | 83 | vboxOutBoundVmessUser = QVBoxLayout() 84 | vboxOutBoundVmessUser.addLayout(hboxTreeView) 85 | 86 | groupBoxOutboundVmessUser = QGroupBox("", self) 87 | groupBoxOutboundVmessUser.setLayout(vboxOutBoundVmessUser) 88 | 89 | self.createOutboundVmessPanelSignals() 90 | 91 | if(v2rayshellDebug): 92 | self.__btnDebug = QPushButton("__DebugTest", self) 93 | self.__btnDebug.clicked.connect(self.__DebugTest) 94 | vboxOutBoundVmessUser.addWidget(self.__btnDebug) 95 | self.setLayout(vboxOutBoundVmessUser) 96 | self.settingOutboundVmessPanelFromJSONFile(self.outboundVmessJSONFile, True) 97 | return 98 | 99 | return groupBoxOutboundVmessUser 100 | 101 | def createOutboundVmessPanelSignals(self): 102 | self.btnoutVmessDelete.clicked.connect(self.onbtnoutVmessDelete) 103 | self.btnoutVmessNewServer.clicked.connect(self.onbtnoutVmessNewServer) 104 | self.btnoutVmessNewUser.clicked.connect(self.onbtnoutVmessNewUser) 105 | 106 | def onbtnoutVmessDelete(self): 107 | if (not self.treeViewOutboundVmessAddressMode.rowCount()): return 108 | 109 | itemSelection = self.treeViewOutboundVmessAddress.selectionModel() 110 | rowCurrent = itemSelection.selectedIndexes()[0] 111 | root = rowCurrent.parent().row() 112 | row = rowCurrent.row() 113 | 114 | if (root == -1): 115 | self.treeViewOutboundVmessAddressMode.removeRow(row) 116 | else: 117 | self.treeViewOutboundVmessAddressMode.item(root).removeRow(row) 118 | 119 | def onbtnoutVmessNewUser(self): 120 | rowCount = self.treeViewOutboundVmessAddressMode.rowCount() 121 | if not rowCount: 122 | self.newRowOutVmess(rowCount) 123 | self.treeViewOutboundVmessAddress.setCurrentIndex( 124 | self.treeViewOutboundVmessAddressMode.index(rowCount, 0)) 125 | return 126 | 127 | itemSelection = self.treeViewOutboundVmessAddress.selectionModel() 128 | rowCurrent = itemSelection.selectedIndexes()[0] 129 | root = rowCurrent.parent().row() 130 | row = rowCurrent.row() 131 | if root == -1: 132 | self.treeViewOutboundVmessAddressMode.item(row).appendRow(( 133 | QStandardItem("10"), 134 | QStandardItem("aes-128-cfb"), 135 | QStandardItem(self.createUUID()), 136 | QStandardItem("0"))) 137 | self.treeViewOutboundVmessAddress.expand(self.treeViewOutboundVmessAddressMode.index(row, 0)) 138 | else: 139 | self.treeViewOutboundVmessAddressMode.item(root).appendRow(( 140 | QStandardItem("10"), 141 | QStandardItem("aes-128-cfb"), 142 | QStandardItem(self.createUUID()), 143 | QStandardItem("0"))) 144 | 145 | def onbtnoutVmessNewServer(self): 146 | row = self.treeViewOutboundVmessAddressMode.rowCount() 147 | if not row: 148 | self.newRowOutVmess(row) 149 | self.treeViewOutboundVmessAddress.setCurrentIndex( 150 | self.treeViewOutboundVmessAddressMode.index(row, 0)) 151 | else: 152 | r = self.treeViewOutboundVmessAddressMode.item(row-1) 153 | if r.hasChildren(): 154 | self.newRowOutVmess(row) 155 | 156 | def newRowOutVmess(self, row): 157 | self.treeViewOutboundVmessAddressMode.setRowCount(row+1) 158 | address = self.treeViewOutboundVmessAddressMode.index(row, 0) 159 | port = self.treeViewOutboundVmessAddressMode.index(row, 1) 160 | self.treeViewOutboundVmessAddressMode.setData(address, "127.0.0.1") 161 | self.treeViewOutboundVmessAddressMode.setData(port, "443") 162 | 163 | def settingOutboundVmessPanelFromJSONFile(self, outboundVmessJSONFile=None, openFromJSONFile=False): 164 | logbook.setisOpenJSONFile(openFromJSONFile) 165 | self.treeViewOutboundVmessAddressMode.setRowCount(0) 166 | 167 | if (not outboundVmessJSONFile): 168 | outboundVmessJSONFile = {} 169 | 170 | try: 171 | outboundVmessJSONFile["vnext"] 172 | except KeyError as e: 173 | logbook.writeLog("OutboundVmess", "KeyError", e) 174 | outboundVmessJSONFile["vnext"] = [] 175 | 176 | addressNumber = len(outboundVmessJSONFile["vnext"]) 177 | 178 | if (addressNumber): 179 | addresses = outboundVmessJSONFile["vnext"] 180 | 181 | for i in range(addressNumber): 182 | try: 183 | users = addresses[i]["users"] 184 | usersNumber = len(users) 185 | serverAddress = QStandardItem(addresses[i]["address"]) 186 | serverPort = QStandardItem(str(addresses[i]["port"])) 187 | if (usersNumber): 188 | for j in range(usersNumber): 189 | try: 190 | uuidString = QStandardItem(str(users[j]["id"])) 191 | except Exception: uuidString = QStandardItem("") 192 | try: 193 | alterId = QStandardItem(str(users[j]["alterId"])) 194 | except Exception: alterId = QStandardItem("") 195 | try: 196 | security = QStandardItem(str(users[j]["security"])) 197 | except Exception: security = QStandardItem("") 198 | try: 199 | level = QStandardItem(str(users[j]["level"])) 200 | except Exception: level = QStandardItem("0") 201 | try: 202 | self.treasureChest.addLevel(users[j]["level"]) 203 | except Exception:pass 204 | serverAddress.appendRow((alterId, security, uuidString, level)) 205 | self.treeViewOutboundVmessAddressMode.appendRow((serverAddress, serverPort)) 206 | except KeyError as e: 207 | logbook.writeLog("OutboundVmess", "KeyError", e) 208 | except: 209 | logbook.writeLog("OutboundVmess", "unkonw") 210 | 211 | if (self.treeViewOutboundVmessAddressMode.rowCount() > 0): 212 | self.treeViewOutboundVmessAddress.setCurrentIndex(self.treeViewOutboundVmessAddressMode.index(0, 0)) 213 | 214 | def createOutboundVmessJSONFile(self): 215 | outboundVmessJSONFile = {} 216 | outboundVmessJSONFile["vnext"] = [] 217 | serversNumber = self.treeViewOutboundVmessAddressMode.rowCount() 218 | if (not serversNumber): return 219 | 220 | for i in range(serversNumber): 221 | server = {} 222 | user = {} 223 | server["address"] = self.treeViewOutboundVmessAddressMode.item(i, 0).text() 224 | server["port"] = int(self.treeViewOutboundVmessAddressMode.item(i, 1).text()) 225 | server["users"] = [] 226 | if (self.treeViewOutboundVmessAddressMode.hasChildren()): 227 | usersNumber = self.treeViewOutboundVmessAddressMode.item(i).rowCount() 228 | for j in range(usersNumber): 229 | user["id"] = self.treeViewOutboundVmessAddressMode.item(i).child(j, 2).text() 230 | user["alterId"] = int(self.treeViewOutboundVmessAddressMode.item(i).child(j, 0).text()) 231 | user["security"] = self.treeViewOutboundVmessAddressMode.item(i).child(j, 1).text() 232 | user["level"] = int(self.treeViewOutboundVmessAddressMode.item(i).child(j, 3).text()) 233 | try: 234 | self.treasureChest.addLevel(user["level"]) 235 | except Exception: 236 | pass 237 | server["users"].append(copy.deepcopy(user)) 238 | else: 239 | server["users"] = [] 240 | outboundVmessJSONFile["vnext"].append(copy.deepcopy(server)) 241 | 242 | return outboundVmessJSONFile 243 | 244 | def createUUID(self): 245 | return str(uuid.uuid4()) 246 | 247 | def validateUUID4(self, uuidString): 248 | try: 249 | uuid.UUID(uuidString, version=4) 250 | except ValueError: 251 | return False 252 | return True 253 | 254 | def clearOutVmessPanel(self): 255 | self.treeViewOutboundVmessAddressMode.setRowCount(0) 256 | 257 | def __DebugTest(self): 258 | import json 259 | print(json.dumps(self.createOutboundVmessJSONFile(), indent=4, sort_keys=False)) 260 | 261 | 262 | if __name__ == "__main__": 263 | from PyQt5.QtWidgets import QApplication 264 | app = QApplication(sys.argv) 265 | ex = OutboundVmessSettingPanel() 266 | ex.createOutboundVmessPanel() 267 | ex.setGeometry(300, 300, 580, 580) 268 | ex.show() 269 | sys.exit(app.exec_()) 270 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/policyTAB.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QWidget, QGroupBox, 4 | QVBoxLayout, QApplication, QLabel, QComboBox, 5 | QHBoxLayout, QSpinBox, QGridLayout, QPushButton, 6 | QCheckBox) 7 | from PyQt5.QtCore import QFileInfo, Qt, QCoreApplication 8 | import sys, copy 9 | 10 | v2rayshellDebug = False 11 | 12 | if __name__ == "__main__": 13 | v2rayshellDebug = True 14 | # this for debug test 15 | path = QFileInfo(sys.argv[0]) 16 | srcPath = path.absoluteFilePath().split("/") 17 | sys.path.append("/".join(srcPath[:-3])) 18 | 19 | from bridgehouse.editMap.port import treasureChest 20 | 21 | 22 | class policyTab(QWidget): 23 | 24 | def __init__(self, CaptainstreasureChest=False): 25 | super().__init__() 26 | self.policyJSONFile = { 27 | "levels": { 28 | "0": { 29 | "handshake": 4, 30 | "connIdle": 300, 31 | "uplinkOnly": 5, 32 | "downlinkOnly": 30, 33 | "statsUserUplink": False, 34 | "statsUserDownlink": False 35 | } 36 | } 37 | } 38 | 39 | self.template = { 40 | "handshake": 4, 41 | "connIdle": 300, 42 | "uplinkOnly": 5, 43 | "downlinkOnly": 30, 44 | "statsUserUplink": False, 45 | "statsUserDownlink": False 46 | } 47 | self.translate = QCoreApplication.translate 48 | if (CaptainstreasureChest): 49 | self.treasureChest = CaptainstreasureChest 50 | else: 51 | self.treasureChest = treasureChest.treasureChest() # a empty treasure chest 52 | self.__groupPolicyTitle = self.translate("policyTab", "Policy Setting") 53 | self.levels = {} 54 | 55 | def createPolicyTab(self): 56 | labelLevels = QLabel(self.translate("policyTab", "Levels: ")) 57 | self.comboBoxLevels = QComboBox() 58 | 59 | hboxLevels = QHBoxLayout() 60 | hboxLevels.addWidget(labelLevels) 61 | hboxLevels.addWidget(self.comboBoxLevels) 62 | hboxLevels.addStretch() 63 | 64 | labelhandshake = QLabel( 65 | self.translate("policyTab", "Handshake: ")) 66 | labelconnIdle = QLabel( 67 | self.translate("policyTab", "ConnIdle: ")) 68 | labeluplinkOnly = QLabel( 69 | self.translate("policyTab", "UplinkOnly: ")) 70 | labeldownlinkOnly = QLabel( 71 | self.translate("policyTab", "DownlinkOnly: ")) 72 | self.spinboxhandshake = QSpinBox() 73 | self.spinboxuplinkOnly = QSpinBox() 74 | self.spinboxconnIdle = QSpinBox() 75 | self.spinboxdownlinkOnly = QSpinBox() 76 | self.spinboxhandshake.setRange(0, 20) 77 | self.spinboxhandshake.setValue(4) 78 | self.spinboxconnIdle.setRange(0, 1500) 79 | self.spinboxconnIdle.setValue(300) 80 | self.spinboxuplinkOnly.setRange(0, 25) 81 | self.spinboxuplinkOnly.setValue(5) 82 | self.spinboxdownlinkOnly.setRange(0, 150) 83 | self.spinboxdownlinkOnly.setValue(30) 84 | 85 | self.checkboxStatsUserUplink = QCheckBox( 86 | self.translate("policyTab", "Stats User Uplink")) 87 | self.checkboxStatsUserDownlink = QCheckBox( 88 | self.translate("policyTab", "Stats User Downlink")) 89 | 90 | hboxhandshake = QHBoxLayout() 91 | hboxhandshake.addWidget(self.spinboxhandshake) 92 | hboxhandshake.addStretch() 93 | 94 | self.buttonPolicyApply = QPushButton( 95 | self.translate("policyTab", "Apply"), self) 96 | self.buttonPolicyDefault = QPushButton( 97 | self.translate("policyTab", "Default"), self) 98 | hboxButton = QHBoxLayout() 99 | hboxButton.addStretch() 100 | hboxButton.addWidget(self.buttonPolicyApply) 101 | hboxButton.addWidget(self.buttonPolicyDefault) 102 | 103 | gridBoxLevel = QGridLayout() 104 | gridBoxLevel.addWidget(labelhandshake, 0, 0, 1, 1, Qt.AlignLeft) 105 | gridBoxLevel.addLayout(hboxhandshake, 0, 1, 1, 1, Qt.AlignLeft) 106 | gridBoxLevel.addWidget(labelconnIdle, 1, 0, 1, 1, Qt.AlignLeft) 107 | gridBoxLevel.addWidget(self.spinboxconnIdle, 1, 1, 1, 1, Qt.AlignLeft) 108 | gridBoxLevel.addWidget(labeluplinkOnly, 2, 0, 1, 1, Qt.AlignLeft) 109 | gridBoxLevel.addWidget(self.spinboxuplinkOnly, 2, 1, 1, 1, Qt.AlignLeft) 110 | gridBoxLevel.addWidget(labeldownlinkOnly, 3, 0, 1, 1, Qt.AlignLeft) 111 | gridBoxLevel.addWidget(self.spinboxdownlinkOnly, 3, 1, 1, 1, Qt.AlignLeft) 112 | gridBoxLevel.addWidget(self.checkboxStatsUserUplink, 4, 0, 1, 1, Qt.AlignLeft) 113 | gridBoxLevel.addWidget(self.checkboxStatsUserDownlink, 5, 0, 1, 1, Qt.AlignLeft) 114 | gridBoxLevel.addLayout(hboxButton, 6, 2, 1, 2, Qt.AlignLeft) 115 | 116 | vboxLevels = QVBoxLayout() 117 | vboxLevels.addLayout(hboxLevels) 118 | vboxLevels.addLayout(gridBoxLevel) 119 | vboxLevels.addStretch() 120 | 121 | self.groupBoxPolicy = QGroupBox(self.__groupPolicyTitle, self) 122 | self.groupBoxPolicy.setLayout(vboxLevels) 123 | 124 | self.createPolcyTabSignals() 125 | 126 | if v2rayshellDebug: 127 | levels = ("0", "1", "2", "3", "4", "5", "6") 128 | self.comboBoxLevels.addItems(levels) 129 | self.__debugBtn = QPushButton("__debugTest", self) 130 | self.__debugBtn.clicked.connect(self.__debugTest) 131 | vboxLevels.addWidget(self.__debugBtn) 132 | return self.groupBoxPolicy 133 | 134 | return self.groupBoxPolicy 135 | 136 | def createPolcyTabSignals(self): 137 | self.treasureChest.updateList.updateLevelandEmail.connect(self.onupdatecomboBoxLevels) 138 | self.comboBoxLevels.activated[str].connect(self.settingLevelsSpinbox) 139 | self.buttonPolicyApply.clicked.connect(self.onbuttonPolicyApply) 140 | self.buttonPolicyDefault.clicked.connect(self.settingLevelsSpinboxDefault) 141 | self.spinboxconnIdle.valueChanged.connect(self.changegroupBoxPolicyTitle) 142 | self.spinboxdownlinkOnly.valueChanged.connect(self.changegroupBoxPolicyTitle) 143 | self.spinboxhandshake.valueChanged.connect(self.changegroupBoxPolicyTitle) 144 | self.spinboxuplinkOnly.valueChanged.connect(self.changegroupBoxPolicyTitle) 145 | 146 | def settingLevelsSpinbox(self, level): 147 | if level in self.levels.keys(): 148 | self.spinboxhandshake.setValue(self.levels[level]["handshake"]) 149 | self.spinboxconnIdle.setValue(self.levels[level]["connIdle"]) 150 | self.spinboxdownlinkOnly.setValue(self.levels[level]["downlinkOnly"]) 151 | self.spinboxuplinkOnly.setValue(self.levels[level]["uplinkOnly"]) 152 | self.checkboxStatsUserUplink.setChecked(True if self.levels[level]["statsUserUplink"] else False) 153 | self.checkboxStatsUserDownlink.setChecked(True if self.levels[level]["statsUserDownlink"] else False) 154 | self.groupBoxPolicy.setTitle("{}".format(self.__groupPolicyTitle)) 155 | else: 156 | self.settingLevelsSpinboxDefault() 157 | 158 | def onbuttonPolicyApply(self): 159 | currentLevel = self.comboBoxLevels.currentText() 160 | if currentLevel not in self.levels: self.levels[currentLevel] = {} 161 | self.levels[currentLevel]["handshake"] = self.spinboxhandshake.value() 162 | self.levels[currentLevel]["connIdle"] = self.spinboxconnIdle.value() 163 | self.levels[currentLevel]["downlinkOnly"] = self.spinboxdownlinkOnly.value() 164 | self.levels[currentLevel]["uplinkOnly"] = self.spinboxuplinkOnly.value() 165 | self.levels[currentLevel]["statsUserUplink"] = True if self.checkboxStatsUserUplink.isChecked() else False 166 | self.levels[currentLevel]["statsUserDownlink"] = True if self.checkboxStatsUserDownlink.isChecked() else False 167 | self.groupBoxPolicy.setTitle("{}".format(self.__groupPolicyTitle)) 168 | 169 | def settingLevelsSpinboxDefault(self): 170 | self.spinboxhandshake.setValue(self.template["handshake"]) 171 | self.spinboxconnIdle.setValue(self.template["connIdle"]) 172 | self.spinboxdownlinkOnly.setValue(self.template["downlinkOnly"]) 173 | self.spinboxuplinkOnly.setValue(self.template["uplinkOnly"]) 174 | self.checkboxStatsUserUplink.setChecked(False) 175 | self.checkboxStatsUserDownlink.setChecked(False) 176 | self.onbuttonPolicyApply() 177 | 178 | def changegroupBoxPolicyTitle(self): 179 | self.groupBoxPolicy.setTitle("{}{}".format(self.__groupPolicyTitle, "*")) 180 | 181 | def onupdatecomboBoxLevels(self): 182 | self.comboBoxLevels.clear() 183 | allLevels = copy.deepcopy(self.treasureChest.getLevels()) 184 | 185 | if allLevels: 186 | # add new Levels 187 | for i in allLevels: 188 | if i not in self.levels.keys(): 189 | self.levels[i] = copy.deepcopy(self.template) 190 | 191 | # delete obsolete Levels 192 | for i in self.levels.keys(): 193 | if i not in allLevels: 194 | del self.levels[i] 195 | continue 196 | self.comboBoxLevels.addItem(i) 197 | 198 | def settingPolicyTabFromJSONFile(self, policyJSONFile): 199 | self.levels.clear() 200 | self.comboBoxLevels.clear() 201 | 202 | if (not policyJSONFile): policyJSONFile = {} 203 | 204 | for i, v in policyJSONFile.items(): 205 | try: 206 | int(i) 207 | except Exception: 208 | continue 209 | 210 | try: 211 | v["handshake"] 212 | except Exception: 213 | v["handshake"] = 4 214 | 215 | try: 216 | v["connIdle"] 217 | except Exception: 218 | v["connIdle"] = 300 219 | 220 | try: 221 | v["uplinkOnly"] 222 | except Exception: 223 | v["uplinkOnly"] = 5 224 | 225 | try: 226 | v["downlinkOnly"] 227 | except Exception: 228 | v["downlinkOnly"] = 30 229 | 230 | try: 231 | v["statsUserUplink"] 232 | except Exception: 233 | v["statsUserUplink"] = False 234 | 235 | try: 236 | v["statsUserDownlink"] 237 | except Exception: 238 | v["statsUserDownlink"] = False 239 | 240 | try: 241 | self.levels[str(i)] = copy.deepcopy(v) 242 | except Exception: 243 | pass 244 | self.comboBoxLevels.addItem(str(i)) 245 | 246 | if self.levels: 247 | for i in self.levels.items(): 248 | try: 249 | self.settingLevelsSpinbox(i[0]) 250 | except Exception: 251 | pass 252 | break 253 | 254 | def createPolicyJSONFile(self): 255 | return self.levels 256 | 257 | def __debugTest(self): 258 | import json 259 | print(json.dumps(self.createPolicyJSONFile(), indent=4, sort_keys=False)) 260 | 261 | 262 | if __name__ == "__main__": 263 | app = QApplication(sys.argv) 264 | ex = policyTab() 265 | vbox = QVBoxLayout() 266 | vbox.addWidget(ex.createPolicyTab()) 267 | ex.setLayout(vbox) 268 | ex.setGeometry(200, 100, 250, 200) 269 | ex.show() 270 | sys.exit(app.exec_()) 271 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/port/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from bridgehouse.editMap import logTAB 4 | 5 | logbook = logTAB.logBook() 6 | 7 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/port/updateListSignal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtCore import pyqtSignal, QObject 4 | 5 | 6 | class updateListSignal(QObject): 7 | """ 8 | when the outbound tag had added or changed. 9 | will emit a signal to outbound->(Proxy Setting) and inbound->vmess->(Detour To outboundDetuour) update the outbound tags 10 | 11 | when have new level or email will emit to they panel. fresh the list. 12 | """ 13 | setInboundTag = pyqtSignal() 14 | updateLevelandEmail = pyqtSignal() 15 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/router/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 -------------------------------------------------------------------------------- /src/bridgehouse/editMap/router/codegen.py: -------------------------------------------------------------------------------- 1 | import os 2 | import grpc_tools.protoc as proto # python -m pip install grpcio-tools 3 | 4 | protoFiles = [x for x in os.listdir() if x.endswith(".proto") and os.path.isfile(x)] 5 | 6 | for p in protoFiles: 7 | proto.main("--proto_path=./ --python_out=./ --grpc_python_out=./ {}".format(p).split()) 8 | 9 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/router/geoSite.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | enum Network { 4 | Unknown = 0; 5 | 6 | RawTCP = 1 [deprecated=true]; 7 | TCP = 2; 8 | UDP = 3; 9 | } 10 | 11 | // NetworkList is a list of Networks. 12 | message NetworkList { 13 | repeated Network network = 1; 14 | } 15 | 16 | // PortRange represents a range of ports. 17 | message PortRange { 18 | // The port that this range starts from. 19 | uint32 From = 1; 20 | // The port that this range ends with (inclusive). 21 | uint32 To = 2; 22 | } 23 | 24 | 25 | // Domain for routing decision. 26 | message Domain { 27 | // Type of domain value. 28 | enum Type { 29 | // The value is used as is. 30 | Plain = 0; 31 | // The value is used as a regular expression. 32 | Regex = 1; 33 | // The value is a domain. 34 | Domain = 2; 35 | } 36 | 37 | // Domain matching type. 38 | Type type = 1; 39 | 40 | // Domain value. 41 | string value = 2; 42 | } 43 | 44 | // IP for routing decision, in CIDR form. 45 | message CIDR { 46 | // IP address, should be either 4 or 16 bytes. 47 | bytes ip = 1; 48 | 49 | // Number of leading ones in the network mask. 50 | uint32 prefix = 2; 51 | } 52 | 53 | message GeoIP { 54 | string country_code = 1; 55 | repeated CIDR cidr = 2; 56 | } 57 | 58 | message GeoIPList { 59 | repeated GeoIP entry = 1; 60 | } 61 | 62 | message GeoSite { 63 | string country_code = 1; 64 | repeated Domain domain = 2; 65 | } 66 | 67 | message GeoSiteList{ 68 | repeated GeoSite entry = 1; 69 | } 70 | 71 | message RoutingRule { 72 | string tag = 1; 73 | repeated Domain domain = 2; 74 | repeated CIDR cidr = 3; 75 | PortRange port_range = 4; 76 | NetworkList network_list = 5; 77 | repeated CIDR source_cidr = 6; 78 | repeated string user_email = 7; 79 | repeated string inbound_tag = 8; 80 | } 81 | 82 | message Config { 83 | enum DomainStrategy { 84 | // Use domain as is. 85 | AsIs = 0; 86 | 87 | // Always resolve IP for domains. 88 | UseIp = 1; 89 | 90 | // Resolve to IP if the domain doesn't match any rules. 91 | IpIfNonMatch = 2; 92 | 93 | // Resolve to IP if any rule requires IP matching. 94 | IpOnDemand = 3; 95 | } 96 | DomainStrategy domain_strategy = 1; 97 | repeated RoutingRule rule = 2; 98 | } 99 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/router/geoSite_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/router/readgfwlist.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from base64 import b64decode 4 | 5 | def openGWFLIST(path): 6 | gwfList = [] 7 | with open(path, "r") as f: 8 | gwflistBase64 = f.read() 9 | 10 | if gwflistBase64: 11 | for i in b64decode(gwflistBase64).decode("utf-8").split("\n"): 12 | # https://adblockplus.org/zh_CN/filters 13 | if i.startswith("!#"): 14 | continue 15 | if i.startswith("!"): # remove Comments 16 | continue 17 | if not i: # remove empty 18 | continue 19 | if i.startswith("[AutoProxy"): 20 | continue 21 | if i.startswith("@@"): # remove Exception rules 22 | continue 23 | if i.startswith("!##############General List End#################"): 24 | break 25 | else: 26 | gwfList.append(i) 27 | del gwflistBase64 28 | return gwfList 29 | 30 | 31 | if __name__ == "__main__": 32 | openGWFLIST("gfwlist.txt") 33 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/routingTAB.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QWidget, QTextEdit, 4 | QVBoxLayout, QApplication) 5 | from PyQt5.QtCore import QFileInfo, QCoreApplication 6 | import sys, json, copy 7 | 8 | v2rayshellDebug = False 9 | 10 | if __name__ == "__main__": 11 | v2rayshellDebug = True 12 | # this for debug test 13 | path = QFileInfo(sys.argv[0]) 14 | srcPath = path.absoluteFilePath().split("/") 15 | sys.path.append("/".join(srcPath[:-3])) 16 | 17 | from bridgehouse.editMap.router import geoSiteEditorPanel 18 | 19 | 20 | class routingTab(QWidget): 21 | 22 | def __init__(self): 23 | super().__init__() 24 | self.routingJSONFile = { 25 | "strategy": "rules", 26 | "settings": { 27 | "domainStrategy": "IPIfNonMatch", 28 | "rules": [ 29 | { 30 | "type": "field", 31 | "port": "1-52", 32 | "outboundTag": "direct" 33 | }, 34 | { 35 | "type": "field", 36 | "port": "54-79", 37 | "outboundTag": "direct" 38 | }, 39 | { 40 | "type": "field", 41 | "port": "81-442", 42 | "outboundTag": "direct" 43 | }, 44 | { 45 | "type": "field", 46 | "port": "444-65535", 47 | "outboundTag": "direct" 48 | }, 49 | { 50 | "type": "chinasites", 51 | "outboundTag": "direct" 52 | }, 53 | { 54 | "type": "field", 55 | "ip": [ 56 | "0.0.0.0/8", 57 | "10.0.0.0/8", 58 | "100.64.0.0/10", 59 | "127.0.0.0/8", 60 | "169.254.0.0/16", 61 | "172.16.0.0/12", 62 | "192.0.0.0/24", 63 | "192.0.2.0/24", 64 | "192.168.0.0/16", 65 | "198.18.0.0/15", 66 | "198.51.100.0/24", 67 | "203.0.113.0/24", 68 | "::1/128", 69 | "fc00::/7", 70 | "fe80::/10" 71 | ], 72 | "outboundTag": "direct" 73 | }, 74 | { 75 | "type": "chinaip", 76 | "outboundTag": "direct" 77 | } 78 | ] 79 | } 80 | } 81 | self.translate = QCoreApplication.translate 82 | 83 | def createRoutingTab(self): 84 | self.textEditRoutingJSONFile = QTextEdit() 85 | self.textEditRoutingJSONFile.setReadOnly(True) 86 | self.textEditRoutingJSONFile.setVisible(False) 87 | 88 | self.textEditRoutingJSONFile.setPlainText( 89 | json.dumps(self.routingJSONFile, indent=4, sort_keys=False)) 90 | 91 | self.geoSiteEditorPanel = geoSiteEditorPanel.GeoSiteEditorPanel() 92 | self.panel = self.geoSiteEditorPanel.createGeoSiteEditorPanel() 93 | 94 | return self.panel 95 | 96 | def settingRoutingTABFromJSONFile(self, routingJSONFile=None, openFromJSONFile=False): 97 | 98 | if (not routingJSONFile): routingJSONFile = self.routingJSONFile 99 | 100 | if (openFromJSONFile and routingJSONFile): 101 | self.textEditRoutingJSONFile.clear() 102 | try: 103 | self.textEditRoutingJSONFile.setText( 104 | json.dumps(routingJSONFile, indent=4, sort_keys=False)) 105 | except Exception: 106 | pass 107 | elif (not openFromJSONFile): 108 | # use default settings 109 | self.textEditRoutingJSONFile.setText( 110 | json.dumps(self.routingJSONFile, indent=4, sort_keys=False)) 111 | 112 | def createRoutingJSONFile(self): 113 | try: 114 | routingJSONFile = copy.deepcopy( 115 | json.loads(self.textEditRoutingJSONFile.toPlainText())) 116 | except Exception: 117 | routingJSONFile = {} 118 | 119 | return routingJSONFile 120 | 121 | 122 | if __name__ == "__main__": 123 | app = QApplication(sys.argv) 124 | ex = routingTab() 125 | v = QVBoxLayout() 126 | v.addWidget(ex.createRoutingTab()) 127 | ex.setLayout(v) 128 | ex.setGeometry(200, 100, 800, 768) 129 | ex.show() 130 | sys.exit(app.exec_()) 131 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/statsTAB.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # this feature has not support, it's good for servers, not client side -------------------------------------------------------------------------------- /src/bridgehouse/editMap/toolbox/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/v2ray/V2Ray-shell_alpha/df72d9cc8c4dfb0fa7215580bdd16f76edcf7fbe/src/bridgehouse/editMap/toolbox/__init__.py -------------------------------------------------------------------------------- /src/bridgehouse/editMap/toolbox/toolbox.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import QModelIndex, Qt, pyqtSignal, QObject, QTimer 2 | from PyQt5.QtGui import QStandardItemModel 3 | from PyQt5.QtWidgets import (QApplication, QStyledItemDelegate, 4 | QTableView, QWidget, QPushButton, QLineEdit, QStyle, QSpinBox, 5 | QComboBox) 6 | import uuid 7 | 8 | class UUIDLineEdit(QLineEdit): 9 | def __init__(self, parent=None, btnName=None): 10 | QLineEdit.__init__(self, parent) 11 | 12 | self.btn = QPushButton(btnName, self) 13 | 14 | frameWidth = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth) 15 | self.setStyleSheet('QLineEdit {{ padding-left: {}px; }} '.format( 16 | self.btn.sizeHint().width() + frameWidth + 1)) 17 | msz = self.minimumSizeHint() 18 | self.setMinimumSize(max(msz.width(), self.btn.sizeHint().height() + frameWidth * 2 + 2), 19 | max(msz.height(), self.btn.sizeHint().height() + frameWidth * 2 + 2)) 20 | self.setInputMask("HHHHHHHH-HHHH-HHHH-HHHH-HHHHHHHHHHHH; ") 21 | self.btn.clicked.connect(lambda: self.setText(str(uuid.uuid4()))) 22 | 23 | 24 | class UUIDLineEditDelegate(QStyledItemDelegate): 25 | def __init__(self, btnName=None): 26 | super(UUIDLineEditDelegate, self).__init__() 27 | self.btnName = btnName 28 | 29 | def createEditor(self, parent, option, index): 30 | editor = UUIDLineEdit(parent, self.btnName) 31 | return editor 32 | 33 | def setEditorData(self, lineEdit, index): 34 | value = index.model().data(index, Qt.EditRole) 35 | lineEdit.setText(str(value)) 36 | 37 | def setModelData(self, lineEdit, model, index): 38 | value = lineEdit.text() 39 | model.setData(index, value, Qt.EditRole) 40 | 41 | def updateEditorGeometry(self, lineEdit, option, index): 42 | lineEdit.setGeometry(option.rect) 43 | 44 | 45 | class SpinBoxDelegate(QStyledItemDelegate): 46 | def __init__(self, min=False, max=False): 47 | super(SpinBoxDelegate, self).__init__() 48 | self.min, self.max = min, max 49 | 50 | def createEditor(self, parent, option, index): 51 | editor = QSpinBox(parent) 52 | editor.setFrame(False) 53 | editor.setMinimum(int(self.min)) 54 | editor.setMaximum(int(self.max)) 55 | editor.setValue(0) 56 | 57 | return editor 58 | 59 | def setEditorData(self, spinBox, index): 60 | value = index.model().data(index, Qt.EditRole) 61 | 62 | spinBox.setValue(0 if not value else value) 63 | 64 | def setModelData(self, spinBox, model, index): 65 | spinBox.interpretText() 66 | value = spinBox.value() 67 | 68 | model.setData(index, value, Qt.EditRole) 69 | 70 | def updateEditorGeometry(self, editor, option, index): 71 | editor.setGeometry(option.rect) 72 | 73 | 74 | class ComboBoxSpinBoxDelegate(QStyledItemDelegate): 75 | def __init__(self, comboList=None): 76 | super(ComboBoxSpinBoxDelegate, self).__init__() 77 | self.comboList = comboList 78 | 79 | def createEditor(self, parent, option, index): 80 | if index.parent().row() != -1: 81 | editor = QComboBox(parent) 82 | if self.comboList: 83 | editor.addItems(self.comboList) 84 | 85 | return editor 86 | else: 87 | editor = QSpinBox(parent) 88 | editor.setMaximum(65535) 89 | editor.setMinimum(0) 90 | editor.setValue(443) 91 | return editor 92 | 93 | def setEditorData(self, comboBox, index): 94 | value = index.model().data(index, Qt.EditRole) 95 | if index.parent().row() != -1 and value in self.comboList: 96 | comboBox.setCurrentText(value) 97 | else: 98 | try: 99 | comboBox.setValue(int(value)) 100 | except: 101 | pass 102 | 103 | def setModelData(self, comboBox, model, index): 104 | if index.parent().row() != -1: 105 | value = comboBox.currentText() 106 | else: 107 | value = comboBox.value() 108 | model.setData(index, value, Qt.EditRole) 109 | 110 | def updateEditorGeometry(self, editor, option, index): 111 | editor.setGeometry(option.rect) 112 | 113 | class ComboBoxDelegate(QStyledItemDelegate): 114 | def __init__(self, comboList=None): 115 | super(ComboBoxDelegate, self).__init__() 116 | self.comboList = comboList 117 | 118 | def createEditor(self, parent, option, index): 119 | editor = QComboBox(parent) 120 | if self.comboList: 121 | editor.addItems(self.comboList) 122 | 123 | return editor 124 | 125 | def setEditorData(self, comboBox, index): 126 | value = index.model().data(index, Qt.EditRole) 127 | if value in self.comboList: 128 | comboBox.setCurrentText(value) 129 | 130 | def setModelData(self, comboBox, model, index): 131 | value = comboBox.currentText() 132 | 133 | model.setData(index, value, Qt.EditRole) 134 | 135 | def updateEditorGeometry(self, editor, option, index): 136 | editor.setGeometry(option.rect) 137 | 138 | 139 | class MyComboBox(QComboBox, QObject): 140 | previousTextChanged = pyqtSignal(str, str, name="previousTextChanged") 141 | 142 | def __init__(self, parent=None): 143 | super(MyComboBox, self).__init__() 144 | self.currentTextChanged.connect(self.previousTextChangedSlot) 145 | self.oldText = None 146 | 147 | def previousTextChangedSlot(self, newText): 148 | oldText, self.oldText = self.oldText, newText 149 | self.previousTextChanged.emit(oldText, newText) 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/transport/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from bridgehouse.editMap import logTAB 4 | 5 | logbook = logTAB.logBook() 6 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/transport/certificatesPanel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/transport/dsPanel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QLabel, QWidget, QGroupBox, 4 | QLineEdit, QHBoxLayout, QVBoxLayout, 5 | QPushButton) 6 | from PyQt5.QtCore import QFileInfo, QCoreApplication 7 | import sys 8 | 9 | v2rayshellDebug = False 10 | 11 | if __name__ == "__main__": 12 | v2rayshellDebug = True 13 | # this for debug test 14 | path = QFileInfo(sys.argv[0]) 15 | srcPath = path.absoluteFilePath().split("/") 16 | sys.path.append("/".join(srcPath[:-4])) 17 | 18 | class domainSocketPanel(QWidget): 19 | 20 | def __init__(self): 21 | super().__init__() 22 | self.domainSocketJSONFile = { 23 | "path": "/path/to/ds/file" 24 | } 25 | self.translate = QCoreApplication.translate 26 | 27 | def createdsSettingPanel(self): 28 | labelPath = QLabel(self.translate("domainSocketPanel", 'Path:'), self) 29 | self.lineEditPath = QLineEdit(self) 30 | 31 | vbox = QVBoxLayout() 32 | vbox.addWidget(labelPath) 33 | vbox.addWidget(self.lineEditPath) 34 | 35 | self.groupBoxdsSetting = groupBoxdsSetting = QGroupBox( 36 | self.translate("domainSocketPanel", "Domain Socket Setting"), self) 37 | groupBoxdsSetting.setCheckable(True) 38 | groupBoxdsSetting.setChecked(False) 39 | groupBoxdsSetting.setLayout(vbox) 40 | 41 | if (v2rayshellDebug): 42 | self.__debugBtn = QPushButton("__debugTest", self) 43 | self.__debugBtn.clicked.connect(self.__debugTest) 44 | vbox.addWidget(self.__debugBtn) 45 | 46 | return groupBoxdsSetting 47 | 48 | def settingdsPanelFromJSONFile(self, dsJSONFile, openFromJSONFile=False): 49 | 50 | if not dsJSONFile: 51 | dsJSONFile = {} 52 | self.cleardsPanel() 53 | 54 | try: 55 | dsJSONFile['path'] 56 | except KeyError: 57 | dsJSONFile['path'] = '' 58 | 59 | self.lineEditPath.setText(str(dsJSONFile['path'])) 60 | 61 | def createdsSettingJSONFile(self): 62 | dsJSONFile = {} 63 | dsJSONFile['path'] = self.lineEditPath.text() 64 | 65 | return dsJSONFile 66 | 67 | def cleardsPanel(self): 68 | self.groupBoxdsSetting.setChecked(False) 69 | self.lineEditPath.clear() 70 | 71 | def __debugTest(self): 72 | import json 73 | print(json.dumps(self.createdsSettingJSONFile(), indent=4, sort_keys=False)) 74 | 75 | 76 | if __name__ == "__main__": 77 | from PyQt5.QtWidgets import QApplication 78 | app = QApplication(sys.argv) 79 | ex = domainSocketPanel() 80 | ex.createdsSettingPanel() 81 | ex.setGeometry(300, 300, 680, 230) 82 | ex.show() 83 | sys.exit(app.exec_()) 84 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/transport/http2Panel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QLabel, QWidget, QGroupBox, 4 | QLineEdit, QGridLayout, QPushButton) 5 | from PyQt5.QtCore import QFileInfo, QCoreApplication 6 | import sys, re 7 | 8 | v2rayshellDebug = False 9 | 10 | if __name__ == "__main__": 11 | v2rayshellDebug = True 12 | # this for debug test 13 | path = QFileInfo(sys.argv[0]) 14 | srcPath = path.absoluteFilePath().split("/") 15 | sys.path.append("/".join(srcPath[:-4])) 16 | 17 | from bridgehouse.editMap.transport import logbook 18 | 19 | 20 | class http2Panel(QWidget): 21 | def __init__(self): 22 | super(http2Panel, self).__init__() 23 | self.httpJSONFile = { 24 | "host": ["v2ray.com"], 25 | "path": "/random/path" 26 | } 27 | self.translate = QCoreApplication.translate 28 | 29 | def createHttpSettingPanel(self): 30 | labelHost = QLabel(self.translate("http2Panel", "Host: "), self) 31 | self.lineEditHost = QLineEdit() 32 | 33 | labelPath = QLabel(self.translate("http2Panel", "Path: "), self) 34 | self.lineEditPath = QLineEdit() 35 | 36 | grid = QGridLayout(self) 37 | grid.addWidget(labelHost,0, 0) 38 | grid.addWidget(self.lineEditHost, 0, 1) 39 | grid.addWidget(labelPath, 1, 0) 40 | grid.addWidget(self.lineEditPath, 1, 1) 41 | 42 | self.groupBoxhttp = box = QGroupBox(self.translate("http2Panel", "http Settings "), self) 43 | box.setCheckable(True) 44 | box.setChecked(False) 45 | box.setLayout(grid) 46 | 47 | if (v2rayshellDebug): 48 | self.__debugBtn = QPushButton("__debugTest", self) 49 | self.__debugBtn.clicked.connect(self.__debugTest) 50 | grid.addWidget(self.__debugBtn, 2, 0) 51 | self.settingHttpPanelFromJSONFile(self.httpJSONFile, True) 52 | 53 | return box 54 | 55 | def settingHttpPanelFromJSONFile(self, httpJSONFile, openFromJSONFile=False): 56 | logbook.setisOpenJSONFile(openFromJSONFile) 57 | 58 | if (not httpJSONFile): 59 | self.lineEditHost.clear() 60 | self.lineEditPath.clear() 61 | self.groupBoxhttp.setChecked(False) 62 | del httpJSONFile 63 | return False 64 | 65 | try: 66 | httpJSONFile["host"] 67 | except Exception: 68 | httpJSONFile["host"] = [] 69 | 70 | try: 71 | httpJSONFile["path"] 72 | except Exception: 73 | httpJSONFile["path"] = "" 74 | 75 | 76 | if (httpJSONFile["host"]): 77 | self.lineEditHost.setText(", ".join([str(x) for x in httpJSONFile["host"]])) 78 | 79 | if (httpJSONFile["path"]): 80 | self.lineEditPath.setText(httpJSONFile["path"]) 81 | 82 | def createHttpSettingJSONFile(self): 83 | httpJSONFile = {} 84 | httpJSONFile["host"] = [x.strip() for x in re.split(r"[,;]", self.lineEditHost.text())] 85 | httpJSONFile["path"] = self.lineEditPath.text() 86 | 87 | return httpJSONFile 88 | 89 | def clearHttpPanel(self): 90 | self.lineEditHost.clear() 91 | self.lineEditPath.clear() 92 | self.groupBoxhttp.setChecked(False) 93 | 94 | def __debugTest(self): 95 | import json 96 | print(json.dumps(self.createHttpSettingJSONFile(), indent=4, sort_keys=False)) 97 | 98 | if __name__ == "__main__": 99 | from PyQt5.QtWidgets import QApplication 100 | app = QApplication(sys.argv) 101 | ex = http2Panel() 102 | ex.createHttpSettingPanel() 103 | ex.setGeometry(300, 300, 680, 230) 104 | ex.show() 105 | sys.exit(app.exec_()) 106 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/transport/mkcpPanel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QLabel, QSpinBox, QComboBox, QCheckBox, QWidget, 4 | QGridLayout, QGroupBox, QPushButton) 5 | from PyQt5.QtCore import QFileInfo, QCoreApplication 6 | 7 | v2rayshellDebug = False 8 | import sys 9 | 10 | if __name__ == "__main__": 11 | v2rayshellDebug = True 12 | # this for debug test 13 | path = QFileInfo(sys.argv[0]) 14 | srcPath = path.absoluteFilePath().split("/") 15 | sys.path.append("/".join(srcPath[:-4])) 16 | 17 | from bridgehouse.editMap.transport import logbook 18 | 19 | 20 | class mKcpPanel(QWidget): 21 | 22 | def __init__(self): 23 | super().__init__() 24 | self.mKcpJSONFile = { 25 | "mtu": 1355, 26 | "tti": 50, 27 | "uplinkCapacity": 5, 28 | "downlinkCapacity": 20, 29 | "congestion": False, 30 | "readBufferSize": 2, 31 | "writeBufferSize": 2, 32 | "header": { 33 | "type": "none" 34 | } 35 | } 36 | self.translate = QCoreApplication.translate 37 | self.headerTypes = ("none", "srtp", "uTP", "wechat-video") 38 | 39 | def createmKcpSettingPanel(self): 40 | labelMTU = QLabel( 41 | self.translate("mKcpPanel", "MTU: "), self) 42 | self.spinBoxmKcpMTU = QSpinBox() 43 | labelTTI = QLabel( 44 | self.translate("mKcpPanel", "TTI: "), self) 45 | self.spinBoxmKcpTTI = QSpinBox() 46 | labelUplinkCapacity = QLabel( 47 | self.translate("mKcpPanel", "Uplink Capacity: "), self) 48 | self.spinBoxUpCapacity = QSpinBox() 49 | labelDownlinckCapacity = QLabel( 50 | self.translate("mKcpPanel", "Downlink Capacity: "), self) 51 | self.spinBoxDownCapacity = QSpinBox() 52 | labelReadBufferSize = QLabel( 53 | self.translate("mKcpPanel", "Read Buffer Size: "), self) 54 | self.spinBoxRdBufferSize = QSpinBox() 55 | labelWriteBufferSize = QLabel( 56 | self.translate("mKcpPanel", "Write Buffer Size: "), self) 57 | self.spinBoxWrBufferSize = QSpinBox() 58 | self.checkBoxCongestion = QCheckBox( 59 | self.translate("mKcpPanel", "Congestion"), self) 60 | labelHeaderType = QLabel( 61 | self.translate("mKcpPanel", "Header Type: "), self) 62 | self.comboBoxHeader = QComboBox() 63 | 64 | self.spinBoxmKcpMTU.setRange(576, 1460) 65 | self.spinBoxmKcpMTU.setValue(1335) 66 | self.spinBoxmKcpTTI.setRange(10, 100) 67 | self.spinBoxmKcpTTI.setValue(50) 68 | self.spinBoxUpCapacity.setRange(0, 9212500) # Francesco Poletti 73.7 Tb/s 69 | self.spinBoxUpCapacity.setValue(5) 70 | self.spinBoxDownCapacity.setRange(0, 9212500) 71 | self.spinBoxDownCapacity.setValue(20) 72 | self.spinBoxRdBufferSize.setRange(0, 230584301) # 2.30584301 * 10^12 (2^64 bit) 73 | self.spinBoxRdBufferSize.setValue(2) 74 | self.spinBoxWrBufferSize.setRange(0, 230584301) 75 | self.spinBoxWrBufferSize.setValue(2) 76 | self.comboBoxHeader.addItems(self.headerTypes) 77 | 78 | gridBoxmKCP = QGridLayout(self) 79 | gridBoxmKCP.addWidget(labelMTU, 0, 0) 80 | gridBoxmKCP.addWidget(self.spinBoxmKcpMTU, 0, 1) 81 | gridBoxmKCP.addWidget(labelTTI, 0, 2) 82 | gridBoxmKCP.addWidget(self.spinBoxmKcpTTI, 0, 3) 83 | 84 | gridBoxmKCP.addWidget(labelUplinkCapacity, 1, 0) 85 | gridBoxmKCP.addWidget(self.spinBoxUpCapacity, 1, 1) 86 | gridBoxmKCP.addWidget(labelDownlinckCapacity, 1, 2) 87 | gridBoxmKCP.addWidget(self.spinBoxDownCapacity, 1, 3) 88 | 89 | gridBoxmKCP.addWidget(labelReadBufferSize, 2, 0) 90 | gridBoxmKCP.addWidget(self.spinBoxRdBufferSize, 2, 1) 91 | gridBoxmKCP.addWidget(labelWriteBufferSize, 2, 2) 92 | gridBoxmKCP.addWidget(self.spinBoxWrBufferSize, 2, 3) 93 | gridBoxmKCP.addWidget(self.checkBoxCongestion, 3, 0) 94 | gridBoxmKCP.addWidget(labelHeaderType, 4, 0) 95 | gridBoxmKCP.addWidget(self.comboBoxHeader, 4, 1) 96 | 97 | self.groupBoxmKCPSetting = groupBoxmKCPSetting = QGroupBox( 98 | self.translate("mKcpPanel", "mKcp Setting"), self) 99 | groupBoxmKCPSetting.setCheckable(True) 100 | groupBoxmKCPSetting.setChecked(False) 101 | groupBoxmKCPSetting.adjustSize() 102 | groupBoxmKCPSetting.setLayout(gridBoxmKCP) 103 | 104 | if (v2rayshellDebug): 105 | self.__debugBtn = QPushButton("__debugTest", self) 106 | gridBoxmKCP.addWidget(self.__debugBtn, 5, 0) 107 | self.__debugBtn.clicked.connect(self.__debugTest) 108 | self.settingmKcpPanelFromJSONFile(self.mKcpJSONFile, True) 109 | 110 | return groupBoxmKCPSetting 111 | 112 | def settingmKcpPanelFromJSONFile(self, mKcpJSONFile, openFromJSONFile=False): 113 | logbook.setisOpenJSONFile(openFromJSONFile) 114 | 115 | if (not mKcpJSONFile): 116 | mKcpJSONFile = {} 117 | self.groupBoxmKCPSetting.setChecked(False) 118 | return False 119 | 120 | try: 121 | mKcpJSONFile["mtu"] 122 | except KeyError as e: 123 | logbook.writeLog("mKcp", "KeyError", e) 124 | mKcpJSONFile["mtu"] = 1355 125 | 126 | try: 127 | mKcpJSONFile["tti"] 128 | except KeyError as e: 129 | logbook.writeLog("mKcp", "KeyError", e) 130 | mKcpJSONFile["tti"] = 50 131 | 132 | try: 133 | mKcpJSONFile["uplinkCapacity"] 134 | except KeyError as e: 135 | logbook.writeLog("mKcp", "KeyError", e) 136 | mKcpJSONFile["uplinkCapacity"] = 5 137 | 138 | try: 139 | mKcpJSONFile["downlinkCapacity"] 140 | except KeyError as e: 141 | logbook.writeLog("mKcp", "KeyError", e) 142 | mKcpJSONFile["downlinkCapacity"] = 20 143 | 144 | try: 145 | mKcpJSONFile["congestion"] 146 | except KeyError as e: 147 | logbook.writeLog("mKcp", "KeyError", e) 148 | mKcpJSONFile["congestion"] = False 149 | 150 | try: 151 | mKcpJSONFile["readBufferSize"] 152 | except KeyError as e: 153 | logbook.writeLog("mKcp", "KeyError", e) 154 | mKcpJSONFile["readBufferSize"] = 2 155 | 156 | try: 157 | mKcpJSONFile["writeBufferSize"] 158 | except KeyError as e: 159 | logbook.writeLog("mKcp", "KeyError", e) 160 | mKcpJSONFile["writeBufferSize"] = 2 161 | 162 | try: 163 | mKcpJSONFile["header"] 164 | except KeyError as e: 165 | logbook.writeLog("mKcp", "KeyError", e) 166 | mKcpJSONFile["header"] = {} 167 | 168 | try: 169 | mKcpJSONFile["header"]["type"] 170 | except KeyError as e: 171 | logbook.writeLog("mKcp", "KeyError", e) 172 | mKcpJSONFile["header"]["type"] = "none" 173 | 174 | try: 175 | self.spinBoxmKcpMTU.setValue(int(mKcpJSONFile["mtu"])) 176 | except ValueError as e: 177 | logbook.writeLog("mKcp", "ValueError", e) 178 | mKcpJSONFile["mtu"] = 1355 179 | 180 | try: 181 | self.spinBoxmKcpTTI.setValue(int(mKcpJSONFile["tti"])) 182 | except ValueError as e: 183 | logbook.writeLog("mKcp", "ValueError", e) 184 | mKcpJSONFile["tti"] = 50 185 | 186 | try: 187 | self.spinBoxUpCapacity.setValue(int(mKcpJSONFile["uplinkCapacity"])) 188 | except ValueError as e: 189 | logbook.writeLog("mKcp", "ValueError", e) 190 | mKcpJSONFile["uplinkCapacity"] = 5 191 | 192 | try: 193 | self.spinBoxDownCapacity.setValue(int(mKcpJSONFile["downlinkCapacity"])) 194 | except ValueError as e: 195 | logbook.writeLog("mKcp", "ValueError", e) 196 | mKcpJSONFile["downlinkCapacity"] = 20 197 | 198 | try: 199 | self.checkBoxCongestion.setChecked(mKcpJSONFile["congestion"]) 200 | except ValueError as e: 201 | logbook.writeLog("mKcp", "ValueError", e) 202 | mKcpJSONFile["congestion"] = False 203 | 204 | try: 205 | self.spinBoxRdBufferSize.setValue(int(mKcpJSONFile["readBufferSize"])) 206 | except ValueError as e: 207 | logbook.writeLog("mKcp", "ValueError", e) 208 | mKcpJSONFile["readBufferSize"] = 2 209 | 210 | try: 211 | self.spinBoxWrBufferSize.setValue(int(mKcpJSONFile["writeBufferSize"])) 212 | except ValueError as e: 213 | logbook.writeLog("mKcp", "ValueError", e) 214 | mKcpJSONFile["writeBufferSize"] = 2 215 | 216 | try: 217 | self.comboBoxHeader.setCurrentText(mKcpJSONFile["header"]["type"]) 218 | except ValueError as e: 219 | logbook.writeLog("mKcp", "ValueError", e) 220 | except TypeError as e: 221 | logbook.writeLog("mKcp", "TypeError", e) 222 | except: 223 | logbook.writeLog("mKcp", "unkonw Error") 224 | 225 | def createmKcpSettingJSONFile(self): 226 | mKcpJSONFile = {} 227 | 228 | mKcpJSONFile["mtu"] = self.spinBoxmKcpMTU.value() 229 | mKcpJSONFile["tti"] = self.spinBoxmKcpTTI.value() 230 | mKcpJSONFile["uplinkCapacity"] = self.spinBoxUpCapacity.value() 231 | mKcpJSONFile["downlinkCapacity"] = self.spinBoxDownCapacity.value() 232 | mKcpJSONFile["congestion"] = self.checkBoxCongestion.isChecked() 233 | mKcpJSONFile["readBufferSize"] = self.spinBoxRdBufferSize.value() 234 | mKcpJSONFile["writeBufferSize"] = self.spinBoxWrBufferSize.value() 235 | mKcpJSONFile["header"] = {} 236 | mKcpJSONFile["header"]["type"] = self.comboBoxHeader.currentText() 237 | 238 | return mKcpJSONFile 239 | 240 | def clearmkcpPanel(self): 241 | self.groupBoxmKCPSetting.setChecked(False) 242 | self.spinBoxmKcpMTU.setValue(1355) 243 | self.spinBoxmKcpTTI.setValue(50) 244 | self.spinBoxUpCapacity.setValue(5) 245 | self.spinBoxDownCapacity.setValue(20) 246 | self.checkBoxCongestion.setChecked(False) 247 | self.spinBoxRdBufferSize.setValue(2) 248 | self.spinBoxWrBufferSize.setValue(2) 249 | self.comboBoxHeader.setCurrentIndex(0) 250 | 251 | def __debugTest(self): 252 | import json 253 | print(json.dumps(self.createmKcpSettingJSONFile(), indent=4, sort_keys=False)) 254 | 255 | 256 | if __name__ == "__main__": 257 | from PyQt5.QtWidgets import QApplication 258 | app = QApplication(sys.argv) 259 | ex = mKcpPanel() 260 | ex.createmKcpSettingPanel() 261 | ex.setGeometry(300, 300, 680, 230) 262 | ex.show() 263 | sys.exit(app.exec_()) 264 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/transport/muxPanel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QLabel, QWidget, QGroupBox, QHBoxLayout, QSpinBox, QPushButton) 4 | from PyQt5.QtCore import QFileInfo, QCoreApplication 5 | import sys 6 | 7 | v2rayshellDebug = False 8 | 9 | if __name__ == "__main__": 10 | v2rayshellDebug = True 11 | # this for debug test 12 | path = QFileInfo(sys.argv[0]) 13 | srcPath = path.absoluteFilePath().split("/") 14 | sys.path.append("/".join(srcPath[:-4])) 15 | 16 | from bridgehouse.editMap.transport import logbook 17 | 18 | 19 | class muxPanel(QWidget): 20 | 21 | def __init__(self): 22 | super().__init__() 23 | self.muxJSONFile = { 24 | "enabled": False, 25 | "concurrency": 8 26 | } 27 | self.translate = QCoreApplication.translate 28 | 29 | self.groupBoxmuxSetting = QGroupBox( 30 | self.translate("muxPanel", "Mux Setting"), self) 31 | self.hboxConcurrency = QHBoxLayout() 32 | 33 | def createmuxSettingPanel(self): 34 | labelConcurrency = QLabel( 35 | self.translate("muxPanel", "Concurrency: "), self) 36 | self.spinBoxConcurrency = QSpinBox() 37 | self.spinBoxConcurrency.setRange(1, 1024) 38 | self.spinBoxConcurrency.setValue(8) 39 | 40 | hboxConcurrency = self.hboxConcurrency 41 | hboxConcurrency.addWidget(labelConcurrency) 42 | hboxConcurrency.addWidget(self.spinBoxConcurrency) 43 | hboxConcurrency.addStretch() 44 | 45 | self.groupBoxmuxSetting.setCheckable(True) 46 | self.groupBoxmuxSetting.setLayout(hboxConcurrency) 47 | 48 | if (v2rayshellDebug): 49 | from PyQt5.QtWidgets import QVBoxLayout 50 | self.__debugBtn = QPushButton("__debugTest", self) 51 | v = QVBoxLayout() 52 | v.addWidget(self.groupBoxmuxSetting) 53 | v.addWidget(self.__debugBtn) 54 | self.__debugBtn.clicked.connect(self.__debugTest) 55 | self.settingmuxPanelFromJSONFile(self.muxJSONFile, True) 56 | self.setLayout(v) 57 | return 58 | 59 | return self.groupBoxmuxSetting 60 | 61 | def settingmuxPanelFromJSONFile(self, muxJSONFile, openFromJSONFile=False): 62 | logbook.setisOpenJSONFile(openFromJSONFile) 63 | 64 | if (not muxJSONFile): 65 | muxJSONFile = {} 66 | self.groupBoxmuxSetting.setChecked(False) 67 | return 68 | 69 | try: 70 | muxJSONFile["enabled"] 71 | except KeyError as e: 72 | logbook.writeLog("mux", "KeyError", e) 73 | muxJSONFile["enabled"] = False 74 | 75 | try: 76 | muxJSONFile["concurrency"] 77 | except KeyError as e: 78 | logbook.writeLog("mux", "KeyError", e) 79 | muxJSONFile["concurrency"] = 8 80 | 81 | try: 82 | self.spinBoxConcurrency.setValue(int(muxJSONFile["concurrency"])) 83 | except ValueError as e: 84 | logbook.writeLog("mux", "ValueError", e) 85 | muxJSONFile["concurrency"] = 8 86 | 87 | self.groupBoxmuxSetting.setChecked(bool(muxJSONFile["enabled"])) 88 | 89 | def createmuxSettingJSONFile(self): 90 | muxJSONFile = {} 91 | muxJSONFile["enabled"] = self.groupBoxmuxSetting.isChecked() 92 | muxJSONFile["concurrency"] = self.spinBoxConcurrency.value() 93 | 94 | return muxJSONFile 95 | 96 | def clearmuxPanel(self): 97 | self.groupBoxmuxSetting.setChecked(False) 98 | self.spinBoxConcurrency.setValue(8) 99 | 100 | def __debugTest(self): 101 | import json 102 | print(json.dumps(self.createmuxSettingJSONFile(), indent=4, sort_keys=False)) 103 | 104 | 105 | if __name__ == "__main__": 106 | from PyQt5.QtWidgets import QApplication 107 | app = QApplication(sys.argv) 108 | ex = muxPanel() 109 | ex.createmuxSettingPanel() 110 | ex.setGeometry(300, 300, 680, 230) 111 | ex.show() 112 | sys.exit(app.exec_()) 113 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/transport/tcpPanel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QWidget, QGroupBox, QApplication, 4 | QHBoxLayout, QVBoxLayout, QTextEdit, 5 | QLabel, QComboBox, QPushButton) 6 | from PyQt5.QtCore import QFileInfo, QCoreApplication 7 | import sys, json 8 | 9 | v2rayshellDebug = False 10 | 11 | if __name__ == "__main__": 12 | v2rayshellDebug = True 13 | # this for debug test 14 | path = QFileInfo(sys.argv[0]) 15 | srcPath = path.absoluteFilePath().split("/") 16 | sys.path.append("/".join(srcPath[:-4])) 17 | 18 | from bridgehouse.editMap.transport import logbook 19 | 20 | 21 | class TcpPanel(QWidget): 22 | 23 | def __init__(self): 24 | super().__init__() 25 | self.tcpJSONFile = { 26 | "header": { 27 | "type": "none" 28 | } 29 | } 30 | self.translate = QCoreApplication.translate 31 | self.httpHeader = "none", "http" 32 | 33 | def createTCPSettingPanel(self): 34 | labelHttpHeaderType = QLabel( 35 | self.translate("TcpPanel", "Http type: "), self) 36 | self.comboxHttpHeaderType = QComboBox(self) 37 | self.btnHttpHeaderTypeClear = QPushButton( 38 | self.translate("TcpPanel", "Clear"), self) 39 | self.textHttpHeaderType = QTextEdit() 40 | 41 | self.comboxHttpHeaderType.addItems(self.httpHeader) 42 | self.btnHttpHeaderTypeClear.hide() 43 | self.textHttpHeaderType.hide() 44 | self.textHttpHeaderType.adjustSize() 45 | 46 | hboxhttpHeader = QHBoxLayout() 47 | hboxhttpHeader.addWidget(labelHttpHeaderType) 48 | hboxhttpHeader.addWidget(self.comboxHttpHeaderType) 49 | hboxhttpHeader.addWidget(self.btnHttpHeaderTypeClear) 50 | hboxhttpHeader.addStretch() 51 | 52 | vboxTCPSetting = QVBoxLayout() 53 | vboxTCPSetting.addLayout(hboxhttpHeader) 54 | vboxTCPSetting.addWidget(self.textHttpHeaderType) 55 | 56 | self.groupBoxTCPSetting = groupBoxTCPSetting = QGroupBox( 57 | self.translate("TcpPanel", "TCP Settings"), self) 58 | groupBoxTCPSetting.setCheckable(True) 59 | groupBoxTCPSetting.setChecked(False) 60 | groupBoxTCPSetting.adjustSize() 61 | groupBoxTCPSetting.setLayout(vboxTCPSetting) 62 | 63 | self.createTCPSettingPanelSignles() 64 | 65 | if(v2rayshellDebug): 66 | self.__debugBtn = QPushButton("__debugTest", self) 67 | v = QVBoxLayout() 68 | v.addWidget(groupBoxTCPSetting) 69 | v.addWidget(self.__debugBtn) 70 | v.addStretch() 71 | self.setLayout(v) 72 | self.__debugBtn.clicked.connect(self.__debugTest) 73 | self.settingtcpPanelFromJSONFile(self.tcpJSONFile, True) 74 | return 75 | 76 | return groupBoxTCPSetting 77 | 78 | def createTCPSettingPanelSignles(self): 79 | self.comboxHttpHeaderType.currentTextChanged.connect(self.oncomboxHttpHeaderType) 80 | self.btnHttpHeaderTypeClear.clicked.connect(self.onbtnHttpHeaderTypeClear) 81 | 82 | def onbtnHttpHeaderTypeClear(self): 83 | self.textHttpHeaderType.clear() 84 | 85 | def oncomboxHttpHeaderType(self, e): 86 | if (e == "none"): 87 | self.textHttpHeaderType.hide() 88 | self.btnHttpHeaderTypeClear.hide() 89 | 90 | if (e == "http"): 91 | self.btnHttpHeaderTypeClear.show() 92 | self.textHttpHeaderType.show() 93 | 94 | def jsonDataValitor(self, jsonData): 95 | try: 96 | data = json.loads(str(jsonData)) 97 | except ValueError as e: 98 | return -1, e 99 | return 0, data 100 | 101 | def settingtcpPanelFromJSONFile(self, tcpJSONFile, openFromJSONFile=False): 102 | logbook.setisOpenJSONFile(openFromJSONFile) 103 | 104 | if (not tcpJSONFile): 105 | tcpJSONFile = {} 106 | self.groupBoxTCPSetting.setChecked(False) 107 | self.textHttpHeaderType.clear() 108 | self.comboxHttpHeaderType.setCurrentText("none") 109 | return 110 | 111 | try: 112 | tcpJSONFile["header"] 113 | except KeyError as e: 114 | logbook.writeLog("transport TCP", "KeyError", e) 115 | tcpJSONFile["header"] = {} 116 | 117 | try: 118 | tcpJSONFile["header"]["type"] 119 | except KeyError as e: 120 | logbook.writeLog("transport TCP", "KeyError", e) 121 | tcpJSONFile["header"]["type"] = "none" 122 | 123 | if (tcpJSONFile["header"]["type"] == "http"): 124 | self.textHttpHeaderType.setText( 125 | json.dumps(tcpJSONFile["header"], indent=4, sort_keys=False)) 126 | self.comboxHttpHeaderType.setCurrentText("http") 127 | elif (tcpJSONFile["header"]["type"] == "none"): 128 | self.textHttpHeaderType.clear() 129 | else: 130 | # TODO pop a error message 131 | pass 132 | 133 | def createtcpSettingJSONFile(self): 134 | tcpJSONFile = {} 135 | tcpJSONFile["header"] = {} 136 | 137 | if (self.comboxHttpHeaderType.currentText() == "http"): 138 | header = self.textHttpHeaderType.toPlainText() 139 | checkjsonfile = self.jsonDataValitor(header) 140 | if (checkjsonfile[0] == -1): 141 | tcpJSONFile["header"]["type"] = "none" 142 | return tcpJSONFile 143 | 144 | if (not checkjsonfile[0]): 145 | header = checkjsonfile[1] 146 | http = False 147 | try: 148 | # use input text maybe no value type 149 | http = (header["type"] == "http") 150 | except KeyError: 151 | # TODO pop a error message 152 | pass 153 | except: 154 | # TODO pop a error message 155 | pass 156 | 157 | if(http): 158 | tcpJSONFile["header"] = header 159 | else: 160 | tcpJSONFile["header"]["type"] = "none" 161 | else: 162 | tcpJSONFile["header"]["type"] = "none" 163 | 164 | return tcpJSONFile 165 | 166 | def cleartcpPanel(self): 167 | self.groupBoxTCPSetting.setChecked(False) 168 | self.comboxHttpHeaderType.setCurrentIndex(0) 169 | self.textHttpHeaderType.clear() 170 | 171 | def __debugTest(self): 172 | print(json.dumps(self.createtcpSettingJSONFile(), indent=4, sort_keys=False)) 173 | 174 | 175 | if __name__ == "__main__": 176 | app = QApplication(sys.argv) 177 | ex = TcpPanel() 178 | ex.createTCPSettingPanel() 179 | ex.setGeometry(300, 300, 680, 620) 180 | ex.show() 181 | sys.exit(app.exec_()) 182 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/transport/wsPanel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QLabel, QWidget, QGroupBox, 4 | QLineEdit, QHBoxLayout, QVBoxLayout, 5 | QPushButton) 6 | from PyQt5.QtCore import QFileInfo, QCoreApplication 7 | import sys 8 | 9 | v2rayshellDebug = False 10 | 11 | if __name__ == "__main__": 12 | v2rayshellDebug = True 13 | # this for debug test 14 | path = QFileInfo(sys.argv[0]) 15 | srcPath = path.absoluteFilePath().split("/") 16 | sys.path.append("/".join(srcPath[:-4])) 17 | 18 | from bridgehouse.editMap.transport import logbook 19 | 20 | 21 | class wsPanel(QWidget): 22 | 23 | def __init__(self): 24 | super().__init__() 25 | self.wsJSONFile = { 26 | "path": "", 27 | "headers": { 28 | "Host": "v2ray.com" 29 | } 30 | } 31 | self.translate = QCoreApplication.translate 32 | 33 | def createwsSettingPanel(self): 34 | labelPath = QLabel(self.translate("wsPanel", "Path: "), self) 35 | self.lineEditwebsocksPath = QLineEdit() 36 | labelHost = QLabel(self.translate("wsPanel", "Host: "), self) 37 | self.lineEditwebsocksHost = QLineEdit() 38 | 39 | hboxPath = QHBoxLayout() 40 | hboxPath.addWidget(labelPath) 41 | hboxPath.addWidget(self.lineEditwebsocksPath) 42 | 43 | hboxHost = QHBoxLayout() 44 | hboxHost.addWidget(labelHost) 45 | hboxHost.addWidget(self.lineEditwebsocksHost) 46 | 47 | vboxwsSetting = QVBoxLayout() 48 | vboxwsSetting.addLayout(hboxPath) 49 | vboxwsSetting.addLayout(hboxHost) 50 | 51 | self.groupBoxwsSetting = groupBoxwsSetting = QGroupBox( 52 | self.translate("wsPanel", "WebSocket Setting"), self) 53 | groupBoxwsSetting.setCheckable(True) 54 | groupBoxwsSetting.setChecked(False) 55 | groupBoxwsSetting.adjustSize() 56 | groupBoxwsSetting.setLayout(vboxwsSetting) 57 | 58 | if (v2rayshellDebug): 59 | self.__debugBtn = QPushButton("__debugTest", self) 60 | self.__debugBtn.clicked.connect(self.__debugTest) 61 | vboxwsSetting.addWidget(self.__debugBtn) 62 | self.settingwsPanelFromJSONFile(self.wsJSONFile, True) 63 | 64 | return groupBoxwsSetting 65 | 66 | def settingwsPanelFromJSONFile(self, wsJSONFile, openFromJSONFile=False): 67 | logbook.setisOpenJSONFile(openFromJSONFile) 68 | 69 | if (not wsJSONFile): 70 | wsJSONFile = {} 71 | self.groupBoxwsSetting.setChecked(False) 72 | self.lineEditwebsocksHost.clear() 73 | self.lineEditwebsocksPath.clear() 74 | return False 75 | try: 76 | wsJSONFile["path"] 77 | except KeyError as e: 78 | logbook.writeLog("transport ws", "KeyError", e) 79 | wsJSONFile["path"] = "" 80 | 81 | try: 82 | wsJSONFile["headers"] 83 | except KeyError as e: 84 | logbook.writeLog("transport ws", "KeyError", e) 85 | wsJSONFile["headers"] = {} 86 | 87 | try: 88 | wsJSONFile["headers"]["Host"] 89 | except KeyError as e: 90 | logbook.writeLog("transport ws", "KeyError", e) 91 | wsJSONFile["headers"]["Host"] = "" 92 | 93 | self.lineEditwebsocksPath.setText(str(wsJSONFile["path"])) 94 | self.lineEditwebsocksHost.setText(str(wsJSONFile["headers"]["Host"])) 95 | 96 | def createwsSettingJSONFile(self): 97 | wsJSONFile = {} 98 | wsJSONFile["headers"] = {} 99 | wsJSONFile["headers"]["Host"] = self.lineEditwebsocksHost.text() 100 | wsJSONFile["path"] = self.lineEditwebsocksPath.text() 101 | 102 | return wsJSONFile 103 | 104 | def clearwsPanel(self): 105 | self.lineEditwebsocksHost.clear() 106 | self.lineEditwebsocksPath.clear() 107 | self.groupBoxwsSetting.setChecked(False) 108 | 109 | def __debugTest(self): 110 | import json 111 | print(json.dumps(self.createwsSettingJSONFile(), indent=4, sort_keys=False)) 112 | 113 | 114 | if __name__ == "__main__": 115 | from PyQt5.QtWidgets import QApplication 116 | app = QApplication(sys.argv) 117 | ex = wsPanel() 118 | ex.createwsSettingPanel() 119 | ex.setGeometry(300, 300, 680, 230) 120 | ex.show() 121 | sys.exit(app.exec_()) 122 | -------------------------------------------------------------------------------- /src/bridgehouse/editMap/transportTAB.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QApplication, QPushButton, QVBoxLayout) 4 | from PyQt5.QtCore import QFileInfo, QCoreApplication 5 | import sys, json 6 | 7 | v2rayshellDebug = False 8 | 9 | if __name__ == "__main__": 10 | v2rayshellDebug = True 11 | # this for debug test 12 | path = QFileInfo(sys.argv[0]) 13 | srcPath = path.absoluteFilePath().split("/") 14 | sys.path.append("/".join(srcPath[:-3])) 15 | 16 | from bridgehouse.editMap.transport import transportPanel 17 | 18 | 19 | class transportTab(transportPanel.TransportPanel): 20 | 21 | def __init__(self): 22 | super().__init__() 23 | self.translate = QCoreApplication.translate 24 | 25 | def createTransportPanel(self): 26 | super(transportTab, self).createTransportPanel() 27 | 28 | if v2rayshellDebug: 29 | self.setLayout(self.vboxcheckBoxStreamSetting) 30 | self.__debugBtn = QPushButton("__debugTest", self) 31 | self.vboxTransportPanel.addWidget(self.__debugBtn) 32 | self.__debugBtn.clicked.connect(self.__debugTest) 33 | self.settingtransportPanelFromJSONFile(self.transportJSONFile, True) 34 | 35 | self.groupTransportPanel.setTitle(self.translate("transportTab", "Transport Setting (Global)")) 36 | self.groupTransportPanel.setCheckable(True) 37 | self.groupTransportPanel.setChecked(False) 38 | 39 | return self.groupTransportPanel 40 | 41 | def __debugTest(self): 42 | print(json.dumps(self.createtransportSettingJSONFile(), indent=4, sort_keys=False)) 43 | 44 | 45 | if __name__ == "__main__": 46 | app = QApplication(sys.argv) 47 | ex = transportTab() 48 | v = QVBoxLayout() 49 | v.addWidget(ex.createTransportPanel()) 50 | ex.setLayout(v) 51 | ex.setGeometry(200, 100, 800, 768) 52 | ex.show() 53 | sys.exit(app.exec_()) 54 | -------------------------------------------------------------------------------- /src/bridgehouse/extension/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/v2ray/V2Ray-shell_alpha/df72d9cc8c4dfb0fa7215580bdd16f76edcf7fbe/src/bridgehouse/extension/__init__.py -------------------------------------------------------------------------------- /src/bridgehouse/extension/bugReport.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PyQt5.QtWidgets import (QApplication, QLabel, QPushButton, 4 | QVBoxLayout, QHBoxLayout, QTextEdit, QLineEdit, 5 | QGridLayout, QRadioButton, QGroupBox, QButtonGroup, 6 | QDialog, QSpacerItem) 7 | from PyQt5.QtCore import (QFile, QFileInfo, QUrl, QIODevice, QTimer, 8 | QCoreApplication, QSysInfo) 9 | from PyQt5.QtGui import QDesktopServices 10 | from PyQt5.Qt import QTextCursor, PYQT_VERSION_STR, QT_VERSION_STR 11 | import sys, copy, codecs 12 | 13 | 14 | class bugReport(QDialog): 15 | 16 | def __init__(self): 17 | super(bugReport, self).__init__() 18 | self.translate = QCoreApplication.translate 19 | self.readedHowReportBug = False 20 | self.timerAutoSave = QTimer() 21 | self.timerAutoSave.timeout.connect(self.onautoSave) 22 | self.timerAutoSave.start(1000 * 60 * 3) 23 | 24 | def createPanel(self): 25 | labelReportTitle = QLabel(self.translate("bugReport", "Bug Report Title: ")) 26 | self.lineEditReportTitle = QLineEdit() 27 | 28 | labelTestedEnvironment = QLabel(self.translate("bugReport", "Tested Environment: ")) 29 | self.lineEditTestedEnvironment = QLineEdit( 30 | self.translate("bugReport", 31 | """System Platform:{}_{} Python Version:{}.{}.{}-{} PyQt Version:{} QT Version:{}""").format( 32 | QSysInfo.prettyProductName() if QSysInfo.prettyProductName() == "unknown" else "{}-{}".format(QSysInfo.kernelType(), QSysInfo.kernelVersion()), 33 | QSysInfo.currentCpuArchitecture(), 34 | sys.version_info.major, 35 | sys.version_info.minor, 36 | sys.version_info.micro, 37 | sys.version_info.releaselevel, 38 | PYQT_VERSION_STR, 39 | QT_VERSION_STR)) 40 | 41 | radioBtnQuickReport = QRadioButton(self.translate("bugReport", "Quick Report")) 42 | radioBtnKnowHowFix = QRadioButton(self.translate("bugReport", "Know How Fix")) 43 | radioBtnKnowHowFix.setChecked(True) 44 | radioBtnFeatureRequest = QRadioButton(self.translate("bugReport", "Feature Request")) 45 | buttonOpenHowReportBugURL = QPushButton( 46 | self.translate("bugReport", """Click Me! Read "HOW REPORT A BUG" before report a bug.""")) 47 | 48 | self.buttonGroupBugReport = QButtonGroup() 49 | self.buttonGroupBugReport.addButton(radioBtnQuickReport) 50 | self.buttonGroupBugReport.addButton(radioBtnKnowHowFix) 51 | self.buttonGroupBugReport.addButton(radioBtnFeatureRequest) 52 | self.buttonGroupBugReport.addButton(buttonOpenHowReportBugURL) 53 | 54 | hboxRadiobutton = QHBoxLayout() 55 | hboxRadiobutton.addWidget(radioBtnKnowHowFix) 56 | hboxRadiobutton.addWidget(radioBtnQuickReport) 57 | hboxRadiobutton.addWidget(radioBtnFeatureRequest) 58 | hboxRadiobutton.addWidget(buttonOpenHowReportBugURL) 59 | hboxRadiobutton.addStretch() 60 | 61 | labelStepsToReproduce = QLabel(self.translate("bugReport", "Steps To Reproduce: ")) 62 | self.textEditStepsToReproduce = QTextEdit() 63 | 64 | labelActualresults = QLabel(self.translate("bugReport", "Actual results: ")) 65 | self.textEditActualresults = QTextEdit() 66 | self.textEditActualresults.insertPlainText( 67 | self.translate("bugReport", "if have Python's Traceback, Please Paste.\nif is V2Ray-core JSON Editor issue, please Paste the JSON File without server information.")) 68 | self.textEditActualresults.setAcceptDrops(True) 69 | 70 | labelExpectedresults = QLabel(self.translate("bugReport", "Expected results: ")) 71 | self.textEditExpectedresults = QTextEdit() 72 | 73 | labelFeatureRequest = QLabel(self.translate("bugReport", "Feature Request: ")) 74 | self.textEditFeatureRequest = QTextEdit() 75 | 76 | labelQuickReport = QLabel(self.translate("bugReport", "Quick Report: ")) 77 | self.textEditQuickReport = QTextEdit() 78 | 79 | labelHowFix = QLabel(self.translate("bugReport", "How Fix: ")) 80 | self.textEditHowFix = QTextEdit() 81 | 82 | gridBoxReport = QGridLayout() 83 | gridBoxReport.addWidget(labelReportTitle, 0, 0) 84 | gridBoxReport.addWidget(self.lineEditReportTitle, 0, 1) 85 | gridBoxReport.addWidget(labelTestedEnvironment, 1, 0) 86 | gridBoxReport.addWidget(self.lineEditTestedEnvironment, 1, 1) 87 | gridBoxReport.addLayout(hboxRadiobutton, 2, 0, 1, 2) 88 | 89 | gridBoxQuickReport = QGridLayout() 90 | gridBoxQuickReport.addWidget(labelQuickReport, 0, 0) 91 | gridBoxQuickReport.addWidget(self.textEditQuickReport, 0, 1) 92 | 93 | self.groupBoxQuickReport = QGroupBox("", self) 94 | self.groupBoxQuickReport.setLayout(gridBoxQuickReport) 95 | self.groupBoxQuickReport.hide() 96 | 97 | gridBoxKnowHowFix = QGridLayout() 98 | gridBoxKnowHowFix.addWidget(labelStepsToReproduce, 0, 0) 99 | gridBoxKnowHowFix.addWidget(self.textEditStepsToReproduce, 0, 1) 100 | gridBoxKnowHowFix.addWidget(labelActualresults, 1, 0) 101 | gridBoxKnowHowFix.addWidget(self.textEditActualresults, 1, 1) 102 | self.buttonInsertPiture = QPushButton(self.translate("bugReport", "Insert Picture From URL:")) 103 | self.lineEditInserPiture = QLineEdit() 104 | gridBoxKnowHowFix.addWidget(self.lineEditInserPiture, 2, 1) 105 | gridBoxKnowHowFix.addWidget(self.buttonInsertPiture, 2, 0) 106 | gridBoxKnowHowFix.addItem(QSpacerItem(50, 50), 3, 0, 1, 4) 107 | gridBoxKnowHowFix.addWidget(labelExpectedresults, 4, 0) 108 | gridBoxKnowHowFix.addWidget(self.textEditExpectedresults, 4, 1) 109 | gridBoxKnowHowFix.addWidget(labelHowFix, 5, 0) 110 | gridBoxKnowHowFix.addWidget(self.textEditHowFix, 5, 1) 111 | 112 | self.groupBoxKnowHowFix = QGroupBox() 113 | self.groupBoxKnowHowFix.setLayout(gridBoxKnowHowFix) 114 | 115 | gridBoxFeatureRequest = QGridLayout() 116 | gridBoxFeatureRequest.addWidget(labelFeatureRequest, 0, 0) 117 | gridBoxFeatureRequest.addWidget(self.textEditFeatureRequest, 0, 1) 118 | 119 | self.groupBoxFeatureRequest = QGroupBox("", self) 120 | self.groupBoxFeatureRequest.setLayout(gridBoxFeatureRequest) 121 | self.groupBoxFeatureRequest.hide() 122 | 123 | hboxButton = QHBoxLayout() 124 | self.buttonExportBugReportText = QPushButton(self.translate("bugReport", "Export Bug Report Text")) 125 | self.buttonExitButReport = QPushButton(self.translate("bugReport", "Exit")) 126 | hboxButton.addStretch() 127 | hboxButton.addWidget(self.buttonExportBugReportText) 128 | hboxButton.addWidget(self.buttonExitButReport) 129 | 130 | vboxBugReport = QVBoxLayout(self) 131 | vboxBugReport.addLayout(gridBoxReport) 132 | vboxBugReport.addWidget(self.groupBoxQuickReport) 133 | vboxBugReport.addWidget(self.groupBoxKnowHowFix) 134 | vboxBugReport.addWidget(self.groupBoxFeatureRequest) 135 | vboxBugReport.addLayout(hboxButton) 136 | vboxBugReport.addStretch() 137 | 138 | self.settextEidtReadonly(result=True) 139 | self.createSignals() 140 | 141 | def createSignals(self): 142 | self.buttonGroupBugReport.buttonClicked.connect(self.onbuttonGroupBugReport) 143 | self.buttonExitButReport.clicked.connect(self.close) 144 | self.buttonExportBugReportText.clicked.connect(self.onautoSave) 145 | self.buttonInsertPiture.clicked.connect(self.onbuttonInsertPiture) 146 | 147 | def onbuttonInsertPiture(self): 148 | url = self.lineEditInserPiture.text() 149 | if url != "": 150 | url = QUrl(url) 151 | self.textEditActualresults.moveCursor(QTextCursor.End) 152 | self.textEditActualresults.insertPlainText("""\n""".format(url.path())) 153 | self.textEditActualresults.moveCursor(QTextCursor.End) 154 | self.lineEditInserPiture.clear() 155 | 156 | def settextEidtReadonly(self, result=True): 157 | self.lineEditInserPiture.setReadOnly(result) 158 | self.textEditActualresults.setReadOnly(result) 159 | self.textEditExpectedresults.setReadOnly(result) 160 | self.textEditFeatureRequest.setReadOnly(result) 161 | self.textEditHowFix.setReadOnly(result) 162 | self.textEditQuickReport.setReadOnly(result) 163 | self.textEditStepsToReproduce.setReadOnly(result) 164 | 165 | def onautoSave(self): 166 | currentButton = self.buttonGroupBugReport.buttons() 167 | for i in currentButton: 168 | if i.isChecked(): 169 | button = i.text() 170 | if button == self.translate("bugReport", "Quick Report"): 171 | self.savebugReportText(save=copy.deepcopy(self.saveQuickReport())) 172 | if button == self.translate("bugReport", "Know How Fix"): 173 | self.savebugReportText(save=copy.deepcopy(self.saveKnowHowFix())) 174 | if button == self.translate("bugReport", "Feature Request"): 175 | self.savebugReportText(save=copy.deepcopy(self.saveFeatureRequest())) 176 | 177 | def savebugReportText(self, save=False): 178 | if save: 179 | outFile = QFileInfo(self.translate("bugReport", "bugReport.txt")) 180 | fileName = outFile.fileName() 181 | if QFile.exists(fileName): 182 | QFile.remove(fileName) 183 | 184 | outFile = QFile(fileName) 185 | outFile.open(QIODevice.WriteOnly | QIODevice.Text) 186 | outFile.write(codecs.encode(save, "utf-8")) 187 | 188 | def saveQuickReport(self): 189 | bugReportText = self.translate("bugReport", "Bug Report Title: \n{}\nTested Environment: \n{}\n\nQuick Report:\n{}\n").format( 190 | self.lineEditReportTitle.text(), 191 | self.lineEditTestedEnvironment.text(), 192 | self.textEditQuickReport.toPlainText()) 193 | 194 | return bugReportText 195 | 196 | def saveKnowHowFix(self): 197 | bugReportText = self.translate("bugReport", """Bug Report Title: {} 198 | \nTested Environment: \n{} 199 | \nSteps To Reproduce: \n{} 200 | \nActual Results: \n{} 201 | \nExpected Results: \n{} 202 | \nHow Fix: \n{}\n""").format( 203 | self.lineEditReportTitle.text(), 204 | self.lineEditTestedEnvironment.text(), 205 | self.textEditStepsToReproduce.toPlainText(), 206 | self.textEditActualresults.toPlainText(), 207 | self.textEditExpectedresults.toPlainText(), 208 | self.textEditHowFix.toPlainText()) 209 | 210 | return bugReportText 211 | 212 | def saveFeatureRequest(self): 213 | bugReportText = self.translate("bugReport", """Feature Request Title: \n{} 214 | Tested Environment: \n{} 215 | Feature Request Details:\n{}\n""").format( 216 | self.lineEditReportTitle.text(), 217 | self.lineEditTestedEnvironment.text(), 218 | self.textEditFeatureRequest.toPlainText()) 219 | 220 | return bugReportText 221 | 222 | def onbuttonGroupBugReport(self, button): 223 | 224 | def hideAllWidget(): 225 | self.groupBoxFeatureRequest.hide() 226 | self.groupBoxKnowHowFix.hide() 227 | self.groupBoxQuickReport.hide() 228 | 229 | buttonText = button.text() 230 | if buttonText == self.translate("bugReport", "Quick Report"): 231 | hideAllWidget() 232 | self.groupBoxQuickReport.show() 233 | elif buttonText == self.translate("bugReport", "Know How Fix"): 234 | hideAllWidget() 235 | self.groupBoxKnowHowFix.show() 236 | elif buttonText == self.translate("bugReport", "Feature Request"): 237 | hideAllWidget() 238 | self.groupBoxFeatureRequest.show() 239 | elif buttonText == self.translate("bugReport", """Click Me! Read "HOW REPORT A BUG" before report a bug."""): 240 | QDesktopServices.openUrl(QUrl("https://www.chiark.greenend.org.uk/~sgtatham/bugs.html")) 241 | self.settextEidtReadonly(result=False) 242 | 243 | 244 | if __name__ == '__main__': 245 | app = QApplication(sys.argv) 246 | ex = bugReport() 247 | ex.createPanel() 248 | ex.setGeometry(250, 150, 1024, 768) 249 | ex.show() 250 | sys.exit(app.exec_()) 251 | -------------------------------------------------------------------------------- /src/bridgehouse/extension/runV2raycore.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from PyQt5.QtWidgets import (QWidget, QVBoxLayout, 3 | QPushButton, QTextEdit, QLabel, 4 | QLineEdit, QGridLayout, QFileDialog, QButtonGroup) 5 | from PyQt5.QtCore import (QProcess, QSize, QIODevice, QProcessEnvironment, 6 | QObject, pyqtSignal, QCoreApplication, QFileInfo, 7 | QFile, qDebug) 8 | from PyQt5.Qt import QTextCursor 9 | 10 | import re 11 | import sys 12 | import codecs 13 | import os 14 | import signal 15 | 16 | v2rayshellDebug = False 17 | 18 | if __name__ == "__main__": 19 | v2rayshellDebug = True 20 | # this for debug test 21 | path = QFileInfo(sys.argv[0]) 22 | srcPath = path.absoluteFilePath().split("/") 23 | sys.path.append("/".join(srcPath[:-3])) 24 | 25 | 26 | class runV2raycore(QObject): 27 | """ 28 | you should emit a signal to start or stop a program. 29 | """ 30 | start = pyqtSignal() 31 | stop = pyqtSignal() 32 | 33 | def __init__(self, 34 | outputTextEdit, 35 | v2rayPath="v2ray", 36 | v2rayOption="", 37 | bridgetreasureChest=False): 38 | super().__init__() 39 | self.outputTextEdit = outputTextEdit 40 | self.v2rayPath = v2rayPath 41 | self.v2rayOption = v2rayOption 42 | self.bridgetreasureChest = bridgetreasureChest 43 | if not self.bridgetreasureChest: 44 | from bridgehouse.extension import bridgetreasureChest 45 | self.bridgetreasureChest = bridgetreasureChest.bridgetreasureChest() 46 | 47 | self.v2rayProcess = QProcess() 48 | self.v2rayProcess.setProcessChannelMode(QProcess.MergedChannels) 49 | self.v2rayProcess.setProcessEnvironment( 50 | QProcessEnvironment.systemEnvironment()) 51 | 52 | self.v2rayProcess.readyRead.connect(self.setoutputTextEdit) 53 | self.v2rayProcess.started.connect(self.oncreatePIDFile) 54 | self.start.connect(self.onstart) 55 | self.stop.connect(self.onstop) 56 | self.translate = QCoreApplication.translate 57 | self.pidFile = ".v2rayPID" 58 | 59 | def onstart(self): 60 | if (self.v2rayProcess.state() == QProcess.NotRunning): 61 | self.outputTextEdit.clear() 62 | command = self.translate( 63 | "runV2raycore", "v2ray file path had no seted.") 64 | if (self.v2rayPath): 65 | checkSpaces = re.search(" ", self.v2rayPath) 66 | if checkSpaces: 67 | # in fact, you can just keep this line. 68 | # do not need check spaces 69 | command = '"' + self.v2rayPath + '" ' + self.v2rayOption 70 | else: 71 | command = "{} {}".format(self.v2rayPath, self.v2rayOption) 72 | self.killOrphanProcess() 73 | self.v2rayProcess.start(command, QIODevice.ReadWrite) 74 | self.outputTextEdit.insertPlainText("{}\n\n".format(command)) 75 | 76 | if (self.v2rayProcess.state() == QProcess.NotRunning): 77 | self.outputTextEdit.moveCursor(QTextCursor.End) 78 | self.outputTextEdit.append("\n") 79 | self.outputTextEdit.insertPlainText( 80 | str("{}\n".format(command))) 81 | self.outputTextEdit.insertPlainText( 82 | str(self.translate( 83 | "runV2raycore", "{} Error Code:{}").format( 84 | self.v2rayProcess.errorString(), 85 | self.v2rayProcess.error()))) 86 | self.outputTextEdit.moveCursor(QTextCursor.End) 87 | 88 | self.outputTextEdit.textChanged.connect(self.getV2raycoreVersion) 89 | 90 | def killOrphanProcess(self): 91 | openFile = QFileInfo(self.pidFile) 92 | 93 | fileName = openFile.fileName() 94 | if QFile.exists(fileName): 95 | openFile = QFile(fileName) 96 | else: 97 | return 98 | v2rayPID = None 99 | 100 | try: 101 | openFile.open(QIODevice.ReadOnly | QIODevice.Text) 102 | v2rayPID = str(openFile.readAll(), "utf-8") 103 | except Exception: 104 | pass 105 | 106 | try: 107 | os.kill(int(v2rayPID), signal.SIGTERM) 108 | except Exception: 109 | pass 110 | 111 | def oncreatePIDFile(self): 112 | if self.v2rayProcess.state() == QProcess.NotRunning: 113 | return 114 | 115 | outFile = QFileInfo(self.pidFile) 116 | fileName = outFile.fileName() 117 | if QFile.exists(fileName): 118 | QFile.remove(fileName) 119 | outFile = QFile(fileName) 120 | 121 | v2rayPID = str(self.v2rayProcess.processId()) 122 | qDebug("process ID is: {}".format(v2rayPID)) 123 | try: 124 | outFile.open(QIODevice.WriteOnly | QIODevice.Text) 125 | outFile.write(codecs.encode(v2rayPID, "utf-8")) 126 | except Exception: 127 | pass 128 | outFile.close() 129 | 130 | def getV2raycoreVersion(self): 131 | text = self.outputTextEdit.toPlainText() 132 | version = re.findall("V2Ray v\d\.\d{1,2}", text) 133 | failtostart = re.findall("Failed to start App", text) 134 | if (version): 135 | version = version[0].split(" ")[1] 136 | self.bridgetreasureChest.setV2raycoreVersion(version) 137 | if (failtostart): 138 | self.outputTextEdit.textChanged.disconnect( 139 | self.getV2raycoreVersion) 140 | self.onstop() 141 | 142 | def onstop(self): 143 | if (self.v2rayProcess.state() == QProcess.Running): 144 | self.v2rayProcess.close() 145 | self.v2rayProcess.kill() 146 | self.outputTextEdit.moveCursor(QTextCursor.End) 147 | self.outputTextEdit.append("\n\n") 148 | self.outputTextEdit.insertPlainText( 149 | str(self.translate( 150 | "runV2raycore", 151 | "{} is stop now...").format(self.v2rayPath))) 152 | self.outputTextEdit.insertPlainText( 153 | str(self.translate( 154 | "runV2raycore", 155 | "\n{} is ready to run...").format(self.v2rayPath))) 156 | self.outputTextEdit.moveCursor(QTextCursor.End) 157 | 158 | def setoutputTextEdit(self): 159 | self.outputTextEdit.moveCursor(QTextCursor.End) 160 | self.outputTextEdit.insertPlainText( 161 | str(self.v2rayProcess.readAllStandardOutput(), "utf-8")) 162 | self.outputTextEdit.insertPlainText( 163 | str(self.v2rayProcess.readAllStandardError(), "utf-8")) 164 | self.outputTextEdit.moveCursor(QTextCursor.End) 165 | 166 | 167 | class executeProgramPanel(QWidget): 168 | 169 | def __init__(self): 170 | super().__init__() 171 | 172 | self.start = QPushButton("Start") 173 | self.stop = QPushButton("Stop") 174 | self.outputTextEdit = QTextEdit() 175 | self.outputTextEdit.isReadOnly() 176 | 177 | self.labelComand = QLabel("Comand: ") 178 | self.lineEditComand = QLineEdit() 179 | self.lineEditComand.setText("ping") 180 | self.buttonOpenComand = QPushButton("Open") 181 | 182 | self.labelOption = QLabel("Option: ") 183 | self.lineEditOption = QLineEdit() 184 | self.lineEditOption.setText("127.0.0.1 -t") 185 | 186 | self.buttonGroup = buttonGroup = QButtonGroup() 187 | buttonGroup.addButton(self.start) 188 | buttonGroup.addButton(self.stop) 189 | buttonGroup.addButton(self.buttonOpenComand) 190 | 191 | gridBox = QGridLayout() 192 | gridBox.addWidget(self.labelComand, 0, 0) 193 | gridBox.addWidget(self.lineEditComand, 0, 1, 1, 4) 194 | gridBox.addWidget(self.buttonOpenComand, 0, 5, 1, 1) 195 | gridBox.addWidget(self.labelOption, 1, 0, 1, 1) 196 | gridBox.addWidget(self.lineEditOption, 1, 1, 1, 4) 197 | gridBox.addWidget(self.start, 2, 4) 198 | gridBox.addWidget(self.stop, 2, 5) 199 | 200 | vbox = QVBoxLayout() 201 | vbox.addWidget(self.outputTextEdit) 202 | vbox.addLayout(gridBox) 203 | self.setFixedSize(QSize(600, 460)) 204 | 205 | self.setLayout(vbox) 206 | 207 | self.buttonGroup.buttonClicked.connect(self.onbuttonGroupClicked) 208 | 209 | def onbuttonGroupClicked(self, e): 210 | if e.text() == "Start": 211 | exeFile = None 212 | option = None 213 | if (self.lineEditComand.text() != ""): 214 | exeFile = self.lineEditComand.text() 215 | if (self.lineEditOption.text() != ""): 216 | option = self.lineEditOption.text() 217 | 218 | self.runV2ray = runV2raycore(outputTextEdit=self.outputTextEdit, 219 | v2rayPath=exeFile, 220 | v2rayOption=option) 221 | self.runV2ray.start.emit() 222 | 223 | elif e.text() == "Open": 224 | options = QFileDialog.Options() 225 | filePath, _ = QFileDialog.getOpenFileName( 226 | self, 227 | "Open V2ray execute File", 228 | "", 229 | "All File (*)", 230 | options=options) 231 | if (filePath): 232 | self.lineEditOption.clear() 233 | self.lineEditComand.clear() 234 | self.lineEditComand.setText(filePath) 235 | 236 | elif e.text() == "Stop": 237 | self.runV2ray.stop.emit() 238 | 239 | 240 | if __name__ == "__main__": 241 | from PyQt5.QtWidgets import QApplication 242 | app = QApplication(sys.argv) 243 | ex = executeProgramPanel() 244 | ex.show() 245 | sys.exit(app.exec_()) 246 | -------------------------------------------------------------------------------- /src/bridgehouse/icons/rocket.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/v2ray/V2Ray-shell_alpha/df72d9cc8c4dfb0fa7215580bdd16f76edcf7fbe/src/bridgehouse/icons/rocket.psd -------------------------------------------------------------------------------- /src/bridgehouse/icons/start.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/v2ray/V2Ray-shell_alpha/df72d9cc8c4dfb0fa7215580bdd16f76edcf7fbe/src/bridgehouse/icons/start.ico -------------------------------------------------------------------------------- /src/bridgehouse/icons/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/v2ray/V2Ray-shell_alpha/df72d9cc8c4dfb0fa7215580bdd16f76edcf7fbe/src/bridgehouse/icons/start.png -------------------------------------------------------------------------------- /src/bridgehouse/icons/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/v2ray/V2Ray-shell_alpha/df72d9cc8c4dfb0fa7215580bdd16f76edcf7fbe/src/bridgehouse/icons/stop.png -------------------------------------------------------------------------------- /src/bridgehouse/v2ray-shell.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python -*- 2 | 3 | block_cipher = None 4 | 5 | 6 | a = Analysis(['bridge.py'], 7 | pathex=['./bridgehouse'], 8 | binaries=[], 9 | datas=[], 10 | hiddenimports=[], 11 | hookspath=[], 12 | runtime_hooks=[], 13 | excludes=[], 14 | win_no_prefer_redirects=False, 15 | win_private_assemblies=False, 16 | cipher=block_cipher) 17 | icons = Tree('icons', 'icons') 18 | pyz = PYZ(a.pure, a.zipped_data, 19 | cipher=block_cipher) 20 | exe = EXE(pyz, 21 | a.scripts, 22 | a.binaries, 23 | a.zipfiles, 24 | a.datas, 25 | icons, 26 | name='v2ray-shell', 27 | debug=False, 28 | strip=False, 29 | upx=True, 30 | runtime_tmpdir=None, 31 | console=False, 32 | icon="./icons/start.ico") 33 | -------------------------------------------------------------------------------- /translations/en_US.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/v2ray/V2Ray-shell_alpha/df72d9cc8c4dfb0fa7215580bdd16f76edcf7fbe/translations/en_US.qm -------------------------------------------------------------------------------- /translations/tr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from PyQt5 import pylupdate_main as pylupdate 3 | import sys 4 | 5 | profile = "../v2rayshell.pro" 6 | sys.argv.append(profile) 7 | pylupdate.main() -------------------------------------------------------------------------------- /translations/zh_CN.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/v2ray/V2Ray-shell_alpha/df72d9cc8c4dfb0fa7215580bdd16f76edcf7fbe/translations/zh_CN.qm -------------------------------------------------------------------------------- /v2ray-shell.pyw: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from tkinter import Tk, BOTH, Text, END 4 | from tkinter.ttk import Frame, Button, Label 5 | from pathlib import Path 6 | import sys, subprocess 7 | 8 | class runV2Rayshell(Frame): 9 | def __init__(self, root): 10 | super(runV2Rayshell, self).__init__() 11 | self.root = root 12 | self.pyqt5 = False 13 | self.check_PyQt5_installed() 14 | self.pip_source = u"""[global] 15 | index-url = https://pypi.douban.com/simple/ 16 | trusted-host = pypi.douban.com 17 | """ 18 | if (not self.pyqt5): 19 | self.ui() 20 | else: 21 | self.ui() 22 | self.root.withdraw() 23 | self.run_current_v2rayshell() 24 | 25 | def run_current_v2rayshell(self): 26 | dir = "/src/bridgehouse/" 27 | path = Path().cwd() 28 | src_path ="{}{}".format(path, dir) 29 | if Path(src_path).is_dir(): 30 | bridgehouse_path = src_path.split("/") 31 | sys.path.append("/".join(bridgehouse_path[:-2])) 32 | try: 33 | from bridgehouse import bridge 34 | except Exception as e: 35 | self.root.deiconify() 36 | self.text.delete(1.0, END) 37 | self.text.insert(2.0, e) 38 | else: 39 | #os.system('"{}{}"'.format(src_path, "bridge.py")) 40 | if sys.platform == 'win32': 41 | subprocess.call('pythonw "{}{}"'.format(src_path, "bridge.py"), shell=True) 42 | else: 43 | subprocess.call('"{}{}"'.format(src_path, "bridge.py"), shell = True) 44 | self.root.destroy() 45 | 46 | def check_PyQt5_installed(self): 47 | try: import PyQt5 48 | except Exception: self.pyqt5 = False 49 | else: self.pyqt5 = True 50 | 51 | def set_pip_source(self): 52 | name = "pip.ini" 53 | home_path = "{}/pip".format(str(Path.home())) 54 | file_path = "{}/{}".format(home_path, name) 55 | if not Path(home_path).is_dir(): 56 | Path(home_path).mkdir() 57 | 58 | if Path(home_path).is_dir(): 59 | try: 60 | with open(file_path, "w", encoding="utf8") as f: 61 | f.write(self.pip_source) 62 | except Exception: 63 | self.text.delete(1.0, END) 64 | self.text.insert( 65 | 2.0,"install {} failed. you can do by manual...\n{}".format( 66 | filePath, self.pip_source)) 67 | else: 68 | self.text.delete(1.0, END) 69 | self.text.insert( 70 | 2.0,"{} installed successfully...\n{}".format( 71 | file_path, self.pip_source)) 72 | 73 | def install_PyQt5(self): 74 | test = subprocess.Popen( 75 | ["pip3","install","PyQt5"], 76 | stdout=subprocess.PIPE) 77 | output = test.communicate()[0] 78 | self.text.delete(1.0, END) 79 | self.text.insert(END, output.decode("utf-8")) 80 | 81 | def ui(self): 82 | label_no_pyqt5 = Label(self, text = "There is no PyQt5 install.") 83 | label_pip_source = Label( 84 | self, 85 | text = "You can click on the button below\nto speed up the installation of pypi source") 86 | label_install_pyqt5 = Label( 87 | self, text = "Then as a ROOT (OR ADMINISTRATOR) install PyQt5, \nlast restart this script.") 88 | butonn_install_source = Button( 89 | self, text = "install pypi source", 90 | command = self.set_pip_source) 91 | 92 | buton_install_PyQt5 = Button( 93 | self, 94 | text = "install PyQt5", 95 | command = self.install_PyQt5) 96 | 97 | self.text = Text(self) 98 | 99 | label_empty = Label(self) 100 | label_empty2 = Label(self) 101 | label_empty3 = Label(self) 102 | 103 | label_no_pyqt5.pack() 104 | label_pip_source.pack() 105 | label_install_pyqt5.pack() 106 | label_empty.pack() 107 | butonn_install_source.pack() 108 | label_empty2.pack() 109 | buton_install_PyQt5.pack() 110 | self.text.pack() 111 | label_empty3.pack() 112 | self.master.title("start run v2ray-shell") 113 | self.pack(fill = BOTH, expand = 1) 114 | self.center_window() 115 | 116 | def center_window(self): 117 | w = 620; h = 460 118 | sw = self.master.winfo_screenwidth() 119 | sh = self.master.winfo_screenheight() 120 | 121 | x = (sw -w)/2 122 | y = (sh -h)/2 123 | self.master.geometry("{}x{}+{}+{}".format(w, h, int(x), int(y))) 124 | 125 | if __name__ == "__main__": 126 | root = Tk() 127 | runV2Rayshell(root) 128 | root.mainloop() -------------------------------------------------------------------------------- /v2rayshell.pro: -------------------------------------------------------------------------------- 1 | SOURCES += ./src/bridgehouse/bridge.py \ 2 | ./src/bridgehouse/extension/bridgePreference.py \ 3 | ./src/bridgehouse/extension/bridgetreasureChest.py \ 4 | ./src/bridgehouse/extension/proxyTest.py \ 5 | ./src/bridgehouse/extension/runV2raycore.py \ 6 | ./src/bridgehouse/extension/updatePanel.py \ 7 | ./src/bridgehouse/extension/bugReport.py \ 8 | ./src/bridgehouse/editMap/apiTAB.py \ 9 | ./src/bridgehouse/editMap/cafeteriaTAB.py \ 10 | ./src/bridgehouse/editMap/dnsTAB.py \ 11 | ./src/bridgehouse/editMap/logTAB.py \ 12 | ./src/bridgehouse/editMap/nauticalChartPanel.py \ 13 | ./src/bridgehouse/editMap/policyTAB.py \ 14 | ./src/bridgehouse/editMap/routingTAB.py \ 15 | ./src/bridgehouse/editMap/statsTAB.py \ 16 | ./src/bridgehouse/editMap/transportTAB.py \ 17 | ./src/bridgehouse/editMap/inbound/dokodemodoorPanel.py \ 18 | ./src/bridgehouse/editMap/inbound/httpPanel.py \ 19 | ./src/bridgehouse/editMap/inbound/shadowsocksPanel.py \ 20 | ./src/bridgehouse/editMap/inbound/socksPanel.py \ 21 | ./src/bridgehouse/editMap/inbound/vmessPanel.py \ 22 | ./src/bridgehouse/editMap/inbound/mtPanel.py \ 23 | ./src/bridgehouse/editMap/outbound/blackholePanel.py \ 24 | ./src/bridgehouse/editMap/outbound/freedomPanel.py \ 25 | ./src/bridgehouse/editMap/outbound/shadowsocksPanel.py \ 26 | ./src/bridgehouse/editMap/outbound/socksPanel.py \ 27 | ./src/bridgehouse/editMap/outbound/vmessPanel.py \ 28 | ./src/bridgehouse/editMap/port/inboundPanel.py \ 29 | ./src/bridgehouse/editMap/port/openV2rayJSONFile.py \ 30 | ./src/bridgehouse/editMap/port/outboundPanel.py \ 31 | ./src/bridgehouse/editMap/port/treasureChest.py \ 32 | ./src/bridgehouse/editMap/port/updateListSignal.py \ 33 | ./src/bridgehouse/editMap/router/geoSiteEditorPanel.py \ 34 | ./src/bridgehouse/editMap/transport/certificatesPanel.py \ 35 | ./src/bridgehouse/editMap/transport/http2Panel.py \ 36 | ./src/bridgehouse/editMap/transport/mkcpPanel.py \ 37 | ./src/bridgehouse/editMap/transport/muxPanel.py \ 38 | ./src/bridgehouse/editMap/transport/tcpPanel.py \ 39 | ./src/bridgehouse/editMap/transport/dsPanel.py \ 40 | ./src/bridgehouse/editMap/transport/transportPanel.py \ 41 | ./src/bridgehouse/editMap/transport/wsPanel.py 42 | 43 | TRANSLATIONS += ./translations/zh_CN.ts \ 44 | ./translations/en_US.ts 45 | 46 | CODECFORSRC = UTF-8 47 | --------------------------------------------------------------------------------