├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── cfw.service ├── cfw ├── CFWError.py ├── __init__.py ├── config.py ├── data │ └── __init__.py ├── extensions │ ├── __init__.py │ ├── iptables.py │ └── log.py └── ip_tools.py ├── client.py ├── config.yaml ├── install.py ├── ip_list ├── blacklist.txt ├── blacklist6.txt ├── whitelist.txt └── whitelist6.txt ├── poetry.lock ├── pyproject.toml ├── requirements.txt ├── server.py ├── uninstall.py └── update.py /.gitignore: -------------------------------------------------------------------------------- 1 | # custom rules 2 | cfw/data/rules.list 3 | cfw/data/rules6.list 4 | cfw/data/ipset_blacklist.txt 5 | cfw/data/ipset_blacklist6.txt 6 | cfw/data/iptables-cfw 7 | cfw/data/ip6tables-cfw 8 | log/log.csv 9 | *.DS_Store 10 | 11 | # Byte-compiled / optimized / DLL files 12 | __pycache__/ 13 | *.py[cod] 14 | *$py.class 15 | 16 | # C extensions 17 | *.so 18 | 19 | # Distribution / packaging 20 | .Python 21 | build/ 22 | develop-eggs/ 23 | dist/ 24 | downloads/ 25 | eggs/ 26 | .eggs/ 27 | lib/ 28 | lib64/ 29 | parts/ 30 | sdist/ 31 | var/ 32 | wheels/ 33 | pip-wheel-metadata/ 34 | share/python-wheels/ 35 | *.egg-info/ 36 | .installed.cfg 37 | *.egg 38 | MANIFEST 39 | 40 | # PyInstaller 41 | # Usually these files are written by a python script from a template 42 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 43 | *.manifest 44 | *.spec 45 | 46 | # Installer logs 47 | pip-log.txt 48 | pip-delete-this-directory.txt 49 | 50 | # Unit test / coverage reports 51 | htmlcov/ 52 | .tox/ 53 | .nox/ 54 | .coverage 55 | .coverage.* 56 | .cache 57 | nosetests.xml 58 | coverage.xml 59 | *.cover 60 | *.py,cover 61 | .hypothesis/ 62 | .pytest_cache/ 63 | 64 | # Translations 65 | *.mo 66 | *.pot 67 | 68 | # Django stuff: 69 | *.log 70 | local_settings.py 71 | db.sqlite3 72 | db.sqlite3-journal 73 | 74 | # Flask stuff: 75 | instance/ 76 | .webassets-cache 77 | 78 | # Scrapy stuff: 79 | .scrapy 80 | 81 | # Sphinx documentation 82 | docs/_build/ 83 | 84 | # PyBuilder 85 | target/ 86 | 87 | # Jupyter Notebook 88 | .ipynb_checkpoints 89 | 90 | # IPython 91 | profile_default/ 92 | ipython_config.py 93 | 94 | # pyenv 95 | .python-version 96 | 97 | # pipenv 98 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 99 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 100 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 101 | # install all needed dependencies. 102 | #Pipfile.lock 103 | 104 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 105 | __pypackages__/ 106 | 107 | # Celery stuff 108 | celerybeat-schedule 109 | celerybeat.pid 110 | 111 | # SageMath parsed files 112 | *.sage.py 113 | 114 | # Environments 115 | .env 116 | .venv 117 | env/ 118 | venv/ 119 | ENV/ 120 | env.bak/ 121 | venv.bak/ 122 | 123 | # Spyder project settings 124 | .spyderproject 125 | .spyproject 126 | 127 | # Rope project settings 128 | .ropeproject 129 | 130 | # mkdocs documentation 131 | /site 132 | 133 | # mypy 134 | .mypy_cache/ 135 | .dmypy.json 136 | dmypy.json 137 | 138 | # Pyre type checker 139 | .pyre/ 140 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Cyberbolt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CFW 2 | 3 | [中文版](https://github.com/Cyberbolt/cfw#%E4%B8%AD%E6%96%87%E7%89%88) 4 | 5 | CFW (Cyber Firewall) is a human friendly Linux firewall. It is designed to help prevent denial-of-service attacks (DDOS), and can control the opening and closing of Linux system ports. Since it runs natively based on Linux, CFW has good software compatibility. 6 | 7 | The software is based on iptables and ipset, and is developed in Python. When using it, it is recommended to turn off the firewall (such as firewalld, ufw) that comes with the distribution to avoid conflicts. 8 | 9 | With CFW you will be able to: 10 | 11 | - Automatically block malicious ip in the Internet through custom rules to prevent denial of service attacks 12 | 13 | - Protects all ports of Linux from DDOS attacks, not just web applications 14 | 15 | - Obtain good software compatibility, natively support Nginx, Caddy and other servers 16 | 17 | - Supports the use of CDN, when using CDN, please set the ip segment of CDN to CFW whitelist 18 | 19 | - Control open or close the TCP/UDP port of the Linux system 20 | 21 | - Get a friendly command-line interactive experience 22 | 23 | ### Background 24 | 25 | Web applications run on the complex Internet and may face malicious attacks at any time, resulting in denial of service. To ban these unfriendly ip, CFW was born for this. 26 | 27 | CFW was originally inspired by BaoTa Panel's Nginx Firewall. However, I encountered many difficulties in the process of using the Nginx firewall. The firewall only protects against CC attacks against web applications (usually ports 80 and 443), and cannot protect other ports of Linux servers. At the same time, the firewall needs to be paid on a monthly basis, and it is always bundled with the BaoTa ecology (the latest BaoTa panel even needs to log in to an account bound to the real-name system of the mobile phone), thus limiting the degree of freedom of the software. We wanted to run a firewall on all ports in clean Linux, so we developed one ourselves. 28 | 29 | Since CFW is based on iptables and ipset, it will inevitably conflict with the firewall (such as firewalld, ufw) that comes with the distribution. We have added CFW to control the port switch. 30 | 31 | ### Implementation 32 | 33 | CFW gets all the connections of the current server through `ss -Hntu | awk '{print $5,$6}'` command. If the client's request exceeds the set concurrent number, the ip will be blocked by iptables and stored in the ipset data structure. 34 | 35 | CFW realizes the switch of Linux port by calling iptables command. 36 | 37 | ### Installation 38 | 39 | Please make sure that the system has the following dependencies: 40 | 41 | Example 1 (Debian, Ubuntu) 42 | 43 | ``` 44 | sudo apt install -y curl ipset python3 git net-tools 45 | ``` 46 | 47 | Example 2 (CentOS) 48 | 49 | ``` 50 | sudo yum install -y curl ipset python3 git net-tools 51 | ``` 52 | 53 | After installing the system dependencies, enter the following command to install CFW: 54 | 55 | ``` 56 | sudo curl https://raw.githubusercontent.com/Cyberbolt/cfw/main/install.py | python3 57 | ``` 58 | 59 | Once installed, use `source ~/.bashrc` to activate CFW's environment variables. (A new shell will automatically activate the environment variable) 60 | 61 | Enter `systemctl status cfw` in the Linux terminal, if `active (running)` is displayed, it means that CFW has been successfully run, and it will run automatically when the server restarts. 62 | 63 | ### Uninstallation 64 | 65 | ``` 66 | sudo curl https://raw.githubusercontent.com/Cyberbolt/cfw/main/uninstall.py | python3 67 | ``` 68 | 69 | ### Links 70 | 71 | - GitHub: https://github.com/Cyberbolt/cfw 72 | - CyberLight: https://www.cyberlight.xyz/ 73 | - Potato Blog: https://www.weekly21.com/ 74 | 75 | ### Configuration 76 | 77 | The configuration file is in `/etc/cfw/config.yaml`. After modifying the configuration file, run `systemctl restart cfw` to take effect. 78 | 79 | Configuration file parameter description: 80 | ``` 81 | # CFW running port 82 | port: 6680 83 | # The frequency at which CFW detects connections, in seconds. The 84 | # default here is every 5 seconds. 85 | frequency: 5 86 | # The maximum number of concurrent connections per ip is allowed. 87 | # Exceeding it will be banned by CFW. 88 | max_num: 100 89 | # The time to unblock the ip. The default ip here will be automatically 90 | # unblocked 600 seconds after it is blocked. If the value here is 0, 91 | # it will be permanently banned. 92 | unblock_time: 600 93 | # Data backup time, unit: seconds. 94 | backup_time: 60 95 | 96 | # ipv4 whitelist path. Written in txt, one ip per line, supports 97 | # subnet mask. (The local address and intranet address are in this 98 | # file by default) 99 | whitelist: /etc/cfw/ip_list/whitelist.txt 100 | # ipv4 blacklist path. Written in txt, one ip per line, supports 101 | # subnet mask. 102 | blacklist: /etc/cfw/ip_list/blacklist.txt 103 | 104 | # ipv6 whitelist path. Written in txt, one ip per line. 105 | whitelist6: /etc/cfw/ip_list/whitelist6.txt 106 | # ipv6 blacklist path. Written in txt, one ip per line. 107 | blacklist6: /etc/cfw/ip_list/blacklist6.txt 108 | 109 | # path to log file 110 | log_file_path: /etc/cfw/log/log.csv 111 | # The maximum number of lines in the log file. (It will scroll 112 | # automatically after reaching the maximum number of rows. If 113 | # the value here is 0, the maximum number of rows is not limited) 114 | log_max_lines: 10000000 115 | ``` 116 | 117 | ### Commands 118 | 119 | All `[]` in the command represent variables. 120 | 121 | #### Run 122 | 123 | Stop CFW `systemctl stop cfw` 124 | 125 | Start CFW `systemctl start cfw` 126 | 127 | Restart CFW `systemctl restart cfw` 128 | 129 | #### Ip Blacklist Management 130 | 131 | Manually block a single ipv4 `cfw block [ip]` 132 | 133 | Manually unblock a single ipv4 `cfw unblock [ip]` 134 | 135 | View ipv4 blacklist `cfw blacklist` 136 | 137 | Manually block a single ipv6 `cfw block6 [ip]` 138 | 139 | Manually unblock a single ipv6 `cfw unblock6 [ip]` 140 | 141 | View ipv6 blacklist `cfw blacklist6` 142 | 143 | #### Linux port operation 144 | 145 | Allow ipv4 port `cfw allow [port]` 146 | 147 | Block ipv4 port `cfw deny [port]` 148 | 149 | Separately allow ipv4 TCP port `cfw allow [port]/tcp`, for example `cfw allow 69.162.81.155/tcp` 150 | 151 | Block ipv4 TCP port individually `cfw deny [port]/tcp`, e.g. `cfw deny 69.162.81.155/tcp` 152 | 153 | ipv4 UDP port operation is the same 154 | 155 | View all allowed ipv4 ports `cfw status` 156 | 157 | Allow ipv6 port `cfw allow6 [port]` 158 | 159 | Block ipv6 port `cfw deny6 [port]` 160 | 161 | Separately allow ipv6 TCP port `cfw allow6 [port]/tcp`, for example `cfw allow6 69.162.81.155/tcp` 162 | 163 | Block ipv6 TCP port individually `cfw deny6 [port]/tcp`, e.g. `cfw deny6 69.162.81.155/tcp` 164 | 165 | ipv6 UDP port operation is the same 166 | 167 | View all allowed ipv6 ports `cfw status6` 168 | 169 | #### Log Operations 170 | 171 | Dynamic query log `cfw log [num]`. 'num' is the number of query logs, and the query results will be in reverse chronological order. With your help, CFW can go from strength to strength. 172 | 173 | ### More 174 | 175 | If you encounter any problems in use, please leave a message at [https://github.com/Cyberbolt/cfw/issues](https://github.com/Cyberbolt/cfw/issues). 176 | 177 | # 中文版 178 | 179 | CFW (Cyber Firewall) 是一个人性化的 Linux 防火墙。它旨在协助阻止拒绝服务攻击 (DDOS),同时能控制 Linux 系统端口的开关。由于基于 Linux 原生运行,CFW 拥有良好的软件兼容性。 180 | 181 | 该软件基于 iptables 和 ipset,使用 Python 开发,使用时建议关闭发行版自带的防火墙 (如 firewalld、ufw) 避免冲突。 182 | 183 | 通过 CFW,您将能够: 184 | 185 | - 通过自定义的规则自动封禁互联网中的恶意 ip,以防止拒绝服务攻击 186 | 187 | - 保护 Linux 的所有端口遭受 DDOS 攻击,而不仅仅是 Web 应用 188 | 189 | - 获得良好的软件兼容性,原生支持 Nginx、Caddy 等服务器 190 | 191 | - 支持配合 CDN 使用,使用 CDN 时请将 CDN 的 ip 段设置为 CFW 白名单 192 | 193 | - 控制开启或关闭 Linux 系统的 TCP/UDP 端口 194 | 195 | - 获得友好的命令行交互式体验 196 | 197 | ### 背景 198 | 199 | Web 应用程序运行在复杂的互联网中,随时可能面临恶意攻击,导致拒绝服务现象。封禁这些不友好的 ip,CFW 正是为此而诞生。 200 | 201 | CFW 的灵感最初来自宝塔面板的 Nginx 防火墙。然而,使用 Nginx 防火墙的过程中遇到诸多不顺。该防火墙仅针对 Web 应用 (通常是 80 和 443 端口) 防御 CC 攻击,无法保护 Linux 服务器的其他端口。同时,该防火墙需要按月付费,并始终捆绑宝塔生态 (最新的宝塔面板甚至需要登录绑定手机实名制的账号),从而限制了软件自由度。我们想在纯净的 Linux 中运行防火墙,并对所有端口生效,于是自己开发了一个。 202 | 203 | 由于 CFW 基于 iptables 和 ipset,不免会与发行版自带的防火墙 (如 firewalld、ufw) 冲突,我们增加了 CFW 对端口开关的控制。 204 | 205 | ### 实现 206 | 207 | CFW 通过 `ss -Hntu | awk '{print $5,$6}'` 命令获取当前服务器的所有连接。客户端的请求若超过设定并发数,该 ip 将被 iptables 封禁,并存储在 ipset 数据结构中。 208 | 209 | CFW 通过调用 iptables 命令实现 Linux 端口的开关。 210 | 211 | ### 安装 212 | 213 | 请先确保系统拥有以下依赖: 214 | 215 | 示例 1 (Debian, Ubuntu) 216 | 217 | ``` 218 | sudo apt install -y curl ipset python3 git net-tools 219 | ``` 220 | 221 | 示例 2 (CentOS) 222 | 223 | ``` 224 | sudo yum install -y curl ipset python3 git net-tools 225 | ``` 226 | 227 | 安装好系统依赖后,输入以下命令安装 CFW: 228 | 229 | ``` 230 | sudo curl https://raw.githubusercontent.com/Cyberbolt/cfw/main/install.py | python3 231 | ``` 232 | 233 | 完成安装后,使用 `source ~/.bashrc` 激活 CFW 的环境变量。(新开 shell 将自动激活环境变量) 234 | 235 | 在 Linux 终端输入 `systemctl status cfw`,显示 `active (running)` 字样说明 CFW 已成功运行,同时会在服务器重启时自动运行。 236 | 237 | ### 卸载 238 | 239 | ``` 240 | sudo curl https://raw.githubusercontent.com/Cyberbolt/cfw/main/uninstall.py | python3 241 | ``` 242 | 243 | ### 链接 244 | 245 | - GitHub: https://github.com/Cyberbolt/cfw 246 | - 电光笔记: https://www.cyberlight.xyz/ 247 | - Potato Blog: https://www.weekly21.com/ 248 | 249 | ### 配置 250 | 251 | 配置文件在 `/etc/cfw/config.yaml` 中,修改配置文件后运行 `systemctl restart cfw` 即可生效。 252 | 253 | 配置文件参数说明: 254 | ``` 255 | # CFW 运行端口 256 | port: 6680 257 | # CFW 检测连接的频率,单位:秒。此处默认 5 秒一次。 258 | frequency: 5 259 | # 允许每个 ip 连接的最大并发数,超过将被 CFW 封禁。 260 | max_num: 100 261 | # 解封 ip 的时间。此处默认 ip 被封禁后 600 秒将自动解封。若此处值为 0,则永久封禁。 262 | unblock_time: 600 263 | # 数据备份时间,单位:秒。 264 | backup_time: 60 265 | 266 | # ipv4 白名单路径。写在 txt 中,一行一个 ip,支持子网掩码。(本地地址、内网地址默认在该文件中) 267 | whitelist: /etc/cfw/ip_list/whitelist.txt 268 | # ipv4 黑名单路径。写在 txt 中,一行一个 ip,支持子网掩码。 269 | blacklist: /etc/cfw/ip_list/blacklist.txt 270 | 271 | # ipv6 白名单路径。写在 txt 中,一行一个 ip。 272 | whitelist6: /etc/cfw/ip_list/whitelist6.txt 273 | # ipv6 黑名单路径。写在 txt 中,一行一个 ip。 274 | blacklist6: /etc/cfw/ip_list/blacklist6.txt 275 | 276 | # 日志文件的路径 277 | log_file_path: /etc/cfw/log/log.csv 278 | # 日志文件的最大行数。(达到最大行数后将自动滚动。若此处值为 0,则不限制最大行数) 279 | log_max_lines: 10000000 280 | ``` 281 | 282 | ### 命令 283 | 284 | 命令中所有 `[]` 表示变量。 285 | 286 | #### 运行 287 | 288 | 停止 CFW `systemctl stop cfw` 289 | 290 | 启动 CFW `systemctl start cfw` 291 | 292 | 重启 CFW `systemctl restart cfw` 293 | 294 | #### ip 黑名单管理 295 | 296 | 手动封禁单个 ipv4 `cfw block [ip]` 297 | 298 | 手动解封单个 ipv4 `cfw unblock [ip]` 299 | 300 | 查看 ipv4 黑名单 `cfw blacklist` 301 | 302 | 手动封禁单个 ipv6 `cfw block6 [ip]` 303 | 304 | 手动解封单个 ipv6 `cfw unblock6 [ip]` 305 | 306 | 查看 ipv6 黑名单 `cfw blacklist6` 307 | 308 | #### Linux 端口操作 309 | 310 | 放行 ipv4 端口 `cfw allow [port]` 311 | 312 | 阻止 ipv4 端口 `cfw deny [port]` 313 | 314 | 单独放行 ipv4 TCP 端口 `cfw allow [port]/tcp`,示例如 `cfw allow 69.162.81.155/tcp` 315 | 316 | 单独阻止 ipv4 TCP 端口 `cfw deny [port]/tcp`,示例如 `cfw deny 69.162.81.155/tcp` 317 | 318 | ipv4 UDP 端口操作同理 319 | 320 | 查看所有放行的 ipv4 端口 `cfw status` 321 | 322 | 放行 ipv6 端口 `cfw allow6 [port]` 323 | 324 | 阻止 ipv6 端口 `cfw deny6 [port]` 325 | 326 | 单独放行 ipv6 TCP 端口 `cfw allow6 [port]/tcp`,示例如 `cfw allow6 69.162.81.155/tcp` 327 | 328 | 单独阻止 ipv6 TCP 端口 `cfw deny6 [port]/tcp`,示例如 `cfw deny6 69.162.81.155/tcp` 329 | 330 | ipv6 UDP 端口操作同理 331 | 332 | 查看所有放行的 ipv6 端口 `cfw status6` 333 | 334 | #### 日志操作 335 | 336 | 动态查询日志 `cfw log [num]`。'num' 为查询日志的条数,查询结果将按时间倒序。 337 | 338 | ### 更多 339 | 340 | 如果您在使用中遇到任何问题,欢迎在 [https://github.com/Cyberbolt/cfw/issues](https://github.com/Cyberbolt/cfw/issues) 处留言。有了您的帮助,CFW 才能日渐壮大。 -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cyberbolt/cfw/6fc2c68eaad929078980bb3aadcaa834776cfc89/__init__.py -------------------------------------------------------------------------------- /cfw.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=CFW Sercice 3 | After=multi-user.target network.target nss-lookup.target 4 | [Service] 5 | User=root 6 | WorkingDirectory=/etc/cfw 7 | ExecStart=/etc/cfw/py39/bin/python /etc/cfw/server.py 8 | Restart=on-failure 9 | [Install] 10 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /cfw/CFWError.py: -------------------------------------------------------------------------------- 1 | class CFWError(RuntimeError): 2 | pass 3 | 4 | 5 | class ConfigurationCFWError(CFWError): 6 | pass 7 | 8 | 9 | class ParameterCFWError(CFWError): 10 | pass 11 | 12 | 13 | class ListCFWError(CFWError): 14 | pass 15 | -------------------------------------------------------------------------------- /cfw/__init__.py: -------------------------------------------------------------------------------- 1 | from .extensions.iptables import ( 2 | shell, cmd, Rules, 3 | block_ip, unblock_ip, 4 | block_ip6, unblock_ip6 5 | ) 6 | from .config import config 7 | from .CFWError import * 8 | -------------------------------------------------------------------------------- /cfw/config.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | 3 | from .CFWError import ConfigurationCFWError 4 | 5 | 6 | with open("config.yaml") as f: 7 | config = yaml.load(f, Loader=yaml.FullLoader) 8 | 9 | 10 | def load_config(): 11 | if config.get("port") == None: 12 | raise ConfigurationCFWError( 13 | "The 'port' parameter does not exist." 14 | ) 15 | if type(config.get("port")) != int: 16 | raise ConfigurationCFWError( 17 | "The 'port' parameter must be of integer type." 18 | ) 19 | 20 | if config.get("frequency") == None: 21 | raise ConfigurationCFWError( 22 | "The 'frequency' parameter does not exist." 23 | ) 24 | if type(config.get("frequency")) != int: 25 | raise ConfigurationCFWError( 26 | "The 'frequency' parameter must be of integer type." 27 | ) 28 | 29 | if config.get("max_num") == None: 30 | raise ConfigurationCFWError( 31 | "The 'max_num' parameter does not exist." 32 | ) 33 | if type(config.get("max_num")) != int: 34 | raise ConfigurationCFWError( 35 | "The 'max_num' parameter must be of integer type." 36 | ) 37 | 38 | if config.get("backup_time") == None: 39 | raise ConfigurationCFWError( 40 | "The 'backup_time' parameter does not exist." 41 | ) 42 | if type(config.get("backup_time")) != int: 43 | raise ConfigurationCFWError( 44 | "The 'backup_time' parameter must be of integer type." 45 | ) 46 | 47 | if config.get("unblock_time") == None: 48 | raise ConfigurationCFWError( 49 | "The 'unblock_time' parameter does not exist." 50 | ) 51 | if type(config.get("unblock_time")) != int: 52 | raise ConfigurationCFWError( 53 | "The 'unblock_time' parameter must be of integer type." 54 | ) 55 | 56 | if config.get("whitelist") == None: 57 | raise ConfigurationCFWError( 58 | "The 'whitelist' parameter does not exist." 59 | ) 60 | if config.get("whitelist").rsplit(".", 1)[1] != "txt": 61 | raise ConfigurationCFWError( 62 | "The whitelist file must be in 'txt' format." 63 | ) 64 | if config.get("blacklist") == None: 65 | raise ConfigurationCFWError( 66 | "The 'blacklist' parameter does not exist." 67 | ) 68 | if config.get("blacklist").rsplit(".", 1)[1] != "txt": 69 | raise ConfigurationCFWError( 70 | "The blacklist file must be in 'txt' format." 71 | ) 72 | if config.get("whitelist6") == None: 73 | raise ConfigurationCFWError( 74 | "The 'whitelist6' parameter does not exist." 75 | ) 76 | if config.get("whitelist6").rsplit(".", 1)[1] != "txt": 77 | raise ConfigurationCFWError( 78 | "The whitelist6 file must be in 'txt' format." 79 | ) 80 | if config.get("blacklist6") == None: 81 | raise ConfigurationCFWError( 82 | "The 'blacklist6' parameter does not exist." 83 | ) 84 | if config.get("blacklist6").rsplit(".", 1)[1] != "txt": 85 | raise ConfigurationCFWError( 86 | "The blacklist6 file must be in 'txt' format." 87 | ) 88 | 89 | if config.get("log_file_path") == None: 90 | raise ConfigurationCFWError( 91 | "The 'log_file_path' parameter does not exist." 92 | ) 93 | if config.get("log_file_path").rsplit(".", 1)[1] != "csv": 94 | raise ConfigurationCFWError( 95 | "The log file must be in 'csv' format." 96 | ) 97 | 98 | if config.get("log_max_lines") == None: 99 | raise ConfigurationCFWError( 100 | "The 'log_max_lines' parameter does not exist." 101 | ) 102 | if type(config.get("log_max_lines")) != int: 103 | raise ConfigurationCFWError( 104 | "The 'log_max_lines' parameter must be of integer type." 105 | ) 106 | 107 | 108 | load_config() 109 | -------------------------------------------------------------------------------- /cfw/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cyberbolt/cfw/6fc2c68eaad929078980bb3aadcaa834776cfc89/cfw/data/__init__.py -------------------------------------------------------------------------------- /cfw/extensions/__init__.py: -------------------------------------------------------------------------------- 1 | from .iptables import shell -------------------------------------------------------------------------------- /cfw/extensions/iptables.py: -------------------------------------------------------------------------------- 1 | """ 2 | Call the Linux iptables API. 3 | """ 4 | 5 | import os 6 | import sys 7 | import shutil 8 | import pickle 9 | import threading 10 | import ipaddress 11 | import subprocess 12 | 13 | from .log import Log 14 | from ..config import config 15 | from ..CFWError import * 16 | 17 | lock = threading.RLock() 18 | 19 | 20 | def cmd(cmd: str): 21 | try: 22 | subprocess.run( 23 | cmd, 24 | shell=True, 25 | check=True 26 | ) 27 | return True 28 | except subprocess.CalledProcessError as e: 29 | sys.exit(1) 30 | 31 | 32 | def shell(cmd: str) -> str: 33 | """ 34 | Execute a shell statement and return the output. 35 | """ 36 | r = subprocess.run( 37 | cmd, 38 | shell=True, 39 | stdout=subprocess.PIPE, 40 | stderr=subprocess.STDOUT 41 | ) 42 | return r.stdout.decode() 43 | 44 | 45 | class Rules(list): 46 | """ 47 | Data structure for storing iptables rules 48 | """ 49 | 50 | version = '' 51 | 52 | def __init__(self, version: int = 4): 53 | self.data = [] 54 | if version == 6: 55 | self.version = '6' 56 | elif version != 4: 57 | raise ParameterCFWError("The version number can only be 4 or 6.") 58 | # If rules.list exists, load the previous configuration file. 59 | if os.path.exists(f"cfw/data/rules{self.version}.list"): 60 | with open(f"cfw/data/rules{self.version}.list", "rb") as f: 61 | self.data = pickle.load(f) 62 | return 63 | # New configuration file 64 | ssh_port = shell("netstat -anp | grep ssh | awk '{print $4}' | awk 'NR==1{print}' | awk -F : '{print $2}'").rstrip() 65 | if ssh_port == '\n': 66 | ssh_port = None 67 | if ssh_port: 68 | self.data.append(f"-A INPUT -p tcp --dport {ssh_port} -j ACCEPT") 69 | # self.data.append(f"-A OUTPUT -p tcp --sport {ssh_port} -j ACCEPT") 70 | self.data.append(f"-A INPUT -i lo -j ACCEPT") 71 | self.data.append(f"-I INPUT -m state --state ESTABLISHED -j ACCEPT") 72 | self.data.append(f"-A INPUT -p tcp -m multiport --dports 0:65535 -j DROP") 73 | self.data.append(f"-A OUTPUT -p tcp -m multiport --dports 0:65535 -j ACCEPT") 74 | self.data.append(f"-A INPUT -p udp -m multiport --dports 0:65535 -j DROP") 75 | self.data.append(f"-A OUTPUT -p udp -m multiport --dports 0:65535 -j ACCEPT") 76 | self.data.append(f"-I INPUT -m set --match-set blacklist{self.version} src -j DROP") 77 | 78 | def add_tcp_port(self, port: str) -> bool: 79 | if f"-A INPUT -p tcp --dport {port} -j ACCEPT" in self.data: 80 | return False 81 | self.data.insert(0, f"-A INPUT -p tcp --dport {port} -j ACCEPT") 82 | # self.data.insert(0, f"-A OUTPUT -p tcp --sport {port} -j ACCEPT") 83 | return True 84 | 85 | def rm_tcp_port(self, port: str) -> bool: 86 | if f"-A INPUT -p tcp --dport {port} -j ACCEPT" not in self.data: 87 | return False 88 | self.data.remove(f"-A INPUT -p tcp --dport {port} -j ACCEPT") 89 | # self.data.remove(f"-A OUTPUT -p tcp --sport {port} -j ACCEPT") 90 | return True 91 | 92 | def add_udp_port(self, port: str) -> bool: 93 | if f"-A INPUT -p udp --dport {port} -j ACCEPT" in self.data: 94 | return False 95 | self.data.insert(0, f"-A INPUT -p udp --dport {port} -j ACCEPT") 96 | # self.data.insert(0, f"-A OUTPUT -p udp --sport {port} -j ACCEPT") 97 | return True 98 | 99 | def rm_udp_port(self, port: str) -> bool: 100 | if f"-A INPUT -p udp --dport {port} -j ACCEPT" not in self.data: 101 | return False 102 | self.data.remove(f"-A INPUT -p udp --dport {port} -j ACCEPT") 103 | # self.data.remove(f"-A OUTPUT -p udp --sport {port} -j ACCEPT") 104 | return True 105 | 106 | def save_rules(self): 107 | start = "*filter\n" 108 | end = "COMMIT\n" 109 | content = '' 110 | for line in self.data: 111 | content += line + '\n' 112 | rules = start + content + end 113 | with open(f"cfw/data/rules{self.version}-backup.list", "wb") as f: 114 | pickle.dump(self.data, f) 115 | shutil.move(f"cfw/data/rules{self.version}-backup.list", 116 | f"cfw/data/rules{self.version}.list") 117 | with open(f"cfw/data/ip{self.version}tables-cfw-backup", "w") as f: 118 | f.write(rules) 119 | shutil.move(f"cfw/data/ip{self.version}tables-cfw-backup", 120 | f"cfw/data/ip{self.version}tables-cfw") 121 | shell(f"ip{self.version}tables-restore < cfw/data/ip{self.version}tables-cfw") 122 | 123 | 124 | class Iplist(list): 125 | 126 | version = '' 127 | 128 | def __init__(self, path: str, version: int = 4): 129 | """ 130 | path: Path to the whitelist file 131 | """ 132 | if version == 6: 133 | self.version = 6 134 | elif version == 4: 135 | self.version = 4 136 | else: 137 | raise ParameterCFWError("The version number can only be 4 or 6.") 138 | self.load(path) 139 | 140 | def load(self, path: str): 141 | """ 142 | Load the black and white list. 143 | """ 144 | with open(path, 'r') as f: 145 | text = f.read().strip().rstrip() 146 | for ip in text.splitlines(): 147 | if self.version == 4: 148 | try: 149 | ip = str(ipaddress.IPv4Address(ip)) 150 | except: 151 | try: 152 | ip = str(ipaddress.IPv4Network(ip, strict=False)) 153 | except: 154 | raise ListCFWError("The ip format is incorrect.") 155 | self.append(ip) 156 | elif self.version == 6: 157 | try: 158 | ip = str(ipaddress.IPv6Address(ip)) 159 | except: 160 | try: 161 | ip = str(ipaddress.IPv6Network(ip, strict=False)) 162 | except: 163 | raise ListCFWError("The ip format is incorrect.") 164 | self.append(ip) 165 | 166 | 167 | whitelist = Iplist(config["whitelist"]) 168 | whitelist6 = Iplist(config["whitelist6"], version=6) 169 | log = Log() 170 | 171 | 172 | def block_ip(ip: str, timeout: int = 600, type: str = 'cfw'): 173 | for ip_w in whitelist: 174 | if ipaddress.IPv4Network(ip, strict=False).overlaps(ipaddress.IPv4Network(ip_w, strict=False)): 175 | return { 176 | "code": 0, 177 | "message": "This ip is in the whitelist and cannot be blocked." 178 | } 179 | r = shell(f"ipset add blacklist {ip} timeout {timeout}") 180 | if r != '': 181 | return { 182 | "code": 0, 183 | "message": "This ip has been blocked." 184 | } 185 | log.write(type, f"block {ip}, timeout: {timeout}") 186 | return { 187 | "code": 1 188 | } 189 | 190 | 191 | def unblock_ip(ip: str, type: str = 'cfw'): 192 | r = shell(f"ipset del blacklist {ip}") 193 | if r != '': 194 | return { 195 | "code": 0, 196 | "message": "The ip is not blocked." 197 | } 198 | log.write(type, f"unblock {ip}") 199 | return { 200 | "code": 1 201 | } 202 | 203 | 204 | def block_ip6(ip: str, timeout: int = 600, type: str = 'cfw'): 205 | for ip_w in whitelist6: 206 | if ipaddress.IPv6Network(ip, strict=False).overlaps(ipaddress.IPv6Network(ip_w, strict=False)): 207 | return { 208 | "code": 0, 209 | "message": "This ip is in the whitelist6 and cannot be blocked." 210 | } 211 | r = shell(f"ipset add blacklist6 {ip} timeout {timeout}") 212 | if r != '': 213 | return { 214 | "code": 0, 215 | "message": "This ip has been blocked." 216 | } 217 | log.write(type, f"block {ip}, timeout: {timeout}") 218 | return { 219 | "code": 1 220 | } 221 | 222 | 223 | def unblock_ip6(ip: str, type: str = 'cfw'): 224 | r = shell(f"ipset del blacklist6 {ip}") 225 | if r != '': 226 | return { 227 | "code": 0, 228 | "message": "The ip is not blocked." 229 | } 230 | log.write(type, f"unblock {ip}") 231 | return { 232 | "code": 1 233 | } 234 | 235 | 236 | def ipset_save(): 237 | lock.acquire() 238 | shell("ipset save blacklist -f cfw/data/ipset_blacklist-backup.txt") 239 | lock.release() 240 | shell("mv cfw/data/ipset_blacklist-backup.txt cfw/data/ipset_blacklist.txt") 241 | 242 | 243 | def ipset6_save(): 244 | lock.acquire() 245 | shell("ipset save blacklist6 -f cfw/data/ipset_blacklist6-backup.txt") 246 | lock.release() 247 | shell("mv cfw/data/ipset_blacklist6-backup.txt cfw/data/ipset_blacklist6.txt") 248 | 249 | 250 | def cfw_init(): 251 | if not os.path.exists("cfw/data/ipset_blacklist.txt"): 252 | shell("ipset create blacklist hash:net timeout 2147483") 253 | else: 254 | shell("ipset restore -f cfw/data/ipset_blacklist.txt") 255 | 256 | if not os.path.exists("cfw/data/ipset_blacklist6.txt"): 257 | shell("ipset create blacklist6 hash:net family inet6 timeout 2147483") 258 | else: 259 | shell("ipset restore -f cfw/data/ipset_blacklist6.txt") 260 | -------------------------------------------------------------------------------- /cfw/extensions/log.py: -------------------------------------------------------------------------------- 1 | import os 2 | import csv 3 | import subprocess 4 | 5 | from clock_timer import timer 6 | 7 | from ..config import config 8 | 9 | 10 | def shell(cmd: str) -> str: 11 | """ 12 | Execute a shell statement and return the output. 13 | """ 14 | r = subprocess.run( 15 | cmd, 16 | shell=True, 17 | stdout=subprocess.PIPE, 18 | stderr=subprocess.STDOUT 19 | ) 20 | return r.stdout.decode() 21 | 22 | 23 | class Log: 24 | 25 | def __init__(self): 26 | if not os.path.exists(config["log_file_path"]): 27 | path = config["log_file_path"].rsplit("/", 1) 28 | if len(path) > 1: 29 | dir, file_name = path 30 | os.mkdir(dir) 31 | with open(config["log_file_path"], "w") as csvfile: 32 | writer = csv.writer(csvfile) 33 | writer.writerow(["time", "type", "content"]) 34 | 35 | def write(self, type: str, content: str): 36 | if config["log_max_lines"] != 0: 37 | lines_num = int(shell(f'wc -l {config["log_file_path"]}').split(' ', 1)[0]) 38 | if lines_num > config["log_max_lines"]: 39 | shell(f'sed -i "2d" {config["log_file_path"]}') 40 | with open(config["log_file_path"], "a") as csvfile: 41 | writer = csv.writer(csvfile) 42 | writer.writerow([timer.get_current_time(), type, content]) 43 | -------------------------------------------------------------------------------- /cfw/ip_tools.py: -------------------------------------------------------------------------------- 1 | """ 2 | Get and handle socket connections. 3 | """ 4 | 5 | import ipaddress 6 | import multiprocessing 7 | from typing import Tuple 8 | from concurrent.futures import ThreadPoolExecutor 9 | 10 | import numpy as np 11 | import pandas as pd 12 | from apscheduler.schedulers.background import BackgroundScheduler 13 | 14 | from .config import config 15 | from .extensions import iptables, shell 16 | from .CFWError import ListCFWError 17 | 18 | executor = ThreadPoolExecutor( 19 | max_workers = multiprocessing.cpu_count() * 2 + 1 20 | ) 21 | 22 | 23 | def get_ss_ip() -> pd.DataFrame: 24 | """ 25 | Get the current socket connection and convert it to a DataFrame. 26 | """ 27 | text = shell("ss -Hntu | awk '{print $5,$6}' ") 28 | data = [] 29 | for line in text.split("\n")[:-1]: 30 | server, client = line.split(" ") 31 | server_ip, server_port = server.split(":") 32 | client_ip, client_port = client.split(":") 33 | # If it is ipv6, convert the format. 34 | if server_ip[0] == "[": 35 | server_ip = server_ip[1:-1] 36 | if client_ip[0] == "[": 37 | client_ip = client_ip[1:-1] 38 | data.append([server_ip, server_port, client_ip, client_port]) 39 | data_df = pd.DataFrame( 40 | data, 41 | columns=["server_ip", "server_port", "client_ip", "client_port"] 42 | ) 43 | return data_df 44 | 45 | 46 | def block_blacklist_ip(): 47 | blacklist = iptables.Iplist(config["blacklist"]) 48 | for ip in blacklist: 49 | r = iptables.block_ip(ip, timeout=0) 50 | if r.get("message") == "This ip is in the whitelist and cannot be blocked.": 51 | raise ListCFWError(f"The '{ip}' of the blacklist exists in the whitelist.") 52 | 53 | blacklist6 = iptables.Iplist(config["blacklist6"]) 54 | for ip in blacklist6: 55 | r = iptables.block_ip6(ip, timeout=0) 56 | if r.get("message") == "This ip is in the whitelist6 and cannot be blocked.": 57 | raise ListCFWError(f"The '{ip}' of the blacklist6 exists in the whitelist6.") 58 | 59 | 60 | def block_ss_ip(): 61 | data = get_ss_ip() 62 | data = data.groupby(["client_ip"], as_index = False)["client_ip"].agg({ 63 | "num": np.size 64 | }).sort_values(by="num", ascending=False, ignore_index=True) 65 | ips = [] 66 | for index, row in data.iterrows(): 67 | if row["num"] > config["max_num"]: 68 | ips.append(row["client_ip"]) 69 | for ip in ips: 70 | version = ipaddress.ip_address(ip).version 71 | if version == 4: 72 | # iptables.block_ip(ip, config["unblock_time"]) 73 | executor.submit(iptables.block_ip, ip, config["unblock_time"]) 74 | elif version == 6: 75 | # iptables.block_ip6(ip, config["unblock_time"]) 76 | executor.submit(iptables.block_ip6, ip, config["unblock_time"]) 77 | 78 | 79 | def start() -> Tuple[iptables.Rules, iptables.Rules]: 80 | iptables.cfw_init() 81 | rules = iptables.Rules() 82 | rules6 = iptables.Rules(version=6) 83 | rules.save_rules() 84 | rules6.save_rules() 85 | 86 | block_blacklist_ip() 87 | 88 | sched = BackgroundScheduler(timezone="Asia/Shanghai") 89 | # ips are banned periodically. 90 | sched.add_job( 91 | block_ss_ip, 92 | 'interval', 93 | seconds=config["frequency"], 94 | id="block_ss_ip" 95 | ) 96 | # Save an ipset every 60 seconds. 97 | sched.add_job( 98 | iptables.ipset_save, 99 | 'interval', 100 | seconds=config["backup_time"], 101 | id="iptables.ipset_save" 102 | ) 103 | sched.add_job( 104 | iptables.ipset6_save, 105 | 'interval', 106 | seconds=config["backup_time"], 107 | id="iptables.ipset6_save" 108 | ) 109 | sched.start() 110 | print("CFW starts running.") 111 | return rules, rules6 112 | -------------------------------------------------------------------------------- /client.py: -------------------------------------------------------------------------------- 1 | import os 2 | # Specify the interpreter running path as the cfw project directory. 3 | current_path = os.path.dirname(__file__) 4 | os.chdir(current_path) 5 | 6 | import httpx 7 | import click 8 | import pandas as pd 9 | 10 | from cfw import cmd, shell, config, ParameterCFWError 11 | 12 | # Show all columns and rows. 13 | pd.set_option('display.max_columns', None) 14 | pd.set_option('display.max_rows', None) 15 | pd.set_option('display.colheader_justify', 'center') 16 | 17 | 18 | @click.group() 19 | def cli(): 20 | pass 21 | 22 | 23 | """ 24 | ipv4 block / unblock 25 | """ 26 | @cli.command(help="Manually block a single ipv4.") 27 | @click.argument("ip", type=str) 28 | @click.option("-t", "--timeout", default=600, type=int) 29 | def block(ip: str, timeout: int): 30 | if timeout > 2000000: 31 | raise ParameterCFWError("The maximum ban time cannot exceed 2,000,000 seconds. If you want to ban permanently, please use 0 instead.") 32 | r = httpx.get(f"http://127.0.0.1:{config['port']}/block_ip", 33 | params={"ip": ip, "timeout": timeout}) 34 | if r.json()["code"]: 35 | pass 36 | else: 37 | print(r.json()["message"]) 38 | 39 | 40 | @cli.command(help="Manually unblock a single ipv4.") 41 | @click.argument("ip", type=str) 42 | def unblock(ip: str): 43 | r = httpx.get(f"http://127.0.0.1:{config['port']}/unblock_ip", 44 | params={"ip": ip}) 45 | if r.json()["code"]: 46 | pass 47 | else: 48 | print(r.json()["message"]) 49 | 50 | 51 | @cli.command(help="View ipv4 blacklist.") 52 | def blacklist(): 53 | r = httpx.get(f"http://127.0.0.1:{config['port']}/blacklist") 54 | text = r.json()["message"] 55 | ips = [] 56 | elements = text.split("Members:\n")[1].strip().split("\n") 57 | if elements[0] == '': 58 | return 59 | for element in elements: 60 | ip, timeout = element.split(" timeout ") 61 | ips.append([ip, timeout]) 62 | data = pd.DataFrame(ips, columns=["blacklist", "timeout"]) 63 | print(data.to_string(index=False)) 64 | 65 | 66 | """ 67 | ipv6 block / unblock 68 | """ 69 | @cli.command(help="Manually block a single ipv6.") 70 | @click.argument("ip", type=str) 71 | @click.option("-t", "--timeout", default=600, type=int) 72 | def block6(ip: str, timeout: int): 73 | if timeout > 2000000: 74 | raise ParameterCFWError("The maximum ban time cannot exceed 2,000,000 seconds. If you want to ban permanently, please use 0 instead.") 75 | r = httpx.get(f"http://127.0.0.1:{config['port']}/block_ip6", 76 | params={"ip": ip, "timeout": timeout}) 77 | if r.json()["code"]: 78 | pass 79 | else: 80 | print(r.json()["message"]) 81 | 82 | 83 | @cli.command(help="Manually unblock a single ipv6.") 84 | @click.argument("ip", type=str) 85 | def unblock6(ip: str): 86 | r = httpx.get(f"http://127.0.0.1:{config['port']}/unblock_ip6", 87 | params={"ip": ip}) 88 | if r.json()["code"]: 89 | pass 90 | else: 91 | print(r.json()["message"]) 92 | 93 | 94 | @cli.command(help="View ipv6 blacklist.") 95 | def blacklist6(): 96 | r = httpx.get(f"http://127.0.0.1:{config['port']}/blacklist6") 97 | text = r.json()["message"] 98 | ips = [] 99 | elements = text.split("Members:\n")[1].strip().split("\n") 100 | if elements[0] == '': 101 | return 102 | for element in elements: 103 | ip, timeout = element.split(" timeout ") 104 | ips.append([ip, timeout]) 105 | data = pd.DataFrame(ips, columns=["blacklist6", "timeout"]) 106 | print(data.to_string(index=False)) 107 | 108 | 109 | """ 110 | ipv4 port 111 | """ 112 | @cli.command(help="Allow ipv4 port.") 113 | @click.argument("port", type=str) 114 | def allow(port: str): 115 | try: 116 | if int(port) < 0 and int(port) > 65535: 117 | raise ParameterCFWError("The port number range can only be 0 to 65535.") 118 | r = httpx.get(f"http://127.0.0.1:{config['port']}/allow_port", 119 | params={"port": port, "protocol": "all"}) 120 | except ValueError: 121 | try: 122 | port, protocol = port.split("/") 123 | if int(port) < 0 and int(port) > 65535: 124 | raise ParameterCFWError("The port number range can only be 0 to 65535.") 125 | if protocol != "tcp" and protocol != "udp": 126 | raise ParameterCFWError("The port protocol can only be tcp or udp.") 127 | r = httpx.get(f"http://127.0.0.1:{config['port']}/allow_port", 128 | params={"port": port, "protocol": protocol}) 129 | except ValueError: 130 | raise ParameterCFWError("'cfw allow' syntax error.") 131 | if r.json()["code"]: 132 | pass 133 | else: 134 | print(r.json()["message"]) 135 | 136 | 137 | @cli.command(help="Block ipv4 port.") 138 | @click.argument("port", type=str) 139 | def deny(port: str): 140 | try: 141 | if int(port) < 0 and int(port) > 65535: 142 | raise ParameterCFWError("The port number range can only be 0 to 65535.") 143 | r = httpx.get(f"http://127.0.0.1:{config['port']}/deny_port", 144 | params={"port": port, "protocol": "all"}) 145 | except ValueError: 146 | try: 147 | port, protocol = port.split("/") 148 | if int(port) < 0 and int(port) > 65535: 149 | raise ParameterCFWError("The port number range can only be 0 to 65535.") 150 | if protocol != "tcp" and protocol != "udp": 151 | raise ParameterCFWError("The port protocol can only be tcp or udp.") 152 | r = httpx.get(f"http://127.0.0.1:{config['port']}/deny_port", 153 | params={"port": port, "protocol": protocol}) 154 | except ValueError: 155 | raise ParameterCFWError("'cfw deny' syntax error.") 156 | if r.json()["code"]: 157 | pass 158 | else: 159 | print(r.json()["message"]) 160 | 161 | 162 | @cli.command(help="View all allowed ipv4 ports.") 163 | def status(): 164 | r = httpx.get(f"http://127.0.0.1:{config['port']}/status") 165 | data = r.json()["message"] 166 | if not data: 167 | return 168 | data = pd.DataFrame(data, columns=["port", "protocol"]) 169 | print(data.sort_values("port").to_string(index=False)) 170 | 171 | 172 | """ 173 | ipv6 port 174 | """ 175 | @cli.command(help="Allow ipv6 port.") 176 | @click.argument("port", type=str) 177 | def allow6(port: str): 178 | try: 179 | if int(port) < 0 and int(port) > 65535: 180 | raise ParameterCFWError("The port number range can only be 0 to 65535.") 181 | r = httpx.get(f"http://127.0.0.1:{config['port']}/allow_port6", 182 | params={"port": port, "protocol": "all"}) 183 | except ValueError: 184 | try: 185 | port, protocol = port.split("/") 186 | if int(port) < 0 and int(port) > 65535: 187 | raise ParameterCFWError("The port number range can only be 0 to 65535.") 188 | if protocol != "tcp" and protocol != "udp": 189 | raise ParameterCFWError("The port protocol can only be tcp or udp.") 190 | r = httpx.get(f"http://127.0.0.1:{config['port']}/allow_port6", 191 | params={"port": port, "protocol": protocol}) 192 | except ValueError: 193 | raise ParameterCFWError("'cfw allow' syntax error.") 194 | if r.json()["code"]: 195 | pass 196 | else: 197 | print(r.json()["message"]) 198 | 199 | 200 | @cli.command(help="Block ipv6 port.") 201 | @click.argument("port", type=str) 202 | def deny6(port: str): 203 | try: 204 | if int(port) < 0 and int(port) > 65535: 205 | raise ParameterCFWError("The port number range can only be 0 to 65535.") 206 | r = httpx.get(f"http://127.0.0.1:{config['port']}/deny_port6", 207 | params={"port": port, "protocol": "all"}) 208 | except ValueError: 209 | try: 210 | port, protocol = port.split("/") 211 | if int(port) < 0 and int(port) > 65535: 212 | raise ParameterCFWError("The port number range can only be 0 to 65535.") 213 | if protocol != "tcp" and protocol != "udp": 214 | raise ParameterCFWError("The port protocol can only be tcp or udp.") 215 | r = httpx.get(f"http://127.0.0.1:{config['port']}/deny_port6", 216 | params={"port": port, "protocol": protocol}) 217 | except ValueError: 218 | raise ParameterCFWError("'cfw deny' syntax error.") 219 | if r.json()["code"]: 220 | pass 221 | else: 222 | print(r.json()["message"]) 223 | 224 | 225 | @cli.command(help="View all allowed ipv6 ports.") 226 | def status6(): 227 | r = httpx.get(f"http://127.0.0.1:{config['port']}/status6") 228 | data = r.json()["message"] 229 | if not data: 230 | return 231 | data = pd.DataFrame(data, columns=["port", "protocol"]) 232 | print(data.sort_values("port").to_string(index=False)) 233 | 234 | 235 | """ 236 | Log 237 | """ 238 | @cli.command(help="Dynamic query log.") 239 | @click.argument("num", type=str) 240 | def log(num: int = 1000): 241 | cmd(f"tail -f -n {num} {config['log_file_path']}") 242 | 243 | 244 | """ 245 | Update CFW 246 | """ 247 | @cli.command(help="Update CFW") 248 | def update(): 249 | shell("git --git-dir=/etc/cfw/.git --work-tree=/etc/cfw pull https://github.com/Cyberbolt/cfw.git --quiet") 250 | cmd("systemctl start cfw") 251 | print("CFW has been updated.") 252 | 253 | 254 | if __name__ == '__main__': 255 | cli(obj={}) 256 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | # For detailed parameter description, please refer to https://github.com/Cyberbolt/cfw#configuration 2 | 3 | # cfw run port 4 | port: 6680 5 | # Program running frequency, unit: second 6 | frequency: 5 7 | # Maximum number of connections running 8 | max_num: 100 9 | # time to unblock ip 10 | unblock_time: 600 11 | # Data backup time 12 | backup_time: 60 13 | 14 | # ipv4 whitelist 15 | whitelist: /etc/cfw/ip_list/whitelist.txt 16 | # ipv4 blacklist 17 | blacklist: /etc/cfw/ip_list/blacklist.txt 18 | 19 | # ipv6 whitelist 20 | whitelist6: /etc/cfw/ip_list/whitelist6.txt 21 | # ipv6 blacklist 22 | blacklist6: /etc/cfw/ip_list/blacklist6.txt 23 | 24 | # log file path 25 | log_file_path: /etc/cfw/log/log.csv 26 | log_max_lines: 10000000 27 | -------------------------------------------------------------------------------- /install.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import subprocess 3 | 4 | 5 | VSERSION = "1.0.3" 6 | 7 | 8 | def cmd(cmd: str): 9 | try: 10 | subprocess.run(cmd, shell=True, check=True) 11 | return True 12 | except subprocess.CalledProcessError as e: 13 | sys.exit(1) 14 | 15 | 16 | def shell(cmd: str) -> str: 17 | """ 18 | Execute a shell statement and return the output. 19 | """ 20 | r = subprocess.run( 21 | cmd, shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT 22 | ) 23 | return r.stdout.decode() 24 | 25 | 26 | def main(): 27 | # clone cfw 28 | cmd("git clone -b {} https://github.com/Cyberbolt/cfw.git /etc/cfw/".format(VSERSION)) 29 | # choose linux architecture 30 | architecture = shell("uname -m").strip() 31 | if architecture != "aarch64" and architecture != "x86_64": 32 | print( 33 | "This CPU architecture is not supported, only x86_64 and arm64 are supported." 34 | ) 35 | sys.exit(1) 36 | cmd( 37 | "curl -# -OL https://github.com/Cyberbolt/cfw/releases/download/1.0.0/py39-Linux-{}.tar.gz".format( 38 | architecture 39 | ) 40 | ) 41 | # Unzip the python3.9 version 42 | cmd("tar -zxvf py39-Linux-{}.tar.gz".format(architecture)) 43 | cmd("mv py39 /etc/cfw/py39") 44 | cmd("rm -rf py39-Linux-{}.tar.gz".format(architecture)) 45 | # Install Python dependencies 46 | cmd("/etc/cfw/py39/bin/python -m pip install -r /etc/cfw/requirements.txt") 47 | # run with systemd 48 | cmd("cp /etc/cfw/cfw.service /etc/systemd/system/cfw.service") 49 | cmd("systemctl daemon-reload") 50 | cmd("systemctl enable cfw") 51 | cmd("systemctl start cfw") 52 | # add environment variable 53 | cmd("""echo "alias cfw='/etc/cfw/py39/bin/python /etc/cfw/client.py'">>~/.bashrc""") 54 | print( 55 | """ 56 | CFW installation is complete, please enter the following command to activate 'cfw'. 57 | 58 | source ~/.bashrc 59 | """ 60 | ) 61 | 62 | 63 | if __name__ == "__main__": 64 | main() 65 | -------------------------------------------------------------------------------- /ip_list/blacklist.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cyberbolt/cfw/6fc2c68eaad929078980bb3aadcaa834776cfc89/ip_list/blacklist.txt -------------------------------------------------------------------------------- /ip_list/blacklist6.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cyberbolt/cfw/6fc2c68eaad929078980bb3aadcaa834776cfc89/ip_list/blacklist6.txt -------------------------------------------------------------------------------- /ip_list/whitelist.txt: -------------------------------------------------------------------------------- 1 | 10.0.0.0/8 2 | 172.16.0.0/12 3 | 192.168.0.0/16 4 | 127.0.0.0/8 5 | 169.254.0.0/16 6 | 192.0.2.0/24 7 | 224.0.0.0/4 8 | 240.0.0.0/4 9 | 0.0.0.0/8 -------------------------------------------------------------------------------- /ip_list/whitelist6.txt: -------------------------------------------------------------------------------- 1 | ::ffff:127.0.0.1 -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "anyio" 3 | version = "3.6.2" 4 | description = "High level compatibility layer for multiple asynchronous event loop implementations" 5 | category = "main" 6 | optional = false 7 | python-versions = ">=3.6.2" 8 | 9 | [package.dependencies] 10 | idna = ">=2.8" 11 | sniffio = ">=1.1" 12 | 13 | [package.extras] 14 | doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] 15 | test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] 16 | trio = ["trio (>=0.16,<0.22)"] 17 | 18 | [package.source] 19 | type = "legacy" 20 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 21 | reference = "aliyun" 22 | 23 | [[package]] 24 | name = "apscheduler" 25 | version = "3.9.1" 26 | description = "In-process task scheduler with Cron-like capabilities" 27 | category = "main" 28 | optional = false 29 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 30 | 31 | [package.dependencies] 32 | pytz = "*" 33 | setuptools = ">=0.7" 34 | six = ">=1.4.0" 35 | tzlocal = ">=2.0,<3.0.0 || >=4.0.0" 36 | 37 | [package.extras] 38 | asyncio = ["trollius"] 39 | doc = ["sphinx", "sphinx-rtd-theme"] 40 | gevent = ["gevent"] 41 | mongodb = ["pymongo (>=3.0)"] 42 | redis = ["redis (>=3.0)"] 43 | rethinkdb = ["rethinkdb (>=2.4.0)"] 44 | sqlalchemy = ["sqlalchemy (>=0.8)"] 45 | testing = ["mock", "pytest", "pytest-asyncio", "pytest-asyncio (<0.6)", "pytest-cov", "pytest-tornado5"] 46 | tornado = ["tornado (>=4.3)"] 47 | twisted = ["twisted"] 48 | zookeeper = ["kazoo"] 49 | 50 | [package.source] 51 | type = "legacy" 52 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 53 | reference = "aliyun" 54 | 55 | [[package]] 56 | name = "certifi" 57 | version = "2022.9.24" 58 | description = "Python package for providing Mozilla's CA Bundle." 59 | category = "main" 60 | optional = false 61 | python-versions = ">=3.6" 62 | 63 | [package.source] 64 | type = "legacy" 65 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 66 | reference = "aliyun" 67 | 68 | [[package]] 69 | name = "click" 70 | version = "8.1.3" 71 | description = "Composable command line interface toolkit" 72 | category = "main" 73 | optional = false 74 | python-versions = ">=3.7" 75 | 76 | [package.dependencies] 77 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 78 | 79 | [package.source] 80 | type = "legacy" 81 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 82 | reference = "aliyun" 83 | 84 | [[package]] 85 | name = "clock-timer" 86 | version = "0.1.6" 87 | description = "clock_timer 是 Python 下的时间库,用于时间字符串处理,可在 Web 开发、数据分析 等领域使用。该库 80% 基于 datetime ,使用该库,您能更人性化地处理时间字符串,而无需每次查询 datetime 繁琐的接口。该库主要用于时间加减计算、获取时间对应的周数和星座、获取当前时间、获取时间戳、时间戳和时间字符串的转换 等功能。后期仍会不断优化,欢迎留言。" 88 | category = "main" 89 | optional = false 90 | python-versions = "*" 91 | 92 | [package.dependencies] 93 | python-dateutil = ">=2.8.2" 94 | six = ">=1.16.0" 95 | 96 | [package.source] 97 | type = "legacy" 98 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 99 | reference = "aliyun" 100 | 101 | [[package]] 102 | name = "colorama" 103 | version = "0.4.6" 104 | description = "Cross-platform colored terminal text." 105 | category = "main" 106 | optional = false 107 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 108 | 109 | [package.source] 110 | type = "legacy" 111 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 112 | reference = "aliyun" 113 | 114 | [[package]] 115 | name = "fastapi" 116 | version = "0.85.1" 117 | description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" 118 | category = "main" 119 | optional = false 120 | python-versions = ">=3.7" 121 | 122 | [package.dependencies] 123 | pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" 124 | starlette = "0.20.4" 125 | 126 | [package.extras] 127 | all = ["email-validator (>=1.1.1,<2.0.0)", "itsdangerous (>=1.1.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "uvicorn[standard] (>=0.12.0,<0.19.0)"] 128 | dev = ["autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "pre-commit (>=2.17.0,<3.0.0)", "uvicorn[standard] (>=0.12.0,<0.19.0)"] 129 | doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer (>=0.4.1,<0.7.0)"] 130 | test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.8.0)", "databases[sqlite] (>=0.3.2,<0.7.0)", "email-validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.23.0,<0.24.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.971)", "orjson (>=3.2.1,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=7.1.3,<8.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "types-orjson (==3.6.2)", "types-ujson (==5.4.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"] 131 | 132 | [package.source] 133 | type = "legacy" 134 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 135 | reference = "aliyun" 136 | 137 | [[package]] 138 | name = "h11" 139 | version = "0.12.0" 140 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" 141 | category = "main" 142 | optional = false 143 | python-versions = ">=3.6" 144 | 145 | [package.source] 146 | type = "legacy" 147 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 148 | reference = "aliyun" 149 | 150 | [[package]] 151 | name = "httpcore" 152 | version = "0.15.0" 153 | description = "A minimal low-level HTTP client." 154 | category = "main" 155 | optional = false 156 | python-versions = ">=3.7" 157 | 158 | [package.dependencies] 159 | anyio = ">=3.0.0,<4.0.0" 160 | certifi = "*" 161 | h11 = ">=0.11,<0.13" 162 | sniffio = ">=1.0.0,<2.0.0" 163 | 164 | [package.extras] 165 | http2 = ["h2 (>=3,<5)"] 166 | socks = ["socksio (>=1.0.0,<2.0.0)"] 167 | 168 | [package.source] 169 | type = "legacy" 170 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 171 | reference = "aliyun" 172 | 173 | [[package]] 174 | name = "httptools" 175 | version = "0.5.0" 176 | description = "A collection of framework independent HTTP protocol utils." 177 | category = "main" 178 | optional = false 179 | python-versions = ">=3.5.0" 180 | 181 | [package.extras] 182 | test = ["Cython (>=0.29.24,<0.30.0)"] 183 | 184 | [package.source] 185 | type = "legacy" 186 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 187 | reference = "aliyun" 188 | 189 | [[package]] 190 | name = "httpx" 191 | version = "0.23.0" 192 | description = "The next generation HTTP client." 193 | category = "main" 194 | optional = false 195 | python-versions = ">=3.7" 196 | 197 | [package.dependencies] 198 | certifi = "*" 199 | httpcore = ">=0.15.0,<0.16.0" 200 | rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} 201 | sniffio = "*" 202 | 203 | [package.extras] 204 | brotli = ["brotli", "brotlicffi"] 205 | cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"] 206 | http2 = ["h2 (>=3,<5)"] 207 | socks = ["socksio (>=1.0.0,<2.0.0)"] 208 | 209 | [package.source] 210 | type = "legacy" 211 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 212 | reference = "aliyun" 213 | 214 | [[package]] 215 | name = "idna" 216 | version = "3.4" 217 | description = "Internationalized Domain Names in Applications (IDNA)" 218 | category = "main" 219 | optional = false 220 | python-versions = ">=3.5" 221 | 222 | [package.source] 223 | type = "legacy" 224 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 225 | reference = "aliyun" 226 | 227 | [[package]] 228 | name = "numpy" 229 | version = "1.23.4" 230 | description = "NumPy is the fundamental package for array computing with Python." 231 | category = "main" 232 | optional = false 233 | python-versions = ">=3.8" 234 | 235 | [package.source] 236 | type = "legacy" 237 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 238 | reference = "aliyun" 239 | 240 | [[package]] 241 | name = "pandas" 242 | version = "1.5.0" 243 | description = "Powerful data structures for data analysis, time series, and statistics" 244 | category = "main" 245 | optional = false 246 | python-versions = ">=3.8" 247 | 248 | [package.dependencies] 249 | numpy = [ 250 | {version = ">=1.20.3", markers = "python_version < \"3.10\""}, 251 | {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, 252 | ] 253 | python-dateutil = ">=2.8.1" 254 | pytz = ">=2020.1" 255 | 256 | [package.extras] 257 | test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] 258 | 259 | [package.source] 260 | type = "legacy" 261 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 262 | reference = "aliyun" 263 | 264 | [[package]] 265 | name = "pydantic" 266 | version = "1.10.2" 267 | description = "Data validation and settings management using python type hints" 268 | category = "main" 269 | optional = false 270 | python-versions = ">=3.7" 271 | 272 | [package.dependencies] 273 | typing-extensions = ">=4.1.0" 274 | 275 | [package.extras] 276 | dotenv = ["python-dotenv (>=0.10.4)"] 277 | email = ["email-validator (>=1.0.3)"] 278 | 279 | [package.source] 280 | type = "legacy" 281 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 282 | reference = "aliyun" 283 | 284 | [[package]] 285 | name = "python-dateutil" 286 | version = "2.8.2" 287 | description = "Extensions to the standard Python datetime module" 288 | category = "main" 289 | optional = false 290 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 291 | 292 | [package.dependencies] 293 | six = ">=1.5" 294 | 295 | [package.source] 296 | type = "legacy" 297 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 298 | reference = "aliyun" 299 | 300 | [[package]] 301 | name = "python-dotenv" 302 | version = "0.21.0" 303 | description = "Read key-value pairs from a .env file and set them as environment variables" 304 | category = "main" 305 | optional = false 306 | python-versions = ">=3.7" 307 | 308 | [package.extras] 309 | cli = ["click (>=5.0)"] 310 | 311 | [package.source] 312 | type = "legacy" 313 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 314 | reference = "aliyun" 315 | 316 | [[package]] 317 | name = "pytz" 318 | version = "2022.4" 319 | description = "World timezone definitions, modern and historical" 320 | category = "main" 321 | optional = false 322 | python-versions = "*" 323 | 324 | [package.source] 325 | type = "legacy" 326 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 327 | reference = "aliyun" 328 | 329 | [[package]] 330 | name = "pytz-deprecation-shim" 331 | version = "0.1.0.post0" 332 | description = "Shims to make deprecation of pytz easier" 333 | category = "main" 334 | optional = false 335 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" 336 | 337 | [package.dependencies] 338 | tzdata = {version = "*", markers = "python_version >= \"3.6\""} 339 | 340 | [package.source] 341 | type = "legacy" 342 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 343 | reference = "aliyun" 344 | 345 | [[package]] 346 | name = "pyyaml" 347 | version = "6.0" 348 | description = "YAML parser and emitter for Python" 349 | category = "main" 350 | optional = false 351 | python-versions = ">=3.6" 352 | 353 | [package.source] 354 | type = "legacy" 355 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 356 | reference = "aliyun" 357 | 358 | [[package]] 359 | name = "rfc3986" 360 | version = "1.5.0" 361 | description = "Validating URI References per RFC 3986" 362 | category = "main" 363 | optional = false 364 | python-versions = "*" 365 | 366 | [package.dependencies] 367 | idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} 368 | 369 | [package.extras] 370 | idna2008 = ["idna"] 371 | 372 | [package.source] 373 | type = "legacy" 374 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 375 | reference = "aliyun" 376 | 377 | [[package]] 378 | name = "setuptools" 379 | version = "65.4.1" 380 | description = "Easily download, build, install, upgrade, and uninstall Python packages" 381 | category = "main" 382 | optional = false 383 | python-versions = ">=3.7" 384 | 385 | [package.extras] 386 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] 387 | testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] 388 | testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] 389 | 390 | [package.source] 391 | type = "legacy" 392 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 393 | reference = "aliyun" 394 | 395 | [[package]] 396 | name = "six" 397 | version = "1.16.0" 398 | description = "Python 2 and 3 compatibility utilities" 399 | category = "main" 400 | optional = false 401 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 402 | 403 | [package.source] 404 | type = "legacy" 405 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 406 | reference = "aliyun" 407 | 408 | [[package]] 409 | name = "sniffio" 410 | version = "1.3.0" 411 | description = "Sniff out which async library your code is running under" 412 | category = "main" 413 | optional = false 414 | python-versions = ">=3.7" 415 | 416 | [package.source] 417 | type = "legacy" 418 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 419 | reference = "aliyun" 420 | 421 | [[package]] 422 | name = "starlette" 423 | version = "0.20.4" 424 | description = "The little ASGI library that shines." 425 | category = "main" 426 | optional = false 427 | python-versions = ">=3.7" 428 | 429 | [package.dependencies] 430 | anyio = ">=3.4.0,<5" 431 | typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} 432 | 433 | [package.extras] 434 | full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] 435 | 436 | [package.source] 437 | type = "legacy" 438 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 439 | reference = "aliyun" 440 | 441 | [[package]] 442 | name = "typing-extensions" 443 | version = "4.4.0" 444 | description = "Backported and Experimental Type Hints for Python 3.7+" 445 | category = "main" 446 | optional = false 447 | python-versions = ">=3.7" 448 | 449 | [package.source] 450 | type = "legacy" 451 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 452 | reference = "aliyun" 453 | 454 | [[package]] 455 | name = "tzdata" 456 | version = "2022.5" 457 | description = "Provider of IANA time zone data" 458 | category = "main" 459 | optional = false 460 | python-versions = ">=2" 461 | 462 | [package.source] 463 | type = "legacy" 464 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 465 | reference = "aliyun" 466 | 467 | [[package]] 468 | name = "tzlocal" 469 | version = "4.2" 470 | description = "tzinfo object for the local timezone" 471 | category = "main" 472 | optional = false 473 | python-versions = ">=3.6" 474 | 475 | [package.dependencies] 476 | pytz-deprecation-shim = "*" 477 | tzdata = {version = "*", markers = "platform_system == \"Windows\""} 478 | 479 | [package.extras] 480 | devenv = ["black", "pyroma", "pytest-cov", "zest.releaser"] 481 | test = ["pytest (>=4.3)", "pytest-mock (>=3.3)"] 482 | 483 | [package.source] 484 | type = "legacy" 485 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 486 | reference = "aliyun" 487 | 488 | [[package]] 489 | name = "uvicorn" 490 | version = "0.19.0" 491 | description = "The lightning-fast ASGI server." 492 | category = "main" 493 | optional = false 494 | python-versions = ">=3.7" 495 | 496 | [package.dependencies] 497 | click = ">=7.0" 498 | colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} 499 | h11 = ">=0.8" 500 | httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} 501 | python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} 502 | pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} 503 | uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} 504 | watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} 505 | websockets = {version = ">=10.0", optional = true, markers = "extra == \"standard\""} 506 | 507 | [package.extras] 508 | standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.0)"] 509 | 510 | [package.source] 511 | type = "legacy" 512 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 513 | reference = "aliyun" 514 | 515 | [[package]] 516 | name = "uvloop" 517 | version = "0.17.0" 518 | description = "Fast implementation of asyncio event loop on top of libuv" 519 | category = "main" 520 | optional = false 521 | python-versions = ">=3.7" 522 | 523 | [package.extras] 524 | dev = ["Cython (>=0.29.32,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx_rtd_theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] 525 | docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx_rtd_theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] 526 | test = ["Cython (>=0.29.32,<0.30.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"] 527 | 528 | [package.source] 529 | type = "legacy" 530 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 531 | reference = "aliyun" 532 | 533 | [[package]] 534 | name = "watchfiles" 535 | version = "0.18.0" 536 | description = "Simple, modern and high performance file watching and code reload in python." 537 | category = "main" 538 | optional = false 539 | python-versions = ">=3.7" 540 | 541 | [package.dependencies] 542 | anyio = ">=3.0.0,<4" 543 | 544 | [package.source] 545 | type = "legacy" 546 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 547 | reference = "aliyun" 548 | 549 | [[package]] 550 | name = "websockets" 551 | version = "10.4" 552 | description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" 553 | category = "main" 554 | optional = false 555 | python-versions = ">=3.7" 556 | 557 | [package.source] 558 | type = "legacy" 559 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 560 | reference = "aliyun" 561 | 562 | [metadata] 563 | lock-version = "1.1" 564 | python-versions = "^3.9" 565 | content-hash = "5e1ac9bd61b8927fbb2fc8ea1d56c812fccb740a5b34a5f5fcc1f83cdb45d19c" 566 | 567 | [metadata.files] 568 | anyio = [ 569 | {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, 570 | {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, 571 | ] 572 | apscheduler = [ 573 | {file = "APScheduler-3.9.1-py2.py3-none-any.whl", hash = "sha256:ddc25a0ddd899de44d7f451f4375fb971887e65af51e41e5dcf681f59b8b2c9a"}, 574 | {file = "APScheduler-3.9.1.tar.gz", hash = "sha256:65e6574b6395498d371d045f2a8a7e4f7d50c6ad21ef7313d15b1c7cf20df1e3"}, 575 | ] 576 | certifi = [ 577 | {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, 578 | {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, 579 | ] 580 | click = [ 581 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, 582 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, 583 | ] 584 | clock-timer = [ 585 | {file = "clock_timer-0.1.6-py3-none-any.whl", hash = "sha256:7573f7593961383d275ca659acfd854c62e805bf0111c000f6ce8dc168b94046"}, 586 | {file = "clock_timer-0.1.6.tar.gz", hash = "sha256:de6cde8985a155fba1813ea8b418a0ce6988826cf819606267fe0d03a7916d4b"}, 587 | ] 588 | colorama = [ 589 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 590 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 591 | ] 592 | fastapi = [ 593 | {file = "fastapi-0.85.1-py3-none-any.whl", hash = "sha256:de3166b6b1163dc22da4dc4ebdc3192fcbac7700dd1870a1afa44de636a636b5"}, 594 | {file = "fastapi-0.85.1.tar.gz", hash = "sha256:1facd097189682a4ff11cbd01334a992e51b56be663b2bd50c2c09523624f144"}, 595 | ] 596 | h11 = [ 597 | {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, 598 | {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"}, 599 | ] 600 | httpcore = [ 601 | {file = "httpcore-0.15.0-py3-none-any.whl", hash = "sha256:1105b8b73c025f23ff7c36468e4432226cbb959176eab66864b8e31c4ee27fa6"}, 602 | {file = "httpcore-0.15.0.tar.gz", hash = "sha256:18b68ab86a3ccf3e7dc0f43598eaddcf472b602aba29f9aa6ab85fe2ada3980b"}, 603 | ] 604 | httptools = [ 605 | {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8f470c79061599a126d74385623ff4744c4e0f4a0997a353a44923c0b561ee51"}, 606 | {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e90491a4d77d0cb82e0e7a9cb35d86284c677402e4ce7ba6b448ccc7325c5421"}, 607 | {file = "httptools-0.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1d2357f791b12d86faced7b5736dea9ef4f5ecdc6c3f253e445ee82da579449"}, 608 | {file = "httptools-0.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f90cd6fd97c9a1b7fe9215e60c3bd97336742a0857f00a4cb31547bc22560c2"}, 609 | {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5230a99e724a1bdbbf236a1b58d6e8504b912b0552721c7c6b8570925ee0ccde"}, 610 | {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3a47a34f6015dd52c9eb629c0f5a8a5193e47bf2a12d9a3194d231eaf1bc451a"}, 611 | {file = "httptools-0.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:24bb4bb8ac3882f90aa95403a1cb48465de877e2d5298ad6ddcfdebec060787d"}, 612 | {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e67d4f8734f8054d2c4858570cc4b233bf753f56e85217de4dfb2495904cf02e"}, 613 | {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e5eefc58d20e4c2da82c78d91b2906f1a947ef42bd668db05f4ab4201a99f49"}, 614 | {file = "httptools-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0297822cea9f90a38df29f48e40b42ac3d48a28637368f3ec6d15eebefd182f9"}, 615 | {file = "httptools-0.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:557be7fbf2bfa4a2ec65192c254e151684545ebab45eca5d50477d562c40f986"}, 616 | {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:54465401dbbec9a6a42cf737627fb0f014d50dc7365a6b6cd57753f151a86ff0"}, 617 | {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4d9ebac23d2de960726ce45f49d70eb5466725c0087a078866043dad115f850f"}, 618 | {file = "httptools-0.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:e8a34e4c0ab7b1ca17b8763613783e2458e77938092c18ac919420ab8655c8c1"}, 619 | {file = "httptools-0.5.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f659d7a48401158c59933904040085c200b4be631cb5f23a7d561fbae593ec1f"}, 620 | {file = "httptools-0.5.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1616b3ba965cd68e6f759eeb5d34fbf596a79e84215eeceebf34ba3f61fdc7"}, 621 | {file = "httptools-0.5.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3625a55886257755cb15194efbf209584754e31d336e09e2ffe0685a76cb4b60"}, 622 | {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:72ad589ba5e4a87e1d404cc1cb1b5780bfcb16e2aec957b88ce15fe879cc08ca"}, 623 | {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:850fec36c48df5a790aa735417dca8ce7d4b48d59b3ebd6f83e88a8125cde324"}, 624 | {file = "httptools-0.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f222e1e9d3f13b68ff8a835574eda02e67277d51631d69d7cf7f8e07df678c86"}, 625 | {file = "httptools-0.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3cb8acf8f951363b617a8420768a9f249099b92e703c052f9a51b66342eea89b"}, 626 | {file = "httptools-0.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550059885dc9c19a072ca6d6735739d879be3b5959ec218ba3e013fd2255a11b"}, 627 | {file = "httptools-0.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a04fe458a4597aa559b79c7f48fe3dceabef0f69f562daf5c5e926b153817281"}, 628 | {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d0c1044bce274ec6711f0770fd2d5544fe392591d204c68328e60a46f88843b"}, 629 | {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c6eeefd4435055a8ebb6c5cc36111b8591c192c56a95b45fe2af22d9881eee25"}, 630 | {file = "httptools-0.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5b65be160adcd9de7a7e6413a4966665756e263f0d5ddeffde277ffeee0576a5"}, 631 | {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fe9c766a0c35b7e3d6b6939393c8dfdd5da3ac5dec7f971ec9134f284c6c36d6"}, 632 | {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85b392aba273566c3d5596a0a490978c085b79700814fb22bfd537d381dd230c"}, 633 | {file = "httptools-0.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5e3088f4ed33947e16fd865b8200f9cfae1144f41b64a8cf19b599508e096bc"}, 634 | {file = "httptools-0.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c2a56b6aad7cc8f5551d8e04ff5a319d203f9d870398b94702300de50190f63"}, 635 | {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b571b281a19762adb3f48a7731f6842f920fa71108aff9be49888320ac3e24d"}, 636 | {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa47ffcf70ba6f7848349b8a6f9b481ee0f7637931d91a9860a1838bfc586901"}, 637 | {file = "httptools-0.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:bede7ee075e54b9a5bde695b4fc8f569f30185891796b2e4e09e2226801d09bd"}, 638 | {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:64eba6f168803a7469866a9c9b5263a7463fa8b7a25b35e547492aa7322036b6"}, 639 | {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4b098e4bb1174096a93f48f6193e7d9aa7071506a5877da09a783509ca5fff42"}, 640 | {file = "httptools-0.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9423a2de923820c7e82e18980b937893f4aa8251c43684fa1772e341f6e06887"}, 641 | {file = "httptools-0.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca1b7becf7d9d3ccdbb2f038f665c0f4857e08e1d8481cbcc1a86a0afcfb62b2"}, 642 | {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:50d4613025f15f4b11f1c54bbed4761c0020f7f921b95143ad6d58c151198142"}, 643 | {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8ffce9d81c825ac1deaa13bc9694c0562e2840a48ba21cfc9f3b4c922c16f372"}, 644 | {file = "httptools-0.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:1af91b3650ce518d226466f30bbba5b6376dbd3ddb1b2be8b0658c6799dd450b"}, 645 | {file = "httptools-0.5.0.tar.gz", hash = "sha256:295874861c173f9101960bba332429bb77ed4dcd8cdf5cee9922eb00e4f6bc09"}, 646 | ] 647 | httpx = [ 648 | {file = "httpx-0.23.0-py3-none-any.whl", hash = "sha256:42974f577483e1e932c3cdc3cd2303e883cbfba17fe228b0f63589764d7b9c4b"}, 649 | {file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"}, 650 | ] 651 | idna = [ 652 | {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, 653 | {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, 654 | ] 655 | numpy = [ 656 | {file = "numpy-1.23.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:95d79ada05005f6f4f337d3bb9de8a7774f259341c70bc88047a1f7b96a4bcb2"}, 657 | {file = "numpy-1.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:926db372bc4ac1edf81cfb6c59e2a881606b409ddc0d0920b988174b2e2a767f"}, 658 | {file = "numpy-1.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c237129f0e732885c9a6076a537e974160482eab8f10db6292e92154d4c67d71"}, 659 | {file = "numpy-1.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8365b942f9c1a7d0f0dc974747d99dd0a0cdfc5949a33119caf05cb314682d3"}, 660 | {file = "numpy-1.23.4-cp310-cp310-win32.whl", hash = "sha256:2341f4ab6dba0834b685cce16dad5f9b6606ea8a00e6da154f5dbded70fdc4dd"}, 661 | {file = "numpy-1.23.4-cp310-cp310-win_amd64.whl", hash = "sha256:d331afac87c92373826af83d2b2b435f57b17a5c74e6268b79355b970626e329"}, 662 | {file = "numpy-1.23.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:488a66cb667359534bc70028d653ba1cf307bae88eab5929cd707c761ff037db"}, 663 | {file = "numpy-1.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce03305dd694c4873b9429274fd41fc7eb4e0e4dea07e0af97a933b079a5814f"}, 664 | {file = "numpy-1.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8981d9b5619569899666170c7c9748920f4a5005bf79c72c07d08c8a035757b0"}, 665 | {file = "numpy-1.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a70a7d3ce4c0e9284e92285cba91a4a3f5214d87ee0e95928f3614a256a1488"}, 666 | {file = "numpy-1.23.4-cp311-cp311-win32.whl", hash = "sha256:5e13030f8793e9ee42f9c7d5777465a560eb78fa7e11b1c053427f2ccab90c79"}, 667 | {file = "numpy-1.23.4-cp311-cp311-win_amd64.whl", hash = "sha256:7607b598217745cc40f751da38ffd03512d33ec06f3523fb0b5f82e09f6f676d"}, 668 | {file = "numpy-1.23.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7ab46e4e7ec63c8a5e6dbf5c1b9e1c92ba23a7ebecc86c336cb7bf3bd2fb10e5"}, 669 | {file = "numpy-1.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8aae2fb3180940011b4862b2dd3756616841c53db9734b27bb93813cd79fce6"}, 670 | {file = "numpy-1.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c053d7557a8f022ec823196d242464b6955a7e7e5015b719e76003f63f82d0f"}, 671 | {file = "numpy-1.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0882323e0ca4245eb0a3d0a74f88ce581cc33aedcfa396e415e5bba7bf05f68"}, 672 | {file = "numpy-1.23.4-cp38-cp38-win32.whl", hash = "sha256:dada341ebb79619fe00a291185bba370c9803b1e1d7051610e01ed809ef3a4ba"}, 673 | {file = "numpy-1.23.4-cp38-cp38-win_amd64.whl", hash = "sha256:0fe563fc8ed9dc4474cbf70742673fc4391d70f4363f917599a7fa99f042d5a8"}, 674 | {file = "numpy-1.23.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c67b833dbccefe97cdd3f52798d430b9d3430396af7cdb2a0c32954c3ef73894"}, 675 | {file = "numpy-1.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f76025acc8e2114bb664294a07ede0727aa75d63a06d2fae96bf29a81747e4a7"}, 676 | {file = "numpy-1.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12ac457b63ec8ded85d85c1e17d85efd3c2b0967ca39560b307a35a6703a4735"}, 677 | {file = "numpy-1.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95de7dc7dc47a312f6feddd3da2500826defdccbc41608d0031276a24181a2c0"}, 678 | {file = "numpy-1.23.4-cp39-cp39-win32.whl", hash = "sha256:f2f390aa4da44454db40a1f0201401f9036e8d578a25f01a6e237cea238337ef"}, 679 | {file = "numpy-1.23.4-cp39-cp39-win_amd64.whl", hash = "sha256:f260da502d7441a45695199b4e7fd8ca87db659ba1c78f2bbf31f934fe76ae0e"}, 680 | {file = "numpy-1.23.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61be02e3bf810b60ab74e81d6d0d36246dbfb644a462458bb53b595791251911"}, 681 | {file = "numpy-1.23.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:296d17aed51161dbad3c67ed6d164e51fcd18dbcd5dd4f9d0a9c6055dce30810"}, 682 | {file = "numpy-1.23.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4d52914c88b4930dafb6c48ba5115a96cbab40f45740239d9f4159c4ba779962"}, 683 | {file = "numpy-1.23.4.tar.gz", hash = "sha256:ed2cc92af0efad20198638c69bb0fc2870a58dabfba6eb722c933b48556c686c"}, 684 | ] 685 | pandas = [ 686 | {file = "pandas-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0d8d7433d19bfa33f11c92ad9997f15a902bda4f5ad3a4814a21d2e910894484"}, 687 | {file = "pandas-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cc47f2ebaa20ef96ae72ee082f9e101b3dfbf74f0e62c7a12c0b075a683f03c"}, 688 | {file = "pandas-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e8e5edf97d8793f51d258c07c629bd49d271d536ce15d66ac00ceda5c150eb3"}, 689 | {file = "pandas-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41aec9f87455306496d4486df07c1b98c15569c714be2dd552a6124cd9fda88f"}, 690 | {file = "pandas-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c76f1d104844c5360c21d2ef0e1a8b2ccf8b8ebb40788475e255b9462e32b2be"}, 691 | {file = "pandas-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:1642fc6138b4e45d57a12c1b464a01a6d868c0148996af23f72dde8d12486bbc"}, 692 | {file = "pandas-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:171cef540bfcec52257077816a4dbbac152acdb8236ba11d3196ae02bf0959d8"}, 693 | {file = "pandas-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a68a9b9754efff364b0c5ee5b0f18e15ca640c01afe605d12ba8b239ca304d6b"}, 694 | {file = "pandas-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:86d87279ebc5bc20848b4ceb619073490037323f80f515e0ec891c80abad958a"}, 695 | {file = "pandas-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:207d63ac851e60ec57458814613ef4b3b6a5e9f0b33c57623ba2bf8126c311f8"}, 696 | {file = "pandas-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e252a9e49b233ff96e2815c67c29702ac3a062098d80a170c506dff3470fd060"}, 697 | {file = "pandas-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:de34636e2dc04e8ac2136a8d3c2051fd56ebe9fd6cd185581259330649e73ca9"}, 698 | {file = "pandas-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:1d34b1f43d9e3f4aea056ba251f6e9b143055ebe101ed04c847b41bb0bb4a989"}, 699 | {file = "pandas-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b82ccc7b093e0a93f8dffd97a542646a3e026817140e2c01266aaef5fdde11b"}, 700 | {file = "pandas-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e30a31039574d96f3d683df34ccb50bb435426ad65793e42a613786901f6761"}, 701 | {file = "pandas-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62e61003411382e20d7c2aec1ee8d7c86c8b9cf46290993dd8a0a3be44daeb38"}, 702 | {file = "pandas-1.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc987f7717e53d372f586323fff441263204128a1ead053c1b98d7288f836ac9"}, 703 | {file = "pandas-1.5.0-cp38-cp38-win32.whl", hash = "sha256:e178ce2d7e3b934cf8d01dc2d48d04d67cb0abfaffdcc8aa6271fd5a436f39c8"}, 704 | {file = "pandas-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:33a9d9e21ab2d91e2ab6e83598419ea6a664efd4c639606b299aae8097c1c94f"}, 705 | {file = "pandas-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:73844e247a7b7dac2daa9df7339ecf1fcf1dfb8cbfd11e3ffe9819ae6c31c515"}, 706 | {file = "pandas-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e9c5049333c5bebf993033f4bf807d163e30e8fada06e1da7fa9db86e2392009"}, 707 | {file = "pandas-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:85a516a7f6723ca1528f03f7851fa8d0360d1d6121cf15128b290cf79b8a7f6a"}, 708 | {file = "pandas-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:947ed9f896ee61adbe61829a7ae1ade493c5a28c66366ec1de85c0642009faac"}, 709 | {file = "pandas-1.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7f38d91f21937fe2bec9449570d7bf36ad7136227ef43b321194ec249e2149d"}, 710 | {file = "pandas-1.5.0-cp39-cp39-win32.whl", hash = "sha256:2504c032f221ef9e4a289f5e46a42b76f5e087ecb67d62e342ccbba95a32a488"}, 711 | {file = "pandas-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:8a4fc04838615bf0a8d3a03ed68197f358054f0df61f390bcc64fbe39e3d71ec"}, 712 | {file = "pandas-1.5.0.tar.gz", hash = "sha256:3ee61b881d2f64dd90c356eb4a4a4de75376586cd3c9341c6c0fcaae18d52977"}, 713 | ] 714 | pydantic = [ 715 | {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"}, 716 | {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"}, 717 | {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"}, 718 | {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"}, 719 | {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"}, 720 | {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"}, 721 | {file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"}, 722 | {file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"}, 723 | {file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"}, 724 | {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"}, 725 | {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"}, 726 | {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"}, 727 | {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"}, 728 | {file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"}, 729 | {file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"}, 730 | {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"}, 731 | {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"}, 732 | {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"}, 733 | {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"}, 734 | {file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"}, 735 | {file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"}, 736 | {file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"}, 737 | {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"}, 738 | {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"}, 739 | {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"}, 740 | {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"}, 741 | {file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"}, 742 | {file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"}, 743 | {file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"}, 744 | {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"}, 745 | {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"}, 746 | {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"}, 747 | {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"}, 748 | {file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"}, 749 | {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"}, 750 | {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"}, 751 | ] 752 | python-dateutil = [ 753 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, 754 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, 755 | ] 756 | python-dotenv = [ 757 | {file = "python-dotenv-0.21.0.tar.gz", hash = "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"}, 758 | {file = "python_dotenv-0.21.0-py3-none-any.whl", hash = "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5"}, 759 | ] 760 | pytz = [ 761 | {file = "pytz-2022.4-py2.py3-none-any.whl", hash = "sha256:2c0784747071402c6e99f0bafdb7da0fa22645f06554c7ae06bf6358897e9c91"}, 762 | {file = "pytz-2022.4.tar.gz", hash = "sha256:48ce799d83b6f8aab2020e369b627446696619e79645419610b9facd909b3174"}, 763 | ] 764 | pytz-deprecation-shim = [ 765 | {file = "pytz_deprecation_shim-0.1.0.post0-py2.py3-none-any.whl", hash = "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6"}, 766 | {file = "pytz_deprecation_shim-0.1.0.post0.tar.gz", hash = "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"}, 767 | ] 768 | pyyaml = [ 769 | {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, 770 | {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, 771 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, 772 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, 773 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, 774 | {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, 775 | {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, 776 | {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, 777 | {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, 778 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, 779 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, 780 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, 781 | {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, 782 | {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, 783 | {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, 784 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, 785 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, 786 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, 787 | {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, 788 | {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, 789 | {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, 790 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, 791 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, 792 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, 793 | {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, 794 | {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, 795 | {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, 796 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, 797 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, 798 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, 799 | {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, 800 | {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, 801 | {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, 802 | {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, 803 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, 804 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, 805 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, 806 | {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, 807 | {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, 808 | {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, 809 | ] 810 | rfc3986 = [ 811 | {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, 812 | {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, 813 | ] 814 | setuptools = [ 815 | {file = "setuptools-65.4.1-py3-none-any.whl", hash = "sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012"}, 816 | {file = "setuptools-65.4.1.tar.gz", hash = "sha256:3050e338e5871e70c72983072fe34f6032ae1cdeeeb67338199c2f74e083a80e"}, 817 | ] 818 | six = [ 819 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 820 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 821 | ] 822 | sniffio = [ 823 | {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, 824 | {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, 825 | ] 826 | starlette = [ 827 | {file = "starlette-0.20.4-py3-none-any.whl", hash = "sha256:c0414d5a56297d37f3db96a84034d61ce29889b9eaccf65eb98a0b39441fcaa3"}, 828 | {file = "starlette-0.20.4.tar.gz", hash = "sha256:42fcf3122f998fefce3e2c5ad7e5edbf0f02cf685d646a83a08d404726af5084"}, 829 | ] 830 | typing-extensions = [ 831 | {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, 832 | {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, 833 | ] 834 | tzdata = [ 835 | {file = "tzdata-2022.5-py2.py3-none-any.whl", hash = "sha256:323161b22b7802fdc78f20ca5f6073639c64f1a7227c40cd3e19fd1d0ce6650a"}, 836 | {file = "tzdata-2022.5.tar.gz", hash = "sha256:e15b2b3005e2546108af42a0eb4ccab4d9e225e2dfbf4f77aad50c70a4b1f3ab"}, 837 | ] 838 | tzlocal = [ 839 | {file = "tzlocal-4.2-py3-none-any.whl", hash = "sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745"}, 840 | {file = "tzlocal-4.2.tar.gz", hash = "sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7"}, 841 | ] 842 | uvicorn = [ 843 | {file = "uvicorn-0.19.0-py3-none-any.whl", hash = "sha256:cc277f7e73435748e69e075a721841f7c4a95dba06d12a72fe9874acced16f6f"}, 844 | {file = "uvicorn-0.19.0.tar.gz", hash = "sha256:cf538f3018536edb1f4a826311137ab4944ed741d52aeb98846f52215de57f25"}, 845 | ] 846 | uvloop = [ 847 | {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce9f61938d7155f79d3cb2ffa663147d4a76d16e08f65e2c66b77bd41b356718"}, 848 | {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:68532f4349fd3900b839f588972b3392ee56042e440dd5873dfbbcd2cc67617c"}, 849 | {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0949caf774b9fcefc7c5756bacbbbd3fc4c05a6b7eebc7c7ad6f825b23998d6d"}, 850 | {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3d00b70ce95adce264462c930fbaecb29718ba6563db354608f37e49e09024"}, 851 | {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a5abddb3558d3f0a78949c750644a67be31e47936042d4f6c888dd6f3c95f4aa"}, 852 | {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8efcadc5a0003d3a6e887ccc1fb44dec25594f117a94e3127954c05cf144d811"}, 853 | {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3378eb62c63bf336ae2070599e49089005771cc651c8769aaad72d1bd9385a7c"}, 854 | {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6aafa5a78b9e62493539456f8b646f85abc7093dd997f4976bb105537cf2635e"}, 855 | {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c686a47d57ca910a2572fddfe9912819880b8765e2f01dc0dd12a9bf8573e539"}, 856 | {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:864e1197139d651a76c81757db5eb199db8866e13acb0dfe96e6fc5d1cf45fc4"}, 857 | {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2a6149e1defac0faf505406259561bc14b034cdf1d4711a3ddcdfbaa8d825a05"}, 858 | {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6708f30db9117f115eadc4f125c2a10c1a50d711461699a0cbfaa45b9a78e376"}, 859 | {file = "uvloop-0.17.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:23609ca361a7fc587031429fa25ad2ed7242941adec948f9d10c045bfecab06b"}, 860 | {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2deae0b0fb00a6af41fe60a675cec079615b01d68beb4cc7b722424406b126a8"}, 861 | {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45cea33b208971e87a31c17622e4b440cac231766ec11e5d22c76fab3bf9df62"}, 862 | {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9b09e0f0ac29eee0451d71798878eae5a4e6a91aa275e114037b27f7db72702d"}, 863 | {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dbbaf9da2ee98ee2531e0c780455f2841e4675ff580ecf93fe5c48fe733b5667"}, 864 | {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a4aee22ece20958888eedbad20e4dbb03c37533e010fb824161b4f05e641f738"}, 865 | {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:307958f9fc5c8bb01fad752d1345168c0abc5d62c1b72a4a8c6c06f042b45b20"}, 866 | {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ebeeec6a6641d0adb2ea71dcfb76017602ee2bfd8213e3fcc18d8f699c5104f"}, 867 | {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1436c8673c1563422213ac6907789ecb2b070f5939b9cbff9ef7113f2b531595"}, 868 | {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8887d675a64cfc59f4ecd34382e5b4f0ef4ae1da37ed665adba0c2badf0d6578"}, 869 | {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3db8de10ed684995a7f34a001f15b374c230f7655ae840964d51496e2f8a8474"}, 870 | {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7d37dccc7ae63e61f7b96ee2e19c40f153ba6ce730d8ba4d3b4e9738c1dccc1b"}, 871 | {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cbbe908fda687e39afd6ea2a2f14c2c3e43f2ca88e3a11964b297822358d0e6c"}, 872 | {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d97672dc709fa4447ab83276f344a165075fd9f366a97b712bdd3fee05efae8"}, 873 | {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1e507c9ee39c61bfddd79714e4f85900656db1aec4d40c6de55648e85c2799c"}, 874 | {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c092a2c1e736086d59ac8e41f9c98f26bbf9b9222a76f21af9dfe949b99b2eb9"}, 875 | {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:30babd84706115626ea78ea5dbc7dd8d0d01a2e9f9b306d24ca4ed5796c66ded"}, 876 | {file = "uvloop-0.17.0.tar.gz", hash = "sha256:0ddf6baf9cf11a1a22c71487f39f15b2cf78eb5bde7e5b45fbb99e8a9d91b9e1"}, 877 | ] 878 | watchfiles = [ 879 | {file = "watchfiles-0.18.0-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:76a4c4a8e25a2c9a4f7fa3d373bbaf5558c17b97b4cf8411d33de368fe6b68a9"}, 880 | {file = "watchfiles-0.18.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:d5d799614d4c56d29c5ba56f4f619f967210dc10a0d6965b62d326b9e2f72c9e"}, 881 | {file = "watchfiles-0.18.0-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:39b932b044fef6c43e813e0bef908e0edf185bf7b5d8d53246651cb7ac9efe79"}, 882 | {file = "watchfiles-0.18.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1686bc4ac40ffde7256b6543b0f9a2cc8b531ae45243786f1d3f1dda2fe39e24"}, 883 | {file = "watchfiles-0.18.0-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:320bcde0adaa972403ed3b70f784409437325a1a4df2de54ba0672203d8847e5"}, 884 | {file = "watchfiles-0.18.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ba6d8c2f957cae3e888bc250bc60ed09fe869b3f55f09d020ed3fecbefb6a4c"}, 885 | {file = "watchfiles-0.18.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fd4215badad1e3d1ad5fb79f21432dd5157e2e7b0765d27a19dc2a28580c6979"}, 886 | {file = "watchfiles-0.18.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cfdbfc4b6797c28dd1a8524581fed00ca333971b4111af8cd42fb7a92dcdc227"}, 887 | {file = "watchfiles-0.18.0-cp37-abi3-win32.whl", hash = "sha256:8eddc2d19bf6f49aee224072ec0f4f3258125a49f11b5dcff1448e68718a745e"}, 888 | {file = "watchfiles-0.18.0-cp37-abi3-win_amd64.whl", hash = "sha256:be87c9b1fe2b02105a9ac6d9df7500a110652bbd97cf46b13964eeaef9a6c89c"}, 889 | {file = "watchfiles-0.18.0-cp37-abi3-win_arm64.whl", hash = "sha256:184799818c4fa7dbc6a1e4ca20bcbc6b85e4e0db07ce4554ea2f29b75ccd0cdc"}, 890 | {file = "watchfiles-0.18.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:7f39fcdac5d5b9815a0c2ab9005d39854296b11fa15386a9a69c09cbbc5dde2c"}, 891 | {file = "watchfiles-0.18.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78b1e7c29b92dfc8fc32f15949019232b493767d236c2bff31848df13fdb9e8a"}, 892 | {file = "watchfiles-0.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27d64a6ed5e0aebef97c70fa3899a6958d4f7f049effc659e7dc3e81f3170a7b"}, 893 | {file = "watchfiles-0.18.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4bbc8bfa0f3871b1867af42837a5635a9c1cbb2b68d039754b4750642c34aaee"}, 894 | {file = "watchfiles-0.18.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d3a12e4de5446fb6e286b720d0cb3a080811caf0ef43e556c2db5fe10ef0342"}, 895 | {file = "watchfiles-0.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e611a90482ac14ef3ec234c1604ed921d1b0c68970eba82f1cf0d59a3e4eb76"}, 896 | {file = "watchfiles-0.18.0.tar.gz", hash = "sha256:bbe10d134eef1666451382015e48f092c941a6d4562a98ffa1a288f79a897c46"}, 897 | ] 898 | websockets = [ 899 | {file = "websockets-10.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d58804e996d7d2307173d56c297cf7bc132c52df27a3efaac5e8d43e36c21c48"}, 900 | {file = "websockets-10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc0b82d728fe21a0d03e65f81980abbbcb13b5387f733a1a870672c5be26edab"}, 901 | {file = "websockets-10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ba089c499e1f4155d2a3c2a05d2878a3428cf321c848f2b5a45ce55f0d7d310c"}, 902 | {file = "websockets-10.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33d69ca7612f0ddff3316b0c7b33ca180d464ecac2d115805c044bf0a3b0d032"}, 903 | {file = "websockets-10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62e627f6b6d4aed919a2052efc408da7a545c606268d5ab5bfab4432734b82b4"}, 904 | {file = "websockets-10.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ea7b82bfcae927eeffc55d2ffa31665dc7fec7b8dc654506b8e5a518eb4d50"}, 905 | {file = "websockets-10.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e0cb5cc6ece6ffa75baccfd5c02cffe776f3f5c8bf486811f9d3ea3453676ce8"}, 906 | {file = "websockets-10.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae5e95cfb53ab1da62185e23b3130e11d64431179debac6dc3c6acf08760e9b1"}, 907 | {file = "websockets-10.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7c584f366f46ba667cfa66020344886cf47088e79c9b9d39c84ce9ea98aaa331"}, 908 | {file = "websockets-10.4-cp310-cp310-win32.whl", hash = "sha256:b029fb2032ae4724d8ae8d4f6b363f2cc39e4c7b12454df8df7f0f563ed3e61a"}, 909 | {file = "websockets-10.4-cp310-cp310-win_amd64.whl", hash = "sha256:8dc96f64ae43dde92530775e9cb169979f414dcf5cff670455d81a6823b42089"}, 910 | {file = "websockets-10.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47a2964021f2110116cc1125b3e6d87ab5ad16dea161949e7244ec583b905bb4"}, 911 | {file = "websockets-10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e789376b52c295c4946403bd0efecf27ab98f05319df4583d3c48e43c7342c2f"}, 912 | {file = "websockets-10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d3f0b61c45c3fa9a349cf484962c559a8a1d80dae6977276df8fd1fa5e3cb8c"}, 913 | {file = "websockets-10.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f55b5905705725af31ccef50e55391621532cd64fbf0bc6f4bac935f0fccec46"}, 914 | {file = "websockets-10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00c870522cdb69cd625b93f002961ffb0c095394f06ba8c48f17eef7c1541f96"}, 915 | {file = "websockets-10.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f38706e0b15d3c20ef6259fd4bc1700cd133b06c3c1bb108ffe3f8947be15fa"}, 916 | {file = "websockets-10.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f2c38d588887a609191d30e902df2a32711f708abfd85d318ca9b367258cfd0c"}, 917 | {file = "websockets-10.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fe10ddc59b304cb19a1bdf5bd0a7719cbbc9fbdd57ac80ed436b709fcf889106"}, 918 | {file = "websockets-10.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:90fcf8929836d4a0e964d799a58823547df5a5e9afa83081761630553be731f9"}, 919 | {file = "websockets-10.4-cp311-cp311-win32.whl", hash = "sha256:b9968694c5f467bf67ef97ae7ad4d56d14be2751000c1207d31bf3bb8860bae8"}, 920 | {file = "websockets-10.4-cp311-cp311-win_amd64.whl", hash = "sha256:a7a240d7a74bf8d5cb3bfe6be7f21697a28ec4b1a437607bae08ac7acf5b4882"}, 921 | {file = "websockets-10.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:74de2b894b47f1d21cbd0b37a5e2b2392ad95d17ae983e64727e18eb281fe7cb"}, 922 | {file = "websockets-10.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3a686ecb4aa0d64ae60c9c9f1a7d5d46cab9bfb5d91a2d303d00e2cd4c4c5cc"}, 923 | {file = "websockets-10.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d15c968ea7a65211e084f523151dbf8ae44634de03c801b8bd070b74e85033"}, 924 | {file = "websockets-10.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00213676a2e46b6ebf6045bc11d0f529d9120baa6f58d122b4021ad92adabd41"}, 925 | {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e23173580d740bf8822fd0379e4bf30aa1d5a92a4f252d34e893070c081050df"}, 926 | {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:dd500e0a5e11969cdd3320935ca2ff1e936f2358f9c2e61f100a1660933320ea"}, 927 | {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4239b6027e3d66a89446908ff3027d2737afc1a375f8fd3eea630a4842ec9a0c"}, 928 | {file = "websockets-10.4-cp37-cp37m-win32.whl", hash = "sha256:8a5cc00546e0a701da4639aa0bbcb0ae2bb678c87f46da01ac2d789e1f2d2038"}, 929 | {file = "websockets-10.4-cp37-cp37m-win_amd64.whl", hash = "sha256:a9f9a735deaf9a0cadc2d8c50d1a5bcdbae8b6e539c6e08237bc4082d7c13f28"}, 930 | {file = "websockets-10.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c1289596042fad2cdceb05e1ebf7aadf9995c928e0da2b7a4e99494953b1b94"}, 931 | {file = "websockets-10.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0cff816f51fb33c26d6e2b16b5c7d48eaa31dae5488ace6aae468b361f422b63"}, 932 | {file = "websockets-10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dd9becd5fe29773d140d68d607d66a38f60e31b86df75332703757ee645b6faf"}, 933 | {file = "websockets-10.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45ec8e75b7dbc9539cbfafa570742fe4f676eb8b0d3694b67dabe2f2ceed8aa6"}, 934 | {file = "websockets-10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f72e5cd0f18f262f5da20efa9e241699e0cf3a766317a17392550c9ad7b37d8"}, 935 | {file = "websockets-10.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185929b4808b36a79c65b7865783b87b6841e852ef5407a2fb0c03381092fa3b"}, 936 | {file = "websockets-10.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7d27a7e34c313b3a7f91adcd05134315002aaf8540d7b4f90336beafaea6217c"}, 937 | {file = "websockets-10.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:884be66c76a444c59f801ac13f40c76f176f1bfa815ef5b8ed44321e74f1600b"}, 938 | {file = "websockets-10.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:931c039af54fc195fe6ad536fde4b0de04da9d5916e78e55405436348cfb0e56"}, 939 | {file = "websockets-10.4-cp38-cp38-win32.whl", hash = "sha256:db3c336f9eda2532ec0fd8ea49fef7a8df8f6c804cdf4f39e5c5c0d4a4ad9a7a"}, 940 | {file = "websockets-10.4-cp38-cp38-win_amd64.whl", hash = "sha256:48c08473563323f9c9debac781ecf66f94ad5a3680a38fe84dee5388cf5acaf6"}, 941 | {file = "websockets-10.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:40e826de3085721dabc7cf9bfd41682dadc02286d8cf149b3ad05bff89311e4f"}, 942 | {file = "websockets-10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:56029457f219ade1f2fc12a6504ea61e14ee227a815531f9738e41203a429112"}, 943 | {file = "websockets-10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5fc088b7a32f244c519a048c170f14cf2251b849ef0e20cbbb0fdf0fdaf556f"}, 944 | {file = "websockets-10.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc8709c00704194213d45e455adc106ff9e87658297f72d544220e32029cd3d"}, 945 | {file = "websockets-10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0154f7691e4fe6c2b2bc275b5701e8b158dae92a1ab229e2b940efe11905dff4"}, 946 | {file = "websockets-10.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c6d2264f485f0b53adf22697ac11e261ce84805c232ed5dbe6b1bcb84b00ff0"}, 947 | {file = "websockets-10.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9bc42e8402dc5e9905fb8b9649f57efcb2056693b7e88faa8fb029256ba9c68c"}, 948 | {file = "websockets-10.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:edc344de4dac1d89300a053ac973299e82d3db56330f3494905643bb68801269"}, 949 | {file = "websockets-10.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:84bc2a7d075f32f6ed98652db3a680a17a4edb21ca7f80fe42e38753a58ee02b"}, 950 | {file = "websockets-10.4-cp39-cp39-win32.whl", hash = "sha256:c94ae4faf2d09f7c81847c63843f84fe47bf6253c9d60b20f25edfd30fb12588"}, 951 | {file = "websockets-10.4-cp39-cp39-win_amd64.whl", hash = "sha256:bbccd847aa0c3a69b5f691a84d2341a4f8a629c6922558f2a70611305f902d74"}, 952 | {file = "websockets-10.4-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:82ff5e1cae4e855147fd57a2863376ed7454134c2bf49ec604dfe71e446e2193"}, 953 | {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d210abe51b5da0ffdbf7b43eed0cfdff8a55a1ab17abbec4301c9ff077dd0342"}, 954 | {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:942de28af58f352a6f588bc72490ae0f4ccd6dfc2bd3de5945b882a078e4e179"}, 955 | {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9b27d6c1c6cd53dc93614967e9ce00ae7f864a2d9f99fe5ed86706e1ecbf485"}, 956 | {file = "websockets-10.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:3d3cac3e32b2c8414f4f87c1b2ab686fa6284a980ba283617404377cd448f631"}, 957 | {file = "websockets-10.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:da39dd03d130162deb63da51f6e66ed73032ae62e74aaccc4236e30edccddbb0"}, 958 | {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389f8dbb5c489e305fb113ca1b6bdcdaa130923f77485db5b189de343a179393"}, 959 | {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09a1814bb15eff7069e51fed0826df0bc0702652b5cb8f87697d469d79c23576"}, 960 | {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff64a1d38d156d429404aaa84b27305e957fd10c30e5880d1765c9480bea490f"}, 961 | {file = "websockets-10.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b343f521b047493dc4022dd338fc6db9d9282658862756b4f6fd0e996c1380e1"}, 962 | {file = "websockets-10.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:932af322458da7e4e35df32f050389e13d3d96b09d274b22a7aa1808f292fee4"}, 963 | {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a4162139374a49eb18ef5b2f4da1dd95c994588f5033d64e0bbfda4b6b6fcf"}, 964 | {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c57e4c1349fbe0e446c9fa7b19ed2f8a4417233b6984277cce392819123142d3"}, 965 | {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b627c266f295de9dea86bd1112ed3d5fafb69a348af30a2422e16590a8ecba13"}, 966 | {file = "websockets-10.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:05a7233089f8bd355e8cbe127c2e8ca0b4ea55467861906b80d2ebc7db4d6b72"}, 967 | {file = "websockets-10.4.tar.gz", hash = "sha256:eef610b23933c54d5d921c92578ae5f89813438fded840c2e9809d378dc765d3"}, 968 | ] 969 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "cfw" 3 | version = "1.0.1" 4 | description = "CFW (Cyber Firewall) is a human friendly Linux firewall." 5 | authors = ["Cyberbolt , Potato-OvO <1224141267@qq.com>"] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.9" 10 | pandas = "^1.5.0" 11 | pyyaml = "^6.0" 12 | apscheduler = "^3.9.1" 13 | fastapi = "^0.85.1" 14 | uvicorn = {extras = ["standard"], version = "^0.19.0"} 15 | httpx = "^0.23.0" 16 | click = "^8.1.3" 17 | clock-timer = "^0.1.6" 18 | 19 | [[tool.poetry.source]] 20 | name = "aliyun" 21 | url = "https://pypi.tuna.tsinghua.edu.cn/simple/" 22 | default = true 23 | 24 | [build-system] 25 | requires = ["poetry-core"] 26 | build-backend = "poetry.core.masonry.api" 27 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | anyio==3.6.2 2 | APScheduler==3.9.1 3 | certifi==2022.9.24 4 | click==8.1.3 5 | clock-timer==0.1.6 6 | fastapi==0.85.1 7 | h11==0.12.0 8 | httpcore==0.15.0 9 | httptools==0.5.0 10 | httpx==0.23.0 11 | idna==3.4 12 | numpy==1.23.4 13 | pandas==1.5.0 14 | pydantic==1.10.2 15 | python-dateutil==2.8.2 16 | python-dotenv==0.21.0 17 | pytz==2022.4 18 | pytz-deprecation-shim==0.1.0.post0 19 | PyYAML==6.0 20 | rfc3986==1.5.0 21 | six==1.16.0 22 | sniffio==1.3.0 23 | starlette==0.20.4 24 | typing_extensions==4.4.0 25 | tzdata==2022.5 26 | tzlocal==4.2 27 | uvicorn==0.19.0 28 | uvloop==0.17.0 29 | watchfiles==0.18.0 30 | websockets==10.4 31 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | import uvicorn 2 | from fastapi import FastAPI 3 | 4 | import cfw 5 | import cfw.ip_tools 6 | 7 | app = FastAPI() 8 | items = [] 9 | 10 | 11 | @app.on_event("startup") 12 | def startup_event(): 13 | rules, rules6 = cfw.ip_tools.start() 14 | items.append(rules) 15 | items.append(rules6) 16 | 17 | 18 | """ 19 | ipv4 block / unblock 20 | """ 21 | @app.get("/block_ip") 22 | def block_ip(ip: str, timeout: int): 23 | return cfw.block_ip(ip, timeout, "user") 24 | 25 | 26 | @app.get("/unblock_ip") 27 | def unblock_ip(ip: str): 28 | return cfw.unblock_ip(ip, "user") 29 | 30 | 31 | @app.get("/blacklist") 32 | def blacklist(): 33 | text = cfw.shell("ipset list blacklist") 34 | return { 35 | "code": 1, 36 | "message": text 37 | } 38 | 39 | 40 | """ 41 | ipv6 block / unblock 42 | """ 43 | @app.get("/block_ip6") 44 | def block_ip6(ip: str, timeout: int): 45 | return cfw.block_ip6(ip, timeout, "user") 46 | 47 | 48 | @app.get("/unblock_ip6") 49 | def unblock_ip6(ip: str): 50 | return cfw.unblock_ip6(ip, "user") 51 | 52 | 53 | @app.get("/blacklist6") 54 | def blacklist6(): 55 | text = cfw.shell("ipset list blacklist6") 56 | return { 57 | "code": 1, 58 | "message": text 59 | } 60 | 61 | 62 | """ 63 | ipv4 port 64 | """ 65 | @app.get("/allow_port") 66 | def allow_port(port: str, protocol: str): 67 | rules, rules6 = items 68 | if protocol == "all": 69 | r_tcp = rules.add_tcp_port(port) 70 | r_udp = rules.add_udp_port(port) 71 | if not r_tcp and not r_udp: 72 | return {"code": 0, "message": f"{port} port tcp/udp is already open."} 73 | elif protocol == "tcp": 74 | if not rules.add_tcp_port(port): 75 | return {"code": 0, "message": f"{port} port tcp is already open."} 76 | elif protocol == "udp": 77 | if not rules.add_udp_port(port): 78 | return {"code": 0, "message": f"{port} port udp is already open."} 79 | rules.save_rules() 80 | return {"code": 1} 81 | 82 | 83 | @app.get("/deny_port") 84 | def deny_port(port: str, protocol: str): 85 | rules, rules6 = items 86 | if protocol == "all": 87 | r_tcp = rules.rm_tcp_port(port) 88 | r_udp = rules.rm_udp_port(port) 89 | if not r_tcp and not r_udp: 90 | return {"code": 0, "message": f"{port} port tcp/udp has been closed."} 91 | elif protocol == "tcp": 92 | if not rules.rm_tcp_port(port): 93 | return {"code": 0, "message": f"{port} port tcp has been closed."} 94 | elif protocol == "udp": 95 | if not rules.rm_udp_port(port): 96 | return {"code": 0, "message": f"{port} port udp has been closed."} 97 | rules.save_rules() 98 | return {"code": 1} 99 | 100 | 101 | @app.get("/status") 102 | def status_port(): 103 | rules, rules6 = items 104 | ports = [] 105 | for rule in rules.data: 106 | if 'tcp' in rule and "0:65535" not in rule: 107 | port = (rule.split("--dport ")[1].split(" -j")[0], "tcp") 108 | ports.append(port) 109 | elif 'udp' in rule and "0:65535" not in rule: 110 | port = (rule.split("--dport ")[1].split(" -j")[0], "udp") 111 | ports.append(port) 112 | ports = set(ports) 113 | return {"code": 1, "message": ports} 114 | 115 | 116 | """ 117 | ipv6 port 118 | """ 119 | @app.get("/allow_port6") 120 | def allow_port6(port: str, protocol: str): 121 | rules, rules6 = items 122 | if protocol == "all": 123 | r_tcp = rules6.add_tcp_port(port) 124 | r_udp = rules6.add_udp_port(port) 125 | if not r_tcp and not r_udp: 126 | return {"code": 0, "message": f"{port} port tcp/udp is already open."} 127 | elif protocol == "tcp": 128 | if not rules6.add_tcp_port(port): 129 | return {"code": 0, "message": f"{port} port tcp is already open."} 130 | elif protocol == "udp": 131 | if not rules6.add_udp_port(port): 132 | return {"code": 0, "message": f"{port} port udp is already open."} 133 | rules6.save_rules() 134 | return {"code": 1} 135 | 136 | 137 | @app.get("/deny_port6") 138 | def deny_port6(port: str, protocol: str): 139 | rules, rules6 = items 140 | if protocol == "all": 141 | r_tcp = rules6.rm_tcp_port(port) 142 | r_udp = rules6.rm_udp_port(port) 143 | if not r_tcp and not r_udp: 144 | return {"code": 0, "message": f"{port} port tcp/udp has been closed."} 145 | elif protocol == "tcp": 146 | if not rules6.rm_tcp_port(port): 147 | return {"code": 0, "message": f"{port} port tcp has been closed."} 148 | elif protocol == "udp": 149 | if not rules6.rm_udp_port(port): 150 | return {"code": 0, "message": f"{port} port udp has been closed."} 151 | rules6.save_rules() 152 | return {"code": 1} 153 | 154 | 155 | @app.get("/status6") 156 | def status_port6(): 157 | rules, rules6 = items 158 | ports = [] 159 | for rule in rules6.data: 160 | if 'tcp' in rule and "0:65535" not in rule: 161 | port = (rule.split("--dport ")[1].split(" -j")[0], "tcp") 162 | ports.append(port) 163 | elif 'udp' in rule and "0:65535" not in rule: 164 | port = (rule.split("--dport ")[1].split(" -j")[0], "udp") 165 | ports.append(port) 166 | ports = set(ports) 167 | return {"code": 1, "message": ports} 168 | 169 | 170 | if __name__ == "__main__": 171 | uvicorn.run("server:app", port=cfw.config["port"]) 172 | -------------------------------------------------------------------------------- /uninstall.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import subprocess 3 | 4 | 5 | def cmd(cmd: str): 6 | try: 7 | subprocess.run( 8 | cmd, 9 | shell=True, 10 | check=True 11 | ) 12 | return True 13 | except subprocess.CalledProcessError as e: 14 | sys.exit(1) 15 | 16 | 17 | def shell(cmd: str) -> str: 18 | """ 19 | Execute a shell statement and return the output. 20 | """ 21 | r = subprocess.run( 22 | cmd, 23 | shell=True, 24 | check=True, 25 | stdout=subprocess.PIPE, 26 | stderr=subprocess.STDOUT 27 | ) 28 | return r.stdout.decode() 29 | 30 | 31 | def main(): 32 | # Remove cfw 33 | cmd("rm -rf /etc/cfw/") 34 | # Remove cfw for systemd 35 | cmd("systemctl stop cfw") 36 | cmd("rm /etc/systemd/system/cfw.service") 37 | cmd("systemctl daemon-reload") 38 | # Delete environment variable 39 | with open("/root/.bashrc", "r") as f: 40 | text = f.read() 41 | lines = text.splitlines() 42 | text_new = "" 43 | for line in lines: 44 | if "alias cfw='/etc/cfw/py39/bin/python /etc/cfw/client.py'" not in line: 45 | text_new += line + "\n" 46 | with open("/root/.bashrc", "w") as f: 47 | f.write(text_new) 48 | print("CFW uninstallation complete.") 49 | 50 | 51 | if __name__ == "__main__": 52 | main() 53 | -------------------------------------------------------------------------------- /update.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import subprocess 3 | 4 | 5 | def cmd(cmd: str): 6 | try: 7 | subprocess.run( 8 | cmd, 9 | shell=True, 10 | check=True 11 | ) 12 | return True 13 | except subprocess.CalledProcessError as e: 14 | sys.exit(1) 15 | 16 | 17 | def shell(cmd: str) -> str: 18 | """ 19 | Execute a shell statement and return the output. 20 | """ 21 | r = subprocess.run( 22 | cmd, 23 | shell=True, 24 | check=True, 25 | stdout=subprocess.PIPE, 26 | stderr=subprocess.STDOUT 27 | ) 28 | return r.stdout.decode() 29 | 30 | 31 | def main(): 32 | shell("git --git-dir=/etc/cfw/.git --work-tree=/etc/cfw pull https://github.com/Cyberbolt/cfw.git --quiet") 33 | cmd("systemctl start cfw") 34 | print("CFW has been updated.") 35 | 36 | 37 | if __name__ == "__main__": 38 | main() 39 | --------------------------------------------------------------------------------