├── .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 |
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 | 
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 | 
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 | 
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 | 
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 | 
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 |
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 |
--------------------------------------------------------------------------------