├── .gitignore ├── .vs ├── ProjectSettings.json ├── PythonSettings.json ├── VSWorkspaceState.json ├── auto_py_torrent │ └── v16 │ │ └── .suo └── slnx.sqlite ├── HISTORY.rst ├── LICENSE ├── Makefile ├── README.rst ├── auto_py_torrent.pyproj ├── auto_py_torrent.pyproj.user ├── auto_py_torrent.sln ├── auto_py_torrent ├── __init__.py ├── __version__.py └── auto_py_torrent.py ├── docs └── __init__.py ├── requirements.txt ├── setup.py └── tests └── __init__.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | .idea/ 104 | .vscode/ 105 | -------------------------------------------------------------------------------- /.vs/ProjectSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "CurrentProjectSetting": null 3 | } -------------------------------------------------------------------------------- /.vs/PythonSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "TestFramework": "Pytest", 3 | "Interpreter": "env\\Scripts\\python.exe" 4 | } -------------------------------------------------------------------------------- /.vs/VSWorkspaceState.json: -------------------------------------------------------------------------------- 1 | { 2 | "ExpandedNodes": [ 3 | "", 4 | "\\auto_py_torrent", 5 | "\\tests" 6 | ], 7 | "PreviewInSolutionExplorer": false 8 | } -------------------------------------------------------------------------------- /.vs/auto_py_torrent/v16/.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ocslegna/auto_py_torrent/5950c8791acebafc0126f3440796065c9e066dee/.vs/auto_py_torrent/v16/.suo -------------------------------------------------------------------------------- /.vs/slnx.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ocslegna/auto_py_torrent/5950c8791acebafc0126f3440796065c9e066dee/.vs/slnx.sqlite -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | .. :changelog: 2 | 3 | Release History 4 | --------------- 5 | 6 | Development 7 | +++++++++++ 8 | 9 | 1.0.8 (2017-08-04) 10 | ++++++++++++++++++ 11 | 12 | * Improve README. 13 | 14 | **Improvements** 15 | 16 | - Provide images to README. 17 | 18 | 19 | 1.0.7 (2017-08-04) 20 | ++++++++++++++++++ 21 | 22 | * Improve README, PARSER info and provide more explicit information to the user. 23 | 24 | **Improvements** 25 | 26 | - Provide a better README, PARSER and remove back to menu option once a file was downloaded or already returned. 27 | 28 | 29 | 1.0.5 (2017-07-31) 30 | ++++++++++++++++++ 31 | 32 | * Dependency fix and minor usage help. 33 | 34 | **Bugfixes** 35 | 36 | - Fix bad bs4 dependency string in requires. 37 | 38 | 39 | 1.0.4 (2017-07-29) 40 | ++++++++++++++++++ 41 | 42 | * Improve README. 43 | 44 | **Improvements** 45 | 46 | - Provide a better README. 47 | 48 | 49 | 1.0.3 (2017-07-28) 50 | ++++++++++++++++++ 51 | 52 | * Minor. 53 | 54 | **Bugfixes** 55 | 56 | - Fix the select index of provided list. 57 | 58 | 59 | 1.0.2 (2017-07-28) 60 | ++++++++++++++++++ 61 | 62 | * pip3 install integration. 63 | 64 | **Improvements** 65 | 66 | - Provide install with pip3: ``sudo pip3 install auto_py_torrent``. 67 | 68 | 69 | 1.0.0 (2017-07-28) 70 | ++++++++++++++++++ 71 | 72 | * First launch! Gone live. 73 | 74 | **Improvements** 75 | 76 | - ``auto_py_torrent`` runs best_rated and list mode. 77 | - 5 different sites to search. 78 | - Now you can repeat searches and downloads until you get tired. 79 | - Can back to menu while searching. 80 | - No need of double quotes with ['B', 'b'] options to search. 81 | - Minor improvements. 82 | 83 | **Bugfixes** 84 | 85 | - Fix poor decision making after found content. 86 | - Handle bad requests. 87 | 88 | 89 | 0.0.2 (2017-07-26) 90 | ++++++++++++++++++ 91 | 92 | * First alpha tests. 93 | 94 | **Improvements** 95 | 96 | - ``auto_py_torrent`` runs best_rated mode. 97 | 98 | **Bugfixes** 99 | 100 | - Resolve bad domain paths, fix parser, fix init class function, change main function. 101 | 102 | 103 | 0.0.1 (2017-07-23) 104 | ++++++++++++++++++ 105 | 106 | * First pre-alpha tests. 107 | 108 | **Improvements** 109 | 110 | - ``auto_py_torrent`` has now all scheduled torrent sites. 111 | 112 | **Bugfixes** 113 | 114 | - Resolve content for BeautifulSoup instantiation from requests. 115 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Gabriel Scotillo 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | init: 2 | pip3 install -r requirements.txt 3 | 4 | publish: 5 | pip3 install 'twine>=1.9.1' 6 | python3 setup.py sdist bdist_wheel 7 | twine upload dist/* 8 | rm -fr build dist .egg requests.egg-info -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | auto_py_torrent 2 | ############### 3 | 4 | **auto_py_torrent** is an automated tool for download files by obtaining torrents or magnets that are in different provided pages that the user can choose. 5 | 6 | Its goal is to make it easier for users to find the files they want and download them instantly. 7 |    8 | An ``auto_py_torrent`` command is provided in which the user can currently choose between two modes, ``best_rated`` and ``list`` mode, then it selects one of the torrent tracking pages for multimedia content and finally enter the text of what you want to download. 9 | 10 | We Can ``List`` It Out! 11 | 12 | .. image:: https://user-images.githubusercontent.com/6371898/28991985-b72c0340-7967-11e7-97a2-c33d96d43706.png 13 | :target: https://github.com/ocslegna/auto_py_torrent/ 14 | 15 | 16 | 17 | With a Little ``Help`` from My Friends! 18 | 19 | .. image:: https://user-images.githubusercontent.com/6371898/28991984-b46bb402-7967-11e7-9d39-9f8b55362ac9.png 20 | :target: https://github.com/ocslegna/auto_py_torrent/ 21 | 22 | 23 | 24 | .. contents:: 25 | 26 | .. section-numbering:: 27 | 28 | Main features 29 | ============= 30 | 31 | * Simple and easy search, for file downloads using torrent. 32 | * Formatted and colorized terminal output. 33 | 34 | 35 | Installation and an usage example. 36 | ================================== 37 | 38 | First, check your python3 version. 39 | ---------------------------------- 40 | 41 | .. code-block:: bash 42 | 43 | $ python3 --version 44 | 45 | Upgrade it as you wish. 46 | 47 | 48 | Install ``python3-pip``: 49 | ------------------------ 50 | 51 | ``Mac`` 52 | ~~~~~~~ 53 | .. code-block:: bash 54 | 55 | $ brew install python3 56 | 57 | ``Linux`` 58 | ~~~~~~~~ 59 | Using the package manager with different linux distributions: 60 | 61 | .. code-block:: bash 62 | 63 | # Ubuntu/Debian. 64 | $ sudo apt-get update 65 | $ sudo apt-get install -y python3-pip 66 | 67 | .. code-block:: bash 68 | 69 | # Fedora, CentOS, RHEL. 70 | $ sudo dnf install python3-pip 71 | 72 | .. code-block:: bash 73 | 74 | # Arch. 75 | $ pacman -S python3-pip 76 | 77 | 78 | ``Windows`` 79 | ~~~~~~~~~~~ 80 | 81 | If ``C:\path\to\python\Scripts\pip3`` is not there remite to: 82 | 83 | * ``_ 84 | for Windows download. 85 | 86 | 87 | Install virtualenv if necessary and activate it. 88 | ------------------------------------------------ 89 | 90 | .. code-block:: bash 91 | 92 | $ sudo pip3 install virtualenv 93 | $ cd 94 | $ virtualenv venv_auto_py 95 | $ cd venv_auto_py 96 | $ source bin/activate 97 | 98 | 99 | Install auto_py_torrent and get an example! 100 | ------------------------------------------- 101 | 102 | .. code-block:: bash 103 | 104 | # With virtual env activated: 105 | $ pip3 install auto_py_torrent 106 | 107 | # Without virtual env: 108 | $ sudo pip3 install auto_py_torrent 109 | 110 | # This way you are getting a detail list of results from ``torrent_project`` site. 111 | $ auto_py_torrent 1 0 "The simpsons" 112 | 113 | 114 | Usage 115 | ===== 116 | .. code-block:: bash 117 | 118 | $ auto_py_torrent MODE SELECTED_PAGE STRING_TO_SEARCH 119 | 120 | 121 | See also ``auto_py_torrent --help``. 122 | 123 | 124 | Examples 125 | -------- 126 | 127 | Using ``best_rated`` mode with ``torrent_project`` page: 128 | 129 | .. code-block:: bash 130 | 131 | $ auto_py_torrent 0 0 "The simpsons" 132 | 133 | 134 | Using ``list`` mode with ``the pirate bay`` page: 135 | 136 | .. code-block:: bash 137 | 138 | $ auto_py_torrent 1 1 "The simpsons" 139 | 140 | 141 | Meta 142 | ==== 143 | 144 | Dependencies 145 | ------------ 146 | 147 | **auto_py_torrent** uses this incredibles libraries: 148 | 149 | * `BeautifulSoup `_ 150 | — Python library for pulling data out of HTML and XML files. 151 | * `Requests `_ 152 | — Requests is an elegant and simple HTTP library for Python, built for human beings. 153 | * `Tabulate `_ 154 | — Python library for tabular data print in Python, a library and a command-line utility. 155 | * `Coloredlogs `_ 156 | — Python package that enables colored terminal output for Python’s logging module. 157 | * `lxml `_ 158 | — Python library for processing XML and HTML in the Python language. 159 | 160 | 161 | Release History 162 | --------------- 163 | 164 | See `HISTORY `_. 165 | 166 | 167 | Licence 168 | ------- 169 | 170 | MIT: `LICENSE `_. 171 | 172 | 173 | Author 174 | ------- 175 | 176 | `Gabriel Scotillo`_ (`@gabrielscotillo`_) 177 | 178 | 179 | Package index 180 | ------------- 181 | 182 | ``_. 183 | 184 | 185 | .. _Gabriel Scotillo: https://ocslegna.herokuapp.com 186 | .. _@gabrielscotillo: https://twitter.com/gabrielscotillo 187 | -------------------------------------------------------------------------------- /auto_py_torrent.pyproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | 2.0 6 | {16823f05-e280-4302-9c2a-c704ca8b29ca} 7 | 8 | setup.py 9 | 10 | . 11 | . 12 | {888888a0-9f3d-457c-b088-3a5042f75d52} 13 | Standard Python launcher 14 | MSBuild|env|$(MSBuildProjectFullPath) 15 | 16 | 17 | 18 | 19 | 10.0 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | env 40 | env (Python 3.7 (64-bit)) 41 | Scripts\python.exe 42 | Scripts\pythonw.exe 43 | 3.7 44 | X64 45 | PYTHONPATH 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /auto_py_torrent.pyproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | ShowAllFiles 5 | 6 | -------------------------------------------------------------------------------- /auto_py_torrent.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31205.134 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "auto_py_torrent", "auto_py_torrent.pyproj", "{16823F05-E280-4302-9C2A-C704CA8B29CA}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {16823F05-E280-4302-9C2A-C704CA8B29CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {16823F05-E280-4302-9C2A-C704CA8B29CA}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | GlobalSection(ExtensibilityGlobals) = postSolution 21 | SolutionGuid = {06869AA4-B542-46C1-AAC0-A52397FC5A95} 22 | EndGlobalSection 23 | EndGlobal 24 | -------------------------------------------------------------------------------- /auto_py_torrent/__init__.py: -------------------------------------------------------------------------------- 1 | """init.py - root of the package.""" 2 | -------------------------------------------------------------------------------- /auto_py_torrent/__version__.py: -------------------------------------------------------------------------------- 1 | __title__ = 'auto_py_torrent' 2 | __description__ = 'Automated tool for file downloads, using torrent or magnet.' 3 | __url__ = 'https://github.com/ocslegna/auto_py_torrent' 4 | __version__ = '1.0.8' 5 | __build__ = 0x001000 6 | __author__ = 'Gabriel Scotillo' 7 | __author_email__ = 'gabrielscotillo@gmail.com' 8 | __license__ = 'MIT' 9 | __copyright__ = 'Copyright 2017 Gabriel Scotillo' 10 | -------------------------------------------------------------------------------- /auto_py_torrent/auto_py_torrent.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """auto_py_torrent. 5 | 6 | This module provides utilities to download a torrent within specific types. 7 | """ 8 | 9 | # Author: Gabriel Scotillo 10 | # URL: https://github.com/ocslegna/auto_py_torrent 11 | # Please do not download illegal torrents or torrents that you do not have 12 | # permission to own. 13 | # This tool is for educational purposes only. Any damage you make will not 14 | # affect the author. 15 | 16 | 17 | import os 18 | import subprocess 19 | import sys 20 | import traceback 21 | import logging 22 | import argparse 23 | import re 24 | import textwrap 25 | import coloredlogs 26 | import requests 27 | 28 | from bs4 import BeautifulSoup 29 | from bs4 import UnicodeDammit 30 | from tabulate import tabulate 31 | 32 | 33 | MODES = 'best_rated list'.split() 34 | 35 | # {'torrentz2': 36 | # {'page': 'https://torrentz2.eu/search?f=', 37 | # 'key_search': 'did not match'}}, 38 | # {'rarbg': 39 | # {'page': 'https://rarbg.to/torrents.php?search=', 40 | # 'key_search': ''}}, 41 | 42 | TORRENTS = ({'torrent_project': 43 | {'page': 'https://torrentproject.se/?t=', 44 | 'key_search': 'No results', 45 | 'domain': 'https://torrentproject.se'}}, 46 | {'the_pirate_bay': 47 | {'page': 'https://proxyspotting.in/s/?q=', 48 | 'key_search': 'No hits', 49 | 'domain': 'https://proxyspotting.in'}}, 50 | {'1337x': 51 | {'page': 'https://1337x.to/search/', 52 | 'key_search': 'No results were returned', 53 | 'domain': 'https://1337x.to'}}, 54 | {'eztv': 55 | {'page': 'https://eztv.ag/search/', 56 | 'key_search': 'It does not have any.', 57 | 'domain': 'https://eztv.ag'}}, 58 | {'limetorrents': 59 | {'page': 'https://www.limetorrents.cc/search/all/', 60 | 'key_search': 'No results found', 61 | 'domain': 'https://www.limetorrents.cc'}}, 62 | {'isohunt': 63 | {'page': 'https://isohunt.to/torrents/?ihq=', 64 | 'key_search': 'No results found', 65 | 'domain': 'https://isohunt.to'}}) 66 | logging.basicConfig(level=logging.DEBUG) 67 | LOGGER = logging.getLogger(__name__) 68 | coloredlogs.install() 69 | 70 | 71 | class Colors: 72 | """Color class container.""" 73 | 74 | HEADER = '\033[95m' 75 | OKBLUE = '\033[94m' 76 | OKGREEN = '\033[92m' 77 | WARNING = '\033[93m' 78 | FAIL = '\033[91m' 79 | ENDC = '\033[0m' 80 | BOLD = '\033[1m' 81 | UNDERLINE = '\033[4m' 82 | BLACK = '\033[30m' 83 | BLUE = '\033[34m' 84 | GREEN = '\033[42m' 85 | CYAN = '\033[36m' 86 | RED = '\033[41m' 87 | PINK = '\033[95m' 88 | PURPLE = '\033[35m' 89 | LIGHTBLUE = '\033[94m' 90 | LGREEN = '\033[0m\033[32m' 91 | LIGHTCYAN = '\033[0m\033[36m' 92 | LRED = '\033[0m\033[31m' 93 | LIGHTPURPLE = '\033[0m\033[35m' 94 | SEEDER = '\033[1m\033[32m' 95 | LEECHER = '\033[1m\033[31m' 96 | 97 | 98 | def get_parser(): 99 | """Load parser for command line arguments. 100 | 101 | It parses argv/input into args variable. 102 | """ 103 | desc = Colors.LIGHTBLUE + textwrap.dedent( 104 | '''\ 105 | Welcome to 106 | _ _ _ 107 | __ _ _ _| |_ ___ _ __ _ _ | |_ ___ _ __ _ __ ___ _ __ | |_ 108 | / _` | | | | __/ _ \ | '_ \| | | | | __/ _ \| '__| '__/ _ \ '_ \| __| 109 | | (_| | |_| | || (_) | | |_) | |_| | | || (_) | | | | | __/ | | | |_ 110 | \__,_|\__,_|\__\___/____| .__/ \__, |___\__\___/|_| |_| \___|_| |_|\__| 111 | |_____|_| |___/_____| 112 | 113 | ------------------------------------ 114 | auto_py_torrent is an automated tool for download files by obtaining 115 | torrents or magnets that are in different provided pages that the 116 | user can choose. 117 | 118 | Its goal is to make it easier for users to find the files they want 119 | and download them instantly. 120 | 121 | An auto_py_torrent command is provided in which the user can 122 | currently choose between two modes, best_rated and list mode, then it 123 | selects one of the torrent tracking pages for multimedia content and 124 | finally enter the text of what you want to download. 125 | ------------------------------------ 126 | ''') + Colors.ENDC 127 | usage_info = Colors.LGREEN + textwrap.dedent( 128 | '''\ 129 | 130 | Use "%(prog)s --help" for more information. 131 | Examples: 132 | use "%(prog)s MODE SELECTED_PAGE STRING_TO_SEARCH # generic. 133 | use "%(prog)s 0 0 "The simpsons" # best rated. 134 | use "%(prog)s 1 0 "The simpsons" # list rated. 135 | 136 | Mode options: 137 | 0: best_rated. # Download the most rated file. 138 | 1: list. # Get a list, and select one of them. 139 | 140 | Page list options: 141 | 0: torrent project. 142 | 1: the pirate bay. 143 | 2: 1337x. 144 | 3: eztv. 145 | 4: limetorrents. 146 | 5: isohunt. 147 | ''') + Colors.ENDC 148 | epi = Colors.LIGHTPURPLE + textwrap.dedent( 149 | '''\ 150 | -> Thanks for using auto_py_torrent! 151 | ''') + Colors.ENDC 152 | 153 | # Parent and only parser. 154 | parser = argparse.ArgumentParser( 155 | add_help=True, 156 | formatter_class=argparse.RawTextHelpFormatter, 157 | usage=usage_info, 158 | description=desc, 159 | epilog=epi) 160 | parser.add_argument('mode', action='store', 161 | choices=range(len(MODES)), 162 | type=int, 163 | help='Select mode of file download.\n' 164 | ' e.g: 0(rated) or 1(list).') 165 | parser.add_argument('torr_page', action='store', 166 | choices=range(len(TORRENTS)), 167 | type=int, 168 | help='Select tracking page to download from.\n' 169 | ' e.g: 0 to .. ' + str(len(TORRENTS)-1) + '.') 170 | parser.add_argument('str_search', action='store', 171 | type=str, 172 | help='Input torrent string to search.\n' 173 | ' e.g: "String search"') 174 | return(parser) 175 | 176 | 177 | def is_num(var): 178 | """Check if var string is num. Should use it only with strings.""" 179 | try: 180 | int(var) 181 | return True 182 | except ValueError: 183 | return False 184 | 185 | 186 | class AutoPy: 187 | """AutoPy class for instance variables.""" 188 | 189 | def __init__(self, args, string_search, mode_search, 190 | page, key_search, torrent_page, domain): 191 | """Args not entered will be defaulted.""" 192 | self.args = args 193 | self.back_to_menu = False 194 | self.content_page = None 195 | self.domain = domain 196 | self.elements = None 197 | self.found_torrents = False 198 | self.hrefs = [] 199 | self.keep_search = True 200 | self.key_search = key_search 201 | self.magnet = "" 202 | self.mode_search = mode_search 203 | self.page = page 204 | self.picked_choice = False 205 | self.selected = "" 206 | self.string_search = string_search 207 | self.table = None 208 | self.torrent = "" 209 | self.torrent_page = torrent_page 210 | 211 | def open_magnet(self): 212 | """Open magnet according to os.""" 213 | if sys.platform.startswith('linux'): 214 | subprocess.Popen(['xdg-open', self.magnet], 215 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 216 | elif sys.platform.startswith('win32'): 217 | os.startfile(self.magnet) 218 | elif sys.platform.startswith('cygwin'): 219 | os.startfile(self.magnet) 220 | elif sys.platform.startswith('darwin'): 221 | subprocess.Popen(['open', self.magnet], 222 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 223 | else: 224 | subprocess.Popen(['xdg-open', self.magnet], 225 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 226 | 227 | def get_magnet(self, url): 228 | """Get magnet from torrent page. Url already got domain.""" 229 | content_most_rated = requests.get(url) 230 | rated_soup = BeautifulSoup(content_most_rated.content, 'lxml') 231 | 232 | if self.page == 'torrent_project': 233 | self.magnet = rated_soup.find( 234 | 'a', href=True, text=re.compile('Download'))['href'] 235 | 236 | elif self.page == 'the_pirate_bay': 237 | self.magnet = rated_soup.find( 238 | 'a', href=True, text=re.compile('Get this torrent'))['href'] 239 | 240 | elif self.page == '1337x': 241 | div1337 = rated_soup.find( 242 | 'div', {'class': 'torrent-category-detail'}) 243 | self.magnet = div1337.find('a', href=re.compile('magnet'))['href'] 244 | 245 | elif self.page == 'isohunt': 246 | self.magnet = rated_soup.find( 247 | 'a', href=re.compile('magnet'))['href'] 248 | 249 | else: 250 | print('Wrong page to get magnet!') 251 | sys.exit(1) 252 | 253 | def download_torrent(self): 254 | """Download torrent. 255 | 256 | Rated implies download the unique best rated torrent found. 257 | Otherwise: get the magnet and download it. 258 | """ 259 | try: 260 | if self.back_to_menu is True: 261 | return 262 | if self.found_torrents is False: 263 | print('Nothing found.') 264 | return 265 | if self.mode_search == 'best_rated': 266 | print('Downloading..') 267 | self.open_magnet() 268 | elif self.mode_search == 'list': 269 | if self.selected is not None: 270 | # t_p, pirate and 1337x got magnet inside, else direct. 271 | if self.page in ['eztv', 'limetorrents']: 272 | self.magnet = self.hrefs[int(self.selected)] 273 | print('Downloading..') 274 | self.open_magnet() 275 | elif self.page in ['the_pirate_bay', 276 | 'torrent_project', 277 | '1337x', 278 | 'isohunt']: 279 | url = self.hrefs[int(self.selected)] 280 | self.get_magnet(url) 281 | print('Downloading..') 282 | self.open_magnet() 283 | else: 284 | print('Bad selected page.') 285 | else: 286 | print('Nothing selected.') 287 | sys.exit(1) 288 | except Exception: 289 | print(traceback.format_exc()) 290 | sys.exit(0) 291 | 292 | def build_table(self): 293 | """Build table.""" 294 | headers = ['Title', 'Seeders', 'Leechers', 'Age', 'Size'] 295 | titles = [] 296 | seeders = [] 297 | leechers = [] 298 | ages = [] 299 | sizes = [] 300 | 301 | if self.page == 'torrent_project': 302 | titles = [list(span.find('a').stripped_strings)[0] 303 | for span in self.elements[0]] 304 | 305 | seeders = [span.get_text() for span in self.elements[1]] 306 | leechers = [span.get_text() for span in self.elements[2]] 307 | ages = [span.get_text() for span in self.elements[3]] 308 | sizes = [span.get_text() for span in self.elements[4]] 309 | 310 | # Torrents 311 | self.hrefs = [self.domain + 312 | span.find('a')['href'] 313 | for span in self.elements[0]] 314 | 315 | elif self.page == 'the_pirate_bay': 316 | for elem in self.elements[0]: 317 | title = elem.find('a', {'class': 'detLink'}).get_text() 318 | titles.append(title) 319 | 320 | font_text = elem.find( 321 | 'font', {'class': 'detDesc'}).get_text() 322 | dammit = UnicodeDammit(font_text) 323 | age, size = dammit.unicode_markup.split(',')[:-1] 324 | ages.append(age) 325 | sizes.append(size) 326 | # Torrent 327 | href = self.domain + \ 328 | elem.find('a', title=re.compile('magnet'))['href'] 329 | self.hrefs.append(str(href)) 330 | 331 | seeders = [elem.get_text() for elem in self.elements[1]] 332 | leechers = [elem.get_text() for elem in self.elements[2]] 333 | 334 | elif self.page == '1337x': 335 | titles = [elem.get_text() for elem in self.elements[0]] 336 | seeders = [elem.get_text() for elem in self.elements[1]] 337 | leechers = [elem.get_text() for elem in self.elements[2]] 338 | ages = [elem.get_text() for elem in self.elements[3]] 339 | sizes = [elem.get_text('|').split('|')[0] 340 | for elem in self.elements[4]] 341 | 342 | # Torrent 343 | self.hrefs = [self.domain + 344 | elem.find(href=re.compile('torrent'))['href'] 345 | for elem in self.elements[0]] 346 | 347 | elif self.page == 'eztv': 348 | titles = [elem.get_text() for elem in self.elements[0]] 349 | seeders = [elem.get_text() for elem in self.elements[4]] 350 | leechers = ['-' for elem in self.elements[4]] 351 | ages = [elem.get_text() for elem in self.elements[3]] 352 | sizes = [elem.get_text() for elem in self.elements[2]] 353 | 354 | # Magnets 355 | self.hrefs = [elem.find(href=re.compile('magnet'))['href'] 356 | for elem in self.elements[1]] 357 | 358 | elif self.page == 'limetorrents': 359 | titles = [elem.get_text() for elem in self.elements[0]] 360 | seeders = [elem.get_text() for elem in self.elements[3]] 361 | leechers = [elem.get_text() for elem in self.elements[4]] 362 | ages = [elem.get_text() for elem in self.elements[1]] 363 | sizes = [elem.get_text() for elem in self.elements[2]] 364 | 365 | # Magnets 366 | self.hrefs = [elem.find('a', href=re.compile('torrent'))['href'] 367 | for elem in self.elements[0]] 368 | 369 | elif self.page == 'isohunt': 370 | titles = [elem.get_text() for elem in self.elements[0]] 371 | seeders = [elem.get_text() for elem in self.elements[5]] 372 | leechers = ['-' for elem in self.elements[5]] 373 | ages = [elem.get_text() for elem in self.elements[3]] 374 | sizes = [elem.get_text() for elem in self.elements[4]] 375 | 376 | # Torrents 377 | self.hrefs = [self.domain + 378 | elem.find(href=re.compile('torrent_details'))['href'] 379 | for elem in self.elements[0]] 380 | else: 381 | print('Error page') 382 | 383 | self.table = [[Colors.BOLD + 384 | UnicodeDammit(titles[i][:75].strip(), ["utf-8"]).unicode_markup + 385 | Colors.ENDC 386 | if (i + 1) % 2 == 0 387 | else UnicodeDammit( 388 | titles[i][:75].strip()).unicode_markup, 389 | Colors.SEEDER + seeders[i].strip() + Colors.ENDC 390 | if (i + 1) % 2 == 0 391 | else Colors.LGREEN + seeders[i].strip() + Colors.ENDC, 392 | Colors.LEECHER + leechers[i].strip() + Colors.ENDC 393 | if (i + 1) % 2 == 0 394 | else Colors.LRED + leechers[i].strip() + Colors.ENDC, 395 | Colors.LIGHTBLUE + ages[i].strip() + Colors.ENDC 396 | if (i + 1) % 2 == 0 397 | else Colors.BLUE + ages[i].strip() + Colors.ENDC, 398 | Colors.PINK + sizes[i].strip() + Colors.ENDC 399 | if (i + 1) % 2 == 0 400 | else Colors.PURPLE + sizes[i].strip() + Colors.ENDC] 401 | for i in range(len(self.hrefs))] 402 | 403 | print(tabulate(self.table, 404 | headers=headers, 405 | tablefmt='psql', 406 | numalign='right', 407 | stralign='left', 408 | showindex=True)) 409 | 410 | def soupify(self): 411 | """Get proper torrent/magnet information. 412 | 413 | If search_mode is rated then get torrent/magnet. 414 | If not, get all the elements to build the table. 415 | There are different ways for each page. 416 | """ 417 | soup = BeautifulSoup(self.content_page.content, 'lxml') 418 | if self.page == 'torrent_project': 419 | main = soup.find('div', {'id': 'similarfiles'}) 420 | if self.mode_search == 'best_rated': 421 | rated_url = self.domain + \ 422 | main.find(href=re.compile('torrent.html'))['href'] 423 | self.get_magnet(rated_url) 424 | else: 425 | divs = main.find_all('div', limit=30)[2:] 426 | self.elements = list( 427 | zip(*[d.find_all('span', recursive=False) 428 | for d in divs])) # Torrents 429 | 430 | elif self.page == 'the_pirate_bay': 431 | main = soup.find('table', {'id': 'searchResult'}) 432 | if self.mode_search == 'best_rated': 433 | rated_url = self.domain + \ 434 | main.find('a', href=re.compile('torrent'))['href'] 435 | self.get_magnet(rated_url) 436 | else: 437 | trs = main.find_all('tr', limit=30)[1:] 438 | self.elements = list( 439 | zip(*[tr.find_all('td', recursive=False)[1:] 440 | for tr in trs])) # Magnets 441 | 442 | elif self.page == '1337x': 443 | main = soup.find('table', {'class': 'table'}) 444 | if self.mode_search == 'best_rated': 445 | rated_url = self.domain + \ 446 | main.find('a', href=re.compile('torrent'))['href'] 447 | self.get_magnet(rated_url) 448 | else: 449 | trs = main.find_all('tr', limit=30)[1:] 450 | self.elements = list( 451 | zip(*([tr.find_all('td', recursive=False)[:-1] 452 | for tr in trs]))) # Torrents 453 | 454 | elif self.page == 'eztv': 455 | main = soup.find_all('table', {'class': 'forum_header_border'})[2] 456 | if self.mode_search == 'best_rated': 457 | self.magnet = main.find('a', href=re.compile('magnet'))['href'] 458 | else: 459 | trs = main.find_all('tr', limit=30)[2:] 460 | self.elements = list( 461 | zip(*([tr.find_all('td', recursive=False)[1:-1] 462 | for tr in trs]))) # Magnets 463 | 464 | elif self.page == 'limetorrents': 465 | main = soup.find('table', {'class': 'table2'}) 466 | if self.mode_search == 'best_rated': 467 | self.magnet = main.find( 468 | 'a', href=re.compile('torrent'))['href'] 469 | else: 470 | trs = main.find_all('tr', limit=30)[1:] 471 | self.elements = list( 472 | zip(*([tr.find_all('td', recursive=False)[:-1] 473 | for tr in trs]))) # Magnets 474 | 475 | elif self.page == 'isohunt': 476 | main = soup.find('table', {'class': 'table'}) 477 | if self.mode_search == 'best_rated': 478 | rated_url = self.domain + \ 479 | main.find('a', href=re.compile( 480 | 'torrent_details'))['href'] 481 | self.get_magnet(rated_url) 482 | else: 483 | trs = main.find_all('tr', limit=30)[1:-1] 484 | self.elements = list( 485 | zip(*([tr.find_all('td', recursive=False)[1:-1] 486 | for tr in trs]))) # Torrent 487 | else: 488 | print('Cannot soupify current page. Try again.') 489 | 490 | def handle_select(self): 491 | """Handle user's input in list mode.""" 492 | self.selected = input('>> ') 493 | if self.selected in ['Q', 'q']: 494 | sys.exit(1) 495 | elif self.selected in ['B', 'b']: 496 | self.back_to_menu = True 497 | return True 498 | elif is_num(self.selected): 499 | if 0 <= int(self.selected) <= len(self.hrefs) - 1: 500 | self.back_to_menu = False 501 | return True 502 | else: 503 | print(Colors.FAIL + 504 | 'Wrong index. ' + 505 | 'Please select an appropiate one or other option.' + 506 | Colors.ENDC) 507 | return False 508 | else: 509 | print(Colors.FAIL + 510 | 'Invalid input. ' + 511 | 'Please select an appropiate one or other option.' + 512 | Colors.ENDC) 513 | return False 514 | 515 | def select_torrent(self): 516 | """Select torrent. 517 | 518 | First check if specific element/info is obtained in content_page. 519 | Specify to user if it wants best rated torrent or select one from list. 520 | If the user wants best rated: Directly obtain magnet/torrent. 521 | Else: build table with all data and enable the user select the torrent. 522 | """ 523 | try: 524 | self.found_torrents = not bool(self.key_search in 525 | self.content_page.text) 526 | if not self.found_torrents: 527 | print('No torrents found.') 528 | sys.exit(1) 529 | self.soupify() 530 | if self.mode_search == 'list': 531 | self.build_table() 532 | if len(self.hrefs) == 1: 533 | print('Press "0" to download it.') 534 | elif len(self.hrefs) >= 2: 535 | print('\nSelect one of the following torrents. ' + 536 | 'Enter a number between: 0 and ' + 537 | str(len(self.hrefs) - 1)) 538 | 539 | print('If you want to exit write "' + 540 | Colors.LRED + 'Q' + Colors.ENDC + '" or "' + 541 | Colors.LRED + 'q' + Colors.ENDC + '".') 542 | print('If you want to go back to menu and search again write "' + 543 | Colors.LGREEN + 'B' + Colors.ENDC + '" or "' + 544 | Colors.LGREEN + 'b' + Colors.ENDC + '".') 545 | while not(self.picked_choice): 546 | self.picked_choice = self.handle_select() 547 | except Exception: 548 | print('ERROR select_torrent: ') 549 | logging.error(traceback.format_exc()) 550 | sys.exit(0) 551 | 552 | def build_url(self): 553 | """Build appropiate encoded URL. 554 | 555 | This implies the same way of searching a torrent as in the page itself. 556 | """ 557 | url = requests.utils.requote_uri( 558 | self.torrent_page + self.string_search) 559 | if self.page == '1337x': 560 | return(url + '/1/') 561 | elif self.page == 'limetorrents': 562 | return(url + '/') 563 | else: 564 | return(url) 565 | 566 | def get_content(self): 567 | """Get content of the page through url.""" 568 | url = self.build_url() 569 | try: 570 | self.content_page = requests.get(url) 571 | if not(self.content_page.status_code == requests.codes.ok): 572 | self.content_page.raise_for_status() 573 | except requests.exceptions.RequestException as ex: 574 | logging.info('A requests exception has ocurred: ' + str(ex)) 575 | logging.error(traceback.format_exc()) 576 | sys.exit(0) 577 | 578 | 579 | def insert(args): 580 | """Insert args values into instance variables.""" 581 | string_search = args.str_search 582 | mode_search = MODES[args.mode] 583 | page = list(TORRENTS[args.torr_page].keys())[0] 584 | key_search = TORRENTS[args.torr_page][page]['key_search'] 585 | torrent_page = TORRENTS[args.torr_page][page]['page'] 586 | domain = TORRENTS[args.torr_page][page]['domain'] 587 | return([args, string_search, mode_search, page, 588 | key_search, torrent_page, domain]) 589 | 590 | 591 | def initialize(): 592 | """Initialize script.""" 593 | print("Welcome to auto_py_torrent!\n") 594 | 595 | 596 | def run_it(): 597 | """Search and download torrents until the user says it so.""" 598 | initialize() 599 | parser = get_parser() 600 | args = None 601 | first_parse = True 602 | while(True): 603 | if first_parse is True: 604 | first_parse = False 605 | args = parser.parse_args() 606 | else: 607 | print(textwrap.dedent( 608 | '''\ 609 | Search again like in the beginning. 610 | -- You can either choose best rated or list mode. 611 | -- This time, you can insert the search string without double quotes. 612 | Remember the list mode options! 613 | 0: torrent project. 614 | 1: the pirate bay. 615 | 2: 1337x. 616 | 3: eztv. 617 | 4: limetorrents. 618 | 5: isohunt. 619 | ''')) 620 | 621 | print('Or.. if you want to exit just write "' + 622 | Colors.LRED + 'Q' + Colors.ENDC + '" or "' + 623 | Colors.LRED + 'q' + Colors.ENDC + '".') 624 | input_parse = input('>> ').replace("'", "").replace('"', '') 625 | if input_parse in ['Q', 'q']: 626 | sys.exit(1) 627 | 628 | args = parser.parse_args(input_parse.split(' ', 2)) 629 | 630 | if args.str_search.strip() == "": 631 | print('Please insert an appropiate non-empty string.') 632 | else: 633 | auto = AutoPy(*insert(args)) 634 | auto.get_content() 635 | auto.select_torrent() 636 | auto.download_torrent() 637 | 638 | 639 | def main(): 640 | """Entry point for app script.""" 641 | try: 642 | run_it() 643 | except KeyboardInterrupt: 644 | print('\nSee you the next time.') 645 | except Exception: 646 | logging.error(traceback.format_exc()) 647 | 648 | 649 | if __name__ == '__main__': 650 | main() 651 | -------------------------------------------------------------------------------- /docs/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=2.18.1 2 | beautifulsoup4>=4 3 | tabulate>=0.7.7 4 | coloredlogs>=7.1 5 | lxml 6 | 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | 6 | 7 | from codecs import open 8 | 9 | from setuptools import setup 10 | 11 | 12 | here = os.path.abspath(os.path.dirname(__file__)) 13 | 14 | if sys.argv[-1] == 'publish': 15 | os.system('python3 setup.py sdist bdist_wheel') 16 | os.system('twine upload dist/*') 17 | sys.exit() 18 | 19 | packages = ['auto_py_torrent'] 20 | 21 | requires = [ 22 | 'requests>=2.18.1', 23 | 'beautifulsoup4>=4', 24 | 'tabulate>=0.7.7', 25 | 'coloredlogs>=7.1', 26 | 'lxml' 27 | ] 28 | 29 | about = {} 30 | with open(os.path.join(here, 'auto_py_torrent', '__version__.py'), 'r', 'utf-8') as f: 31 | exec(f.read(), about) 32 | 33 | with open('README.rst', 'r', 'utf-8') as f: 34 | readme = f.read() 35 | with open('HISTORY.rst', 'r', 'utf-8') as f: 36 | history = f.read() 37 | 38 | 39 | setup( 40 | name=about['__title__'], 41 | version=about['__version__'], 42 | description=about['__description__'], 43 | long_description=readme + '\n\n' + history, 44 | author=about['__author__'], 45 | author_email=about['__author_email__'], 46 | url=about['__url__'], 47 | packages=packages, 48 | python_requires='>=3', 49 | install_requires=requires, 50 | license=about['__license__'], 51 | classifiers=( 52 | 'Development Status :: 3 - Alpha', 53 | 'Environment :: Console', 54 | 'Intended Audience :: End Users/Desktop', 55 | 'Intended Audience :: Developers', 56 | 'License :: OSI Approved :: MIT License', 57 | 'Natural Language :: English', 58 | 'Programming Language :: Python :: 3', 59 | 'Programming Language :: Python :: 3.3', 60 | 'Programming Language :: Python :: 3.4', 61 | 'Programming Language :: Python :: 3.5', 62 | 'Programming Language :: Python :: 3.6', 63 | 'Topic :: Terminals', 64 | ), 65 | keywords='automate torrent torrent downloads', 66 | entry_points={ 67 | 'console_scripts': ['auto_py_torrent=auto_py_torrent.auto_py_torrent:main'], 68 | } 69 | ) 70 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ocslegna/auto_py_torrent/5950c8791acebafc0126f3440796065c9e066dee/tests/__init__.py --------------------------------------------------------------------------------