├── .flake8 ├── .github ├── checkPATH.PNG ├── clone.png ├── config.png ├── logo.gif ├── path.PNG ├── pathIsValid.PNG ├── pipintsall.png ├── table_of_movies.png ├── usage.png ├── virtualenv.png └── workflows │ └── pylint.yml ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── docs └── windowsVenv.md ├── helper ├── __init__.py ├── user_angent.py └── utils.py ├── install.sh ├── main.py ├── requirements.txt ├── setup.py └── stream_cli ├── __init__.py ├── api ├── __init__.py ├── leet.py └── torrentgalaxy.py ├── interface.py ├── runner.py └── stream.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E203, E266, E501, W503, F403, F401 3 | max-line-length = 90 4 | max-complexity = 18 5 | select = B,C,E,F,W,T4,B9 6 | -------------------------------------------------------------------------------- /.github/checkPATH.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lkabuci/Botflix/08c86b35b93ef049432aee0840ae2fcd8ce2963c/.github/checkPATH.PNG -------------------------------------------------------------------------------- /.github/clone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lkabuci/Botflix/08c86b35b93ef049432aee0840ae2fcd8ce2963c/.github/clone.png -------------------------------------------------------------------------------- /.github/config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lkabuci/Botflix/08c86b35b93ef049432aee0840ae2fcd8ce2963c/.github/config.png -------------------------------------------------------------------------------- /.github/logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lkabuci/Botflix/08c86b35b93ef049432aee0840ae2fcd8ce2963c/.github/logo.gif -------------------------------------------------------------------------------- /.github/path.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lkabuci/Botflix/08c86b35b93ef049432aee0840ae2fcd8ce2963c/.github/path.PNG -------------------------------------------------------------------------------- /.github/pathIsValid.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lkabuci/Botflix/08c86b35b93ef049432aee0840ae2fcd8ce2963c/.github/pathIsValid.PNG -------------------------------------------------------------------------------- /.github/pipintsall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lkabuci/Botflix/08c86b35b93ef049432aee0840ae2fcd8ce2963c/.github/pipintsall.png -------------------------------------------------------------------------------- /.github/table_of_movies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lkabuci/Botflix/08c86b35b93ef049432aee0840ae2fcd8ce2963c/.github/table_of_movies.png -------------------------------------------------------------------------------- /.github/usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lkabuci/Botflix/08c86b35b93ef049432aee0840ae2fcd8ce2963c/.github/usage.png -------------------------------------------------------------------------------- /.github/virtualenv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lkabuci/Botflix/08c86b35b93ef049432aee0840ae2fcd8ce2963c/.github/virtualenv.png -------------------------------------------------------------------------------- /.github/workflows/pylint.yml: -------------------------------------------------------------------------------- 1 | name: Pylint 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: ["3.8", "3.9", "3.10"] 11 | steps: 12 | - uses: actions/checkout@v3 13 | - name: Set up Python ${{ matrix.python-version }} 14 | uses: actions/setup-python@v3 15 | with: 16 | python-version: ${{ matrix.python-version }} 17 | - name: Install dependencies 18 | run: | 19 | python -m pip install --upgrade pip 20 | pip install pylint 21 | - name: Analysing the code with pylint 22 | run: | 23 | pylint $(git ls-files '*.py') 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # player config 2 | player.txt 3 | botflix 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | .pytest_cache/ 8 | .scrapy 9 | .python-version 10 | 11 | # Environments 12 | .env*/ 13 | .env 14 | .venv 15 | env/ 16 | venv/ 17 | ENV/ 18 | env.bak/ 19 | venv.bak/ 20 | Pipfile 21 | Pipfile.lock 22 | config/ 23 | data/ 24 | 25 | # node 26 | node_modules/ 27 | *.json 28 | 29 | # Pycharm 30 | .idea/ 31 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: 22.3.0 4 | hooks: 5 | - id: black 6 | args: [--safe] 7 | 8 | - repo: https://github.com/pre-commit/pre-commit-hooks 9 | rev: v4.3.0 10 | hooks: 11 | - id: requirements-txt-fixer 12 | - id: trailing-whitespace 13 | 14 | - repo: https://github.com/PyCQA/flake8 15 | rev: 4.0.1 16 | hooks: 17 | - id: flake8 18 | 19 | # - repo: https://github.com/pre-commit/mirrors-mypy 20 | # rev: v0.961 21 | # hooks: 22 | # - id: mypy 23 | 24 | - repo: https://github.com/PyCQA/isort 25 | rev: 5.10.1 26 | hooks: 27 | - id: isort 28 | args: ["--profile", "black"] 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Redouane Elkaboussi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

⚠️ REPOSITORY ARCHIVED ⚠️

3 |

4 | This repository is no longer actively maintained.
5 | It has been archived for historical reference.
6 | Thank you to all contributors and users of this project.
7 | Last updated: Oct 21, 2023 8 |

9 |
10 |

11 |
12 | 13 | 14 |

15 | 16 | 17 |

18 | 19 |

Botflix

20 | 21 |
22 | 23 | 24 | ![AUR version](https://img.shields.io/aur/version/botflix-git) 25 | 26 | 27 |
28 | 29 | Botflix is a Python scrapping CLI that combines [scrapy](https://scrapy.org) and [webtorrent](https://github.com/webtorrent/webtorrent-cli) in one command for streaming movies from your terminal. 30 | 31 | ## Installation: 32 | #### For ArchLinux or Arch-based distro: 33 | You can install it manually from [AUR repository](https://aur.archlinux.org/packages/botflix-git) or use a aur helper like `yay` or `paru`. 34 | ```bash 35 | yay -Syu botflix-git 36 | ``` 37 | To run 38 | ```bash 39 | botflix 40 | ``` 41 | `botflix` is the replacement for the command prefix `python3 main.py` in this readme. for example `python3 main.py config "vlc"` will be `botflix config "vlc"`, the final step after installation. 42 | #### For others 43 | > Botflix is written in Python, and it depends on [webtorrent](https://github.com/webtorrent/webtorrent-cli), [NodeJS](https://nodejs.org) and [npm](https://www.npmjs.com) 44 | 45 | 1. install NodeJS on your machine ([read mode](https://nodejs.org/en/download/)). 46 | ```bash 47 | node --version 48 | v17.9.0 #or higher 49 | ``` 50 | 2. make sure that you have npm already installed ([read more](https://docs.npmjs.com/cli/v7/configuring-npm/install)). 51 | ```bash 52 | npm --version 53 | 8.8.0 # or higher 54 | ``` 55 | 3. now let's install webtorrent ([read more](https://github.com/webtorrent/webtorrent-cli)). 56 | ```bash 57 | npm install webtorrent-cli -g 58 | webtorrent --version # 4.0.4 (1.8.16) 59 | ``` 60 | _Note: if not installed try with sudo privileges._ 61 | 62 | 4. clone the repo in your local machine. 63 | ```bash 64 | git clone https://github.com/kaboussi/Botflix && cd Botflix 65 | ``` 66 | ![clone results](.github/clone.png) 67 | 68 | 5. create a virtual environment. 69 | * Beginner Windows users who couldn't set up the [virtualenv] check this doc. 70 | * Unix Users 71 | ```bash 72 | python3 -m venv venv && source venv/bin/activate 73 | ``` 74 | ![creating virtualenv](.github/virtualenv.png) 75 | **Note**: on Debian/Ubuntu systems you will first need to install venv using 76 | ```bash 77 | sudo apt install python3-venv 78 | ``` 79 | 80 | 6. Install necessary packages. 81 | ```bash 82 | pip install -r requirements.txt 83 | ``` 84 | ![install packages](.github/pipintsall.png) 85 | 86 | ## Usage: 87 | * First you need to set up a default player. 88 | > Note that only [vlc](https://www.videolan.org/vlc/) and [mpv](https://mpv.io/) are supported.
89 | > If you are a Windows user, make sure you add your player to the PATH. [read more](https://www.vlchelp.com/add-vlc-command-prompt-windows/) 90 | ```bash 91 | python3 main.py config "vlc" 92 | ``` 93 | ![config](.github/config.png) 94 | * If you want to get top movies: 95 | ```bash 96 | python3 main.py top 97 | ``` 98 | * If you want to watch a TV Series: 99 | ```bash 100 | python3 main.py serie 101 | ``` 102 | * If you want to search for a specific movie (_"[red notice](https://www.imdb.com/title/tt7991608/)"_ for example): 103 | ```bash 104 | python3 main.py search 105 | What movie are you looking for? red notice 106 | ``` 107 | ![table of search](.github/table_of_movies.png "table of search") 108 | * To start watching you can just type the number of the movie in the table. 109 | 110 | ## Contributing: 111 | Contributions are what makes the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated. 112 | 113 | If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again! 114 | 115 | 1. Fork the Project 116 | 2. Create your Feature Branch `git checkout -b feature/AmazingFeature` 117 | 3. Commit your Changes `git commit -m 'Add some AmazingFeature` 118 | 4. Push to the Branch `git push origin feature/AmazingFeature` 119 | Open a Pull Request. 120 | 121 | ## Contributors❤: 122 |
123 | 124 | 125 | 126 |
127 | 128 | ## License: 129 | [MIT](https://mit-license.org/)
130 | [DISCLAIMER](https://www.disclaimertemplate.net/live.php?token=xyytrgo4QtkLMNCB6LEIO6Q39YDFyhu2) 131 | 132 | 133 | 134 | 135 | [virtualenv]: https://github.com/kaboussi/Botflix/blob/main/docs/windowsVenv.md 136 | -------------------------------------------------------------------------------- /docs/windowsVenv.md: -------------------------------------------------------------------------------- 1 | ###
Stream-CLI
2 | 3 | #### Instructions to setup Stream-CLI on windows: 4 | *For windows 10 and 11 users, It's highly recommended to use [Windows WSL], cause many of the tutorials for Python are written for Linux environments.* 5 | 6 | 1. Open PowerShell or Windows Command Prompt (cmd). 7 | 2. type the following command `wsl --install`. 8 | 3. After the installation is complete restart your machine. 9 | 4. Check wsl version `wsl -l -v`. 10 | 11 | > You can use this documentation of how to complete the [setup] 12 | 13 | 1. Install pip `curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py` 14 | 2. run this in the same folder you run the curl in it `python3 get-pip.py` 15 | 3. clone the repo if you aren't already `git clone https://github.com/redelka00/stream-cli` and `cd stream-cli`. 16 | 4. create venv `virtualenv venv`. 17 | 5. Activate virtualenv `.\venv\Scripts\activate.bat` 18 | 19 | Once you setup the virtual environment successfully go back to the [README] 20 | 21 | 22 | 23 | [Windows WSL]: https://docs.microsoft.com/en-us/windows/wsl/ 24 | [setup]: https://www.liquidweb.com/kb/how-to-setup-a-python-virtual-environment-on-windows-10/ 25 | [README]: https://github.com/redelka00/stream-cli 26 | -------------------------------------------------------------------------------- /helper/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lkabuci/Botflix/08c86b35b93ef049432aee0840ae2fcd8ce2963c/helper/__init__.py -------------------------------------------------------------------------------- /helper/user_angent.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | USER_AGENTS = [ 4 | "Mozilla/5.0 (Linux; Android 12; SM-S906N Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/80.0.3987.119 Mobile Safari/537.36", 5 | "Mozilla/5.0 (Linux; Android 10; SM-G996U Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Mobile Safari/537.36", 6 | "Mozilla/5.0 (Linux; Android 10; SM-G980F Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.96 Mobile Safari/537.36", 7 | "Mozilla/5.0 (Linux; Android 9; SM-G973U Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", 8 | "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", 9 | "Mozilla/5.0 (Linux; Android 7.0; SM-G892A Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.107 Mobile Safari/537.36", 10 | "Mozilla/5.0 (Linux; Android 7.0; SM-G930VC Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36", 11 | "Mozilla/5.0 (Linux; Android 6.0.1; SM-G935S Build/MMB29K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36", 12 | "Mozilla/5.0 (Linux; Android 6.0.1; SM-G920V Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36", 13 | "Mozilla/5.0 (Linux; Android 5.1.1; SM-G928X Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.83 Mobile Safari/537.36", 14 | "Mozilla/5.0 (Linux; Android 12; Pixel 6 Build/SD1A.210817.023; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/94.0.4606.71 Mobile Safari/537.36", 15 | "Mozilla/5.0 (Linux; Android 11; Pixel 5 Build/RQ3A.210805.001.A1; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/92.0.4515.159 Mobile Safari/537.36", 16 | "Mozilla/5.0 (Linux; Android 10; Google Pixel 4 Build/QD1A.190821.014.C2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 Mobile Safari/537.36", 17 | "Mozilla/5.0 (Linux; Android 10; Google Pixel 4 Build/QD1A.190821.014.C2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 Mobile Safari/537.36", 18 | "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 Build/OPD1.170811.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36", 19 | "Mozilla/5.0 (Linux; Android 7.1.1; Google Pixel Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/54.0.2840.85 Mobile Safari/537.36", 20 | "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 6P Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.83 Mobile Safari/537.36", 21 | "Mozilla/5.0 (Linux; Android 9; J8110 Build/55.0.A.0.552; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36", 22 | "Mozilla/5.0 (Linux; Android 7.1.1; G8231 Build/41.2.A.0.219; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36", 23 | "Mozilla/5.0 (Linux; Android 6.0.1; E6653 Build/32.2.A.0.253) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36", 24 | "Mozilla/5.0 (Linux; Android 10; HTC Desire 21 pro 5G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.127 Mobile Safari/537.36", 25 | "Mozilla/5.0 (Linux; Android 10; Wildfire U20 5G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.136 Mobile Safari/537.36", 26 | "Mozilla/5.0 (Linux; Android 6.0; HTC One X10 Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/61.0.3163.98 Mobile Safari/537.36", 27 | "Mozilla/5.0 (Linux; Android 6.0; HTC One M9 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.3", 28 | "Mozilla/5.0 (iPhone14,6; U; CPU iPhone OS 15_4 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/19E241 Safari/602.1", 29 | "Mozilla/5.0 (iPhone14,3; U; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/19A346 Safari/602.1", 30 | "Mozilla/5.0 (iPhone13,2; U; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/15E148 Safari/602.1", 31 | "Mozilla/5.0 (iPhone12,1; U; CPU iPhone OS 13_0 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/15E148 Safari/602.1", 32 | "Mozilla/5.0 (iPhone12,1; U; CPU iPhone OS 13_0 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/15E148 Safari/602.1", 33 | "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", 34 | "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/69.0.3497.105 Mobile/15E148 Safari/605.1", 35 | "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/13.2b11866 Mobile/16A366 Safari/605.1.15", 36 | "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1", 37 | "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1", 38 | "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A5370a Safari/604.1", 39 | "Mozilla/5.0 (iPhone9,3; U; CPU iPhone OS 10_0_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/14A403 Safari/602.1", 40 | "Mozilla/5.0 (iPhone9,4; U; CPU iPhone OS 10_0_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/14A403 Safari/602.1", 41 | "Mozilla/5.0 (Apple-iPhone7C2/1202.466; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543 Safari/419.3", 42 | "Mozilla/5.0 (Windows Phone 10.0; Android 6.0.1; Microsoft; RM-1152) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Mobile Safari/537.36 Edge/15.15254", 43 | "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; RM-1127_16056) AppleWebKit/537.36(KHTML, like Gecko) Chrome/42.0.2311.135 Mobile Safari/537.36 Edge/12.10536", 44 | "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.1058", 45 | "Mozilla/5.0 (Linux; Android 12; SM-X906C Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/80.0.3987.119 Mobile Safari/537.36", 46 | "Mozilla/5.0 (Linux; Android 11; Lenovo YT-J706X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36", 47 | "Mozilla/5.0 (Linux; Android 7.0; Pixel C Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/52.0.2743.98 Safari/537.36", 48 | "Mozilla/5.0 (Linux; Android 6.0.1; SGP771 Build/32.2.A.0.253; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/52.0.2743.98 Safari/537.36", 49 | "Mozilla/5.0 (Linux; Android 6.0.1; SHIELD Tablet K1 Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Safari/537.36", 50 | "Mozilla/5.0 (Linux; Android 7.0; SM-T827R4 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.116 Safari/537.36", 51 | "Mozilla/5.0 (Linux; Android 5.0.2; SAMSUNG SM-T550 Build/LRX22G) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/3.3 Chrome/38.0.2125.102 Safari/537.36", 52 | "Mozilla/5.0 (Linux; Android 4.4.3; KFTHWI Build/KTU84M) AppleWebKit/537.36 (KHTML, like Gecko) Silk/47.1.79 like Chrome/47.0.2526.80 Safari/537.36", 53 | "Mozilla/5.0 (Linux; Android 5.0.2; LG-V410/V41020c Build/LRX22G) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/34.0.1847.118 Safari/537.36", 54 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246", 55 | "Mozilla/5.0 (X11; CrOS x86_64 8172.45.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.64 Safari/537.36", 56 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/601.3.9 (KHTML, like Gecko) Version/9.0.2 Safari/601.3.9", 57 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36", 58 | "Mozilla/5.0 (Linux; U; Android 4.2.2; he-il; NEO-X5-116A Build/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30", 59 | "Mozilla/5.0 (Linux; Android 9; AFTWMST22 Build/PS7233; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/88.0.4324.152 Mobile Safari/537.36", 60 | "Mozilla/5.0 (Linux; Android 5.1; AFTS Build/LMY47O) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/41.99900.2250.0242 Safari/537.36", 61 | "Mozilla/5.0 (PlayStation; PlayStation 5/2.26) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/605.1.15", 62 | "Mozilla/5.0 (PlayStation 4 3.11) AppleWebKit/537.73 (KHTML, like Gecko)", 63 | "Mozilla/5.0 (PlayStation Vita 3.61) AppleWebKit/537.73 (KHTML, like Gecko) Silk/3.2", 64 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox Series X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.82 Safari/537.36 Edge/20.02", 65 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64; XBOX_ONE_ED) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393", 66 | "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Xbox; Xbox One) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.10586", 67 | "Mozilla/5.0 (Nintendo Switch; WifiWebAuthApplet) AppleWebKit/601.6 (KHTML, like Gecko) NF/4.0.0.5.10 NintendoBrowser/5.1.0.13343", 68 | "Mozilla/5.0 (Nintendo WiiU) AppleWebKit/536.30 (KHTML, like Gecko) NX/3.0.4.2.12 NintendoBrowser/4.3.1.11264.US", 69 | "Mozilla/5.0 (Nintendo 3DS; U; ; en) Version/1.7412.EU", 70 | "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)", 71 | "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)", 72 | "Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)", 73 | "Mozilla/5.0 (X11; U; Linux armv7l like Android; en-us) AppleWebKit/531.2+ (KHTML, like Gecko) Version/5.0 Safari/533.2+ Kindle/3.0+", 74 | "Mozilla/5.0 (Linux; U; en-US) AppleWebKit/528.5+ (KHTML, like Gecko, Safari/528.5+) Version/4.0 Kindle/3.0 (screen 600x800; rotate)", 75 | ] 76 | 77 | 78 | def get_user_agent() -> str: 79 | return random.choice(USER_AGENTS) 80 | -------------------------------------------------------------------------------- /helper/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | import requests 5 | from rich import print 6 | from rich.console import Console 7 | 8 | PLAYERS_LIST = ["mpv", "vlc", "mplayer"] 9 | PLAYER_FILE = "player.txt" 10 | 11 | console = Console() 12 | 13 | 14 | def clear_screen() -> None: 15 | os.system("clear") if os.name == "posix" else os.system("cls") 16 | 17 | 18 | def is_player_valid() -> bool: 19 | """return True if player is valid otherwise False""" 20 | 21 | try: 22 | player_name = Path(PLAYER_FILE).read_text() 23 | except FileNotFoundError: 24 | return False 25 | if (player_name not in PLAYERS_LIST) or (os.stat(PLAYER_FILE).st_size == 0): 26 | return False 27 | 28 | # Check if player is in windows path 29 | if os.name == "nt": 30 | for exec_path in os.get_exec_path(): 31 | if any( 32 | player_name.lower() 33 | in list(map(lambda x: x.lower(), exec_path.split(os.sep))) 34 | ): 35 | return True 36 | else: 37 | print(f"[bold red]{player_name} is not in your path[bold red]") 38 | return False 39 | return True 40 | 41 | 42 | def handle_errors(url: str): 43 | clear_screen() 44 | try: 45 | requests.get(url) 46 | 47 | except requests.exceptions.ConnectionError: 48 | console.print("[bold red]ERR_INTERNET_DISCONNECTED[red bold]") 49 | 50 | except requests.exceptions.HTTPError: 51 | console.print( 52 | "[bold red]The host is probably banned in your country please consider using a VPN or choose another " 53 | "host[bold red] " 54 | ) 55 | exit() 56 | 57 | except requests.exceptions.RequestException as err: 58 | print(err) 59 | exit() 60 | 61 | 62 | def set_player(name: str): 63 | player = Path(PLAYER_FILE) 64 | player.write_text(name) 65 | 66 | 67 | def get_player() -> str: 68 | player = Path(PLAYER_FILE) 69 | if player.exists(): 70 | return player.read_text() 71 | else: 72 | print("[red bold]You need to setup a default player first[bold red]") 73 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | # TODO: 2 | # Add instructions installation script that include windows 3 | # More flexible (ask the user about the default player for example) 4 | # Add it to aur 5 | 6 | READ='\e[31m%s\e[0m' 7 | GREEN='\e[32m%s\e[0m' 8 | YELLOW='\e[33m%s\e[0m' 9 | BLUE='\e[34m%s\e[0m' 10 | 11 | sudo npm install webtorrent-cli -g 12 | python -m venv venv 13 | source venv/bin/activate 14 | clear 15 | 16 | #printf "Green" "Successfully created a VE" 17 | echo -n mpv > player.txt 18 | printf $BLUE "mpv is now your default player change it latter" 19 | 20 | # Create an executable file 21 | mainpath=$PWD 22 | echo """ 23 | cd $mainpath 24 | source venv/bin/activate 25 | python main.py 26 | """ > botflix 27 | chmod +x botflix 28 | 29 | # move the executable file to path 30 | sudo cp botflix /usr/bin/botflix 31 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from rich.console import Console 2 | from simple_term_menu import TerminalMenu 3 | 4 | from helper.utils import set_player 5 | from stream_cli.api.leet import leet 6 | from stream_cli.api.torrentgalaxy import torrent_galaxy 7 | from stream_cli.runner import apprun 8 | 9 | console = Console() 10 | 11 | 12 | def get_top(choice): 13 | choices = { 14 | 1: apprun(torrent_galaxy()), 15 | 2: apprun(leet()), 16 | } 17 | return choices[choice] 18 | 19 | 20 | def get_movie(choice): 21 | choices = { 22 | 1: apprun(torrent_galaxy("movie")), 23 | 2: apprun(leet("movie")), 24 | } 25 | return choices[choice] 26 | 27 | 28 | def get_serie(choice): 29 | choices = {1: apprun(torrent_galaxy("serie")), 2: apprun(leet("serie"))} 30 | return choices[choice] 31 | 32 | 33 | def main(): 34 | main_menu_title = " Stream-CLI\n" 35 | main_menu_items = [ 36 | "Top movies", 37 | "Search for a movie", 38 | "Search for a TvSerie", 39 | "Configuration", 40 | "Quit", 41 | ] 42 | main_menu_cursor = "> " 43 | main_menu_cursor_style = ("fg_red", "bold") 44 | main_menu_exit = False 45 | 46 | main_menu = TerminalMenu( 47 | menu_entries=main_menu_items, 48 | title=main_menu_title, 49 | menu_cursor=main_menu_cursor, 50 | menu_cursor_style=main_menu_cursor_style, 51 | cycle_cursor=True, 52 | clear_screen=True, 53 | ) 54 | 55 | config_title = " Choose a default player: \n" 56 | config_items = ["Back to Main Menu", "VLC", "MPV"] 57 | config_back = False 58 | config = TerminalMenu( 59 | config_items, 60 | title=config_title, 61 | menu_cursor=main_menu_cursor, 62 | menu_cursor_style=main_menu_cursor_style, 63 | cycle_cursor=True, 64 | clear_screen=True, 65 | ) 66 | 67 | host = ["Back to main menu", "TGx", "1337x", "YTS"] 68 | host_back = False 69 | choose_host = TerminalMenu( 70 | menu_entries=host, 71 | menu_cursor=main_menu_cursor, 72 | menu_cursor_style=main_menu_cursor_style, 73 | cycle_cursor=True, 74 | clear_screen=True, 75 | ) 76 | 77 | while not main_menu_exit: 78 | main_sel = main_menu.show() 79 | 80 | # Top Movies 81 | if main_sel == 0: 82 | while not host_back: 83 | hosts = choose_host.show() 84 | if hosts == 0: 85 | host_back = True 86 | else: 87 | get_top(hosts) 88 | host_back = False 89 | 90 | # Search By a movies 91 | elif main_sel == 1: 92 | while not host_back: 93 | hosts = choose_host.show() 94 | if hosts == 0: 95 | host_back = True 96 | else: 97 | get_movie(hosts) 98 | host_back = False 99 | 100 | # Search By TvShows 101 | elif main_sel == 2: 102 | while not host_back: 103 | hosts = choose_host.show() 104 | if hosts == 0: 105 | host_back = True 106 | else: 107 | get_serie(hosts) 108 | host_back = False 109 | 110 | # Configuration 111 | elif main_sel == 3: 112 | while not config_back: 113 | edit_sel = config.show() 114 | if edit_sel == 0: 115 | config_back = True 116 | elif edit_sel == 1: 117 | set_player("vlc") 118 | config_back = True 119 | elif edit_sel == 2: 120 | set_player("mpv") 121 | config_back = True 122 | config_back = False 123 | 124 | # Quit 125 | elif main_sel == 4: 126 | main_menu_exit = True 127 | 128 | 129 | if __name__ == "__main__": 130 | main() 131 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | autopep8==1.6.0 2 | black==22.3.0 3 | flake8==4.0.1 4 | isort==5.10.1 5 | pre-commit==2.19.0 6 | rich==12.4.4 7 | Scrapy==2.6.1 8 | simple_term_menu==1.4.1 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import setuptools 4 | 5 | requirements = Path("requirements.txt").read_text().splitlines() 6 | readme = Path("README.md").read_text() 7 | project_license = Path("LICENSE").read_text() 8 | 9 | setuptools.setup( 10 | name="stream-cli", 11 | author="Redouane Elkaboussi", 12 | author_email="elkaboussi@pm.me", 13 | version="v0.2", 14 | description="stream movies directly from your terminal!", 15 | long_description=readme, 16 | packages=requirements, 17 | license=project_license, 18 | ) 19 | -------------------------------------------------------------------------------- /stream_cli/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lkabuci/Botflix/08c86b35b93ef049432aee0840ae2fcd8ce2963c/stream_cli/__init__.py -------------------------------------------------------------------------------- /stream_cli/api/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | sys.path.append(os.getcwd()) 5 | -------------------------------------------------------------------------------- /stream_cli/api/leet.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Optional 2 | 3 | import scrapy 4 | from rich.console import Console 5 | 6 | from helper.utils import handle_errors 7 | 8 | MOVIES = "tbody tr" 9 | MOVIE_INFO = "div.torrent-detail-page" 10 | TITLE = "div h1::text" 11 | SIZE = "div.no-top-radius div.clearfix ul.list li:nth-child(4) span::text" 12 | SEEDS = "div.no-top-radius div.clearfix ul.list:nth-child(3) li:nth-child(4) span::text" 13 | LEECHES = ( 14 | "div.no-top-radius div.clearfix ul.list:nth-child(3) li:nth-child(5) span::text" 15 | ) 16 | VIEWS = "div.no-top-radius div.clearfix ul.list:nth-child(3) li span::text" 17 | DOMAIN = "https://1337x.to" 18 | PAGE_INFO = "td a:nth-child(2)::attr(href)" 19 | MAGNET = "div.no-top-radius div.clearfix ul li a::attr(href)" 20 | 21 | 22 | handle_errors("https://www.1337x.to/") 23 | console = Console() 24 | 25 | 26 | def leet(category: Optional[str] = None): 27 | def _set_category() -> str: 28 | """return the target url""" 29 | 30 | if category is None: 31 | return "https://1337x.to/popular-movies" 32 | 33 | else: 34 | name = console.input("[bold]What are you looking for? ") 35 | converted_name = ( 36 | "%20".join(name.split()) + "%207" if name is not None else None 37 | ) 38 | 39 | if category == "movie": 40 | return ( 41 | f"https://1337x.to/category-search/{converted_name}%207/Movies/1/" 42 | ) 43 | 44 | elif category == "serie": 45 | return f"https://1337x.to/category-search/{converted_name}%207/TV/1/" 46 | 47 | class _Leet(scrapy.Spider): 48 | 49 | output: List[Dict] = [] 50 | idx: int = 1 51 | name: str = "leet" 52 | url: str = _set_category() 53 | 54 | start_urls = [url] 55 | limit = 20 56 | 57 | def parse(self, response): 58 | movies = response.css(MOVIES) 59 | for idx, movie in enumerate(movies, start=1): 60 | if idx <= _Leet.limit: 61 | page_info = ( 62 | DOMAIN + movie.css("td a:nth-child(2)::attr(href)").get() 63 | ) 64 | yield response.follow(page_info, callback=self.parse_movie) 65 | else: 66 | break 67 | 68 | def parse_movie(self, response): 69 | 70 | for info in response.css(MOVIE_INFO): 71 | items = { 72 | "index": _Leet.idx, 73 | "title": info.css(TITLE).get().strip(), 74 | "size": info.css(SIZE).get(), 75 | "seeds": info.css(SEEDS).get(), 76 | "leeches": info.css(LEECHES).get(), 77 | "views": info.css(VIEWS).get(), 78 | "magnet": info.css(MAGNET).get(), 79 | } 80 | _Leet.output.append(items) 81 | _Leet.idx += 1 82 | yield items 83 | 84 | return _Leet 85 | -------------------------------------------------------------------------------- /stream_cli/api/torrentgalaxy.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import scrapy 4 | from rich.console import Console 5 | 6 | from helper.utils import handle_errors 7 | 8 | MOVIES = "div.tgxtable div.tgxtablerow" 9 | TITLE = "div.textshadow div a.txlight b::text" 10 | SIZE = "div span.badge::text" 11 | SEEDS = "div span font[color='green'] b::text" 12 | LEECHES = "div span font[color='#ff0000'] b::text" 13 | VIEWS = "div span[title='Views'] font b::text" 14 | LINKS = "div[style='text-align:center;width:52px;padding-bottom:0px;padding-top:5px;'] a:nth-child(2)::attr(href)" 15 | 16 | handle_errors("https://www.torrentgalaxy.to/") 17 | console = Console() 18 | 19 | 20 | def torrent_galaxy(category: Optional[str] = None): 21 | def _set_category() -> str: 22 | """return the target url""" 23 | 24 | if category is None: 25 | return "https://torrentgalaxy.to/" 26 | 27 | else: 28 | name = console.input("[bold]What are you looking for? ") 29 | converted_name = "+".join(name.split()) if name is not None else None 30 | 31 | if category == "movie": 32 | return ( 33 | "https://torrentgalaxy.to/torrents.php?c3=1&c46=1&c45=1&c42=1&c4=1&c1=1" 34 | f"&search={converted_name}&sort=seeders&order=desc&lang=0&nox=2&nox=1#results" 35 | ) 36 | 37 | elif category == "serie": 38 | return ( 39 | f"https://torrentgalaxy.to/torrents.php?c41=1&c5=1&c6=1&search={converted_name}&lang=0&nox=1" 40 | "&sort=seeders&order=desc" 41 | ) 42 | 43 | class _TorrentGalaxy(scrapy.Spider): 44 | 45 | output = [] 46 | name = "tgx" 47 | url = _set_category() 48 | 49 | start_urls = [url] 50 | limit = 20 51 | 52 | def parse(self, response): 53 | 54 | movies = response.css(MOVIES) 55 | for idx, movie in enumerate(movies, start=1): 56 | if idx <= _TorrentGalaxy.limit: 57 | items = { 58 | "index": idx, 59 | "title": movie.css(TITLE).get(), 60 | "size": movie.css(SIZE).get(), 61 | "seeds": movie.css(SEEDS).get(), 62 | "leeches": movie.css(LEECHES).get(), 63 | "views": movie.css(VIEWS).get(), 64 | "magnet": movie.css(LINKS).get(), 65 | } 66 | _TorrentGalaxy.output.append(items) 67 | yield items 68 | else: 69 | break 70 | 71 | return _TorrentGalaxy 72 | -------------------------------------------------------------------------------- /stream_cli/interface.py: -------------------------------------------------------------------------------- 1 | from rich import box 2 | from rich.console import Console 3 | from rich.table import Table 4 | 5 | 6 | def print_table_of_movies(list_of_movies: list): 7 | """takes a list of dictionaries that contain movies data in it 8 | is_top_movies_choice: whether searched for top_movies or custom search 9 | """ 10 | table = Table(title="🎖🎥 Stream-CLI 🎥🎖", box=box.HEAVY) 11 | 12 | table.add_column("Index", justify="center", style="yellow") 13 | table.add_column("Title", style="white", no_wrap=False) 14 | table.add_column("Size", justify="center", style="red") 15 | table.add_column("views", justify="center", style="yellow") 16 | table.add_column("Seeds", justify="center", style="green") 17 | table.add_column("Leeches", justify="center", style="red") 18 | 19 | for movie in list_of_movies: 20 | table.add_row( 21 | str(movie["index"]), 22 | movie["title"], 23 | movie["size"], 24 | movie["views"], 25 | movie["seeds"], 26 | movie["leeches"], 27 | ) 28 | 29 | console = Console() 30 | console.print(table) 31 | -------------------------------------------------------------------------------- /stream_cli/runner.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import Callable, Generator, List 3 | 4 | import requests 5 | from rich import print 6 | from scrapy.crawler import CrawlerProcess 7 | 8 | from helper import utils 9 | from helper.user_angent import get_user_agent 10 | from stream_cli.interface import print_table_of_movies 11 | from stream_cli.stream import get_magnet, stream 12 | 13 | 14 | def start_scrawling(spider_class: Callable[[], Generator]) -> List[dict]: 15 | """takes a spider class as an argument ( TopMoviesSpider or SearchedMoviesSpider ) 16 | return a list of movies inside a dictionary 17 | """ 18 | 19 | twisted_error = "twisted.internet.reactor" 20 | if twisted_error in sys.modules: 21 | del sys.modules[twisted_error] 22 | 23 | process = CrawlerProcess( 24 | settings={ 25 | "LOG_LEVEL": "ERROR", 26 | "USER_AGENT": get_user_agent(), 27 | } 28 | ) 29 | process.crawl(spider_class) 30 | process.start() 31 | 32 | # exit if result is null 33 | utils.clear_screen() 34 | if spider_class.output == []: 35 | response = requests.get("https://www.torrentgalaxy.to/").status_code 36 | if response != 200: 37 | print( 38 | "[bold red]Unable to connect to torrent provider. Please use vpn. Exiting[/bold red]" 39 | ) 40 | else: 41 | print("[bold red]Movie Not Found Error[/bold red]") 42 | exit(1) 43 | 44 | return spider_class.output 45 | 46 | 47 | def apprun(scraping_class) -> None: 48 | if not utils.is_player_valid(): 49 | print("[bold red]Setup a default player first[bold red]") 50 | exit(1) 51 | movies = start_scrawling(scraping_class) 52 | print_table_of_movies(movies) 53 | magnets = [movie["magnet"] for movie in movies] 54 | magnet = get_magnet(magnets) 55 | 56 | player = utils.get_player() 57 | stream(magnet, default_player=player) 58 | -------------------------------------------------------------------------------- /stream_cli/stream.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import List 3 | 4 | from rich.console import Console 5 | 6 | console = Console() 7 | 8 | 9 | def get_magnet(magnets: List[str]) -> str: 10 | """takes magnets list, return the chosen magnet""" 11 | 12 | number = -1 13 | magnet_count = len(magnets) 14 | while (number < 0) or (number > magnet_count): 15 | try: 16 | number = int( 17 | console.input( 18 | "[bold i]Enter Your Choice (or 0 if you want to quit): [bold i]" 19 | ) 20 | ) 21 | if (number < 0) or (number > magnet_count): 22 | console.print( 23 | f"[red]Your choice must be between 0 (quit) and {magnet_count}.[red]" 24 | ) 25 | except Exception: 26 | console.print("[red]Invalid input.[red]") 27 | if number == 0: 28 | console.print("[red]Quitting...[red]") 29 | exit() 30 | else: 31 | # actual index starts 0 --> 13 32 | # but the table shows 1 --> 14 33 | # -1 to select from 1 --> 14 34 | return magnets[number - 1] 35 | 36 | 37 | def stream(magnet: str, default_player: str) -> None: 38 | """takes a chosen magnet and a default player 39 | run the process. 40 | """ 41 | 42 | try: 43 | os.system(f'webtorrent "{magnet}" --{default_player}') 44 | except Exception as e: 45 | print(e) 46 | print(f"[red bold]Error: {default_player} is not in your PATH!", end="\n") 47 | print("Please consider adding the default player to the right path", end="\n") 48 | print("Quitting... [/red bold]") 49 | exit(1) 50 | --------------------------------------------------------------------------------