everything works, needs makeup though
This commit is contained in:
		
							parent
							
								
									5130450355
								
							
						
					
					
						commit
						464683e9ce
					
				
					 14 changed files with 377 additions and 0 deletions
				
			
		
							
								
								
									
										10
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| # python generated files | ||||
| __pycache__/ | ||||
| *.py[oc] | ||||
| build/ | ||||
| dist/ | ||||
| wheels/ | ||||
| *.egg-info | ||||
| 
 | ||||
| # venv | ||||
| .venv | ||||
							
								
								
									
										1
									
								
								.python-version
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.python-version
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| 3.12.5 | ||||
							
								
								
									
										3
									
								
								README.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								README.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| # torrent-downloader | ||||
| 
 | ||||
| Search TPB and download torrents with transmission \o/ | ||||
							
								
								
									
										50
									
								
								pyproject.toml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								pyproject.toml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,50 @@ | |||
| [project] | ||||
| name = "torrent-downloader" | ||||
| version = "0.1.0" | ||||
| description = "Add your description here" | ||||
| authors = [ | ||||
|     { name = "Vladan Popovic", email = "vladanovic@gmail.com" } | ||||
| ] | ||||
| dependencies = [ | ||||
|     "tpblite>=0.8.0", | ||||
|     "transmission-rpc>=7.0.11", | ||||
|     "pydantic-settings>=2.4.0", | ||||
|     "fastapi>=0.114.2", | ||||
|     "jinja2>=3.1.4", | ||||
|     "cinemagoer>=2023.5.1", | ||||
| ] | ||||
| readme = "README.md" | ||||
| requires-python = ">= 3.8" | ||||
| 
 | ||||
| [build-system] | ||||
| requires = ["hatchling"] | ||||
| build-backend = "hatchling.build" | ||||
| 
 | ||||
| [tool.rye] | ||||
| managed = true | ||||
| dev-dependencies = [ | ||||
|     "uvicorn>=0.30.6", | ||||
| ] | ||||
| 
 | ||||
| [tool.hatch.metadata] | ||||
| allow-direct-references = true | ||||
| 
 | ||||
| [tool.hatch.build.targets.wheel] | ||||
| packages = ["src/torrent_downloader"] | ||||
| 
 | ||||
| [tool.rye.scripts] | ||||
| client = "python -m torrent_downloader" | ||||
| server = "python -m torrent_downloader.server" | ||||
| 
 | ||||
| [tool.pyright] | ||||
| venv = ".venv" | ||||
| venvPath = "." | ||||
| 
 | ||||
| [tool.ruff] | ||||
| line-length = 100 | ||||
| indent-width = 4 | ||||
| 
 | ||||
| [tool.ruff.format] | ||||
| quote-style = "double" | ||||
| indent-style = "space" | ||||
| skip-magic-trailing-comma = false | ||||
							
								
								
									
										70
									
								
								requirements-dev.lock
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								requirements-dev.lock
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,70 @@ | |||
| # generated by rye | ||||
| # use `rye lock` or `rye sync` to update this lockfile | ||||
| # | ||||
| # last locked with the following flags: | ||||
| #   pre: false | ||||
| #   features: [] | ||||
| #   all-features: false | ||||
| #   with-sources: false | ||||
| #   generate-hashes: false | ||||
| #   universal: false | ||||
| 
 | ||||
| -e file:. | ||||
| annotated-types==0.7.0 | ||||
|     # via pydantic | ||||
| anyio==4.4.0 | ||||
|     # via starlette | ||||
| certifi==2024.8.30 | ||||
|     # via requests | ||||
| charset-normalizer==3.3.2 | ||||
|     # via requests | ||||
| cinemagoer==2023.5.1 | ||||
|     # via torrent-downloader | ||||
| click==8.1.7 | ||||
|     # via uvicorn | ||||
| fastapi==0.114.2 | ||||
|     # via torrent-downloader | ||||
| greenlet==3.1.0 | ||||
|     # via sqlalchemy | ||||
| h11==0.14.0 | ||||
|     # via uvicorn | ||||
| idna==3.8 | ||||
|     # via anyio | ||||
|     # via requests | ||||
| jinja2==3.1.4 | ||||
|     # via torrent-downloader | ||||
| lxml==5.3.0 | ||||
|     # via cinemagoer | ||||
|     # via tpblite | ||||
| markupsafe==2.1.5 | ||||
|     # via jinja2 | ||||
| pydantic==2.8.2 | ||||
|     # via fastapi | ||||
|     # via pydantic-settings | ||||
| pydantic-core==2.20.1 | ||||
|     # via pydantic | ||||
| pydantic-settings==2.4.0 | ||||
|     # via torrent-downloader | ||||
| python-dotenv==1.0.1 | ||||
|     # via pydantic-settings | ||||
| requests==2.32.3 | ||||
|     # via transmission-rpc | ||||
| sniffio==1.3.1 | ||||
|     # via anyio | ||||
| sqlalchemy==2.0.34 | ||||
|     # via cinemagoer | ||||
| starlette==0.38.5 | ||||
|     # via fastapi | ||||
| tpblite==0.8.0 | ||||
|     # via torrent-downloader | ||||
| transmission-rpc==7.0.11 | ||||
|     # via torrent-downloader | ||||
| typing-extensions==4.12.2 | ||||
|     # via fastapi | ||||
|     # via pydantic | ||||
|     # via pydantic-core | ||||
|     # via sqlalchemy | ||||
|     # via transmission-rpc | ||||
| urllib3==2.2.2 | ||||
|     # via requests | ||||
| uvicorn==0.30.6 | ||||
							
								
								
									
										65
									
								
								requirements.lock
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								requirements.lock
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,65 @@ | |||
| # generated by rye | ||||
| # use `rye lock` or `rye sync` to update this lockfile | ||||
| # | ||||
| # last locked with the following flags: | ||||
| #   pre: false | ||||
| #   features: [] | ||||
| #   all-features: false | ||||
| #   with-sources: false | ||||
| #   generate-hashes: false | ||||
| #   universal: false | ||||
| 
 | ||||
| -e file:. | ||||
| annotated-types==0.7.0 | ||||
|     # via pydantic | ||||
| anyio==4.4.0 | ||||
|     # via starlette | ||||
| certifi==2024.8.30 | ||||
|     # via requests | ||||
| charset-normalizer==3.3.2 | ||||
|     # via requests | ||||
| cinemagoer==2023.5.1 | ||||
|     # via torrent-downloader | ||||
| fastapi==0.114.2 | ||||
|     # via torrent-downloader | ||||
| greenlet==3.1.0 | ||||
|     # via sqlalchemy | ||||
| idna==3.8 | ||||
|     # via anyio | ||||
|     # via requests | ||||
| jinja2==3.1.4 | ||||
|     # via torrent-downloader | ||||
| lxml==5.3.0 | ||||
|     # via cinemagoer | ||||
|     # via tpblite | ||||
| markupsafe==2.1.5 | ||||
|     # via jinja2 | ||||
| pydantic==2.8.2 | ||||
|     # via fastapi | ||||
|     # via pydantic-settings | ||||
| pydantic-core==2.20.1 | ||||
|     # via pydantic | ||||
| pydantic-settings==2.4.0 | ||||
|     # via torrent-downloader | ||||
| python-dotenv==1.0.1 | ||||
|     # via pydantic-settings | ||||
| requests==2.32.3 | ||||
|     # via transmission-rpc | ||||
| sniffio==1.3.1 | ||||
|     # via anyio | ||||
| sqlalchemy==2.0.34 | ||||
|     # via cinemagoer | ||||
| starlette==0.38.5 | ||||
|     # via fastapi | ||||
| tpblite==0.8.0 | ||||
|     # via torrent-downloader | ||||
| transmission-rpc==7.0.11 | ||||
|     # via torrent-downloader | ||||
| typing-extensions==4.12.2 | ||||
|     # via fastapi | ||||
|     # via pydantic | ||||
|     # via pydantic-core | ||||
|     # via sqlalchemy | ||||
|     # via transmission-rpc | ||||
| urllib3==2.2.2 | ||||
|     # via requests | ||||
							
								
								
									
										2
									
								
								src/torrent_downloader/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/torrent_downloader/__init__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | |||
| def hello() -> str: | ||||
|     return "Hello from torrent-downloader!" | ||||
							
								
								
									
										3
									
								
								src/torrent_downloader/__main__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/torrent_downloader/__main__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| from .client import main | ||||
| 
 | ||||
| main() | ||||
							
								
								
									
										48
									
								
								src/torrent_downloader/app.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/torrent_downloader/app.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,48 @@ | |||
| import urllib.parse | ||||
| import transmission_rpc | ||||
| from fastapi import Depends, FastAPI, Request | ||||
| from fastapi.responses import HTMLResponse | ||||
| from fastapi.templating import Jinja2Templates | ||||
| 
 | ||||
| from torrent_downloader.client import TorrentDownloader | ||||
| 
 | ||||
| app = FastAPI() | ||||
| templates = Jinja2Templates(directory="templates") | ||||
| 
 | ||||
| 
 | ||||
| def get_downloader() -> TorrentDownloader: | ||||
|     return TorrentDownloader() | ||||
| 
 | ||||
| 
 | ||||
| def get_active_torrents(downloader=get_downloader()) -> list[transmission_rpc.Torrent]: | ||||
|     return [t for t in downloader.get_active_torrents() if t.format_eta() != "not available"] | ||||
| 
 | ||||
| 
 | ||||
| @app.get("/", response_class=HTMLResponse) | ||||
| async def index(request: Request): | ||||
|     return templates.TemplateResponse(request=request, name="index.html") | ||||
| 
 | ||||
| 
 | ||||
| @app.get("/list", response_class=HTMLResponse) | ||||
| async def list_torrents(request: Request, query: str, downloader=Depends(get_downloader)): | ||||
|     torrents = downloader.search(query) | ||||
|     return templates.TemplateResponse( | ||||
|         request=request, name="torrents.html", context={"torrents": torrents} | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| @app.get("/download", response_class=HTMLResponse) | ||||
| async def download(request: Request, downloader=Depends(get_downloader)): | ||||
|     magnet = urllib.parse.unquote(str(request.query_params)) | ||||
|     downloader.download_magnet(magnet) | ||||
|     active = downloader.get_active_torrents() | ||||
|     return templates.TemplateResponse( | ||||
|         request=request, name="active_torrents.html", context={"torrents": active} | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| @app.get("/active", response_class=HTMLResponse) | ||||
| async def active(request: Request, active=Depends(get_active_torrents)): | ||||
|     return templates.TemplateResponse( | ||||
|         request=request, name="active_torrents.html", context={"torrents": active} | ||||
|     ) | ||||
							
								
								
									
										71
									
								
								src/torrent_downloader/client.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/torrent_downloader/client.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,71 @@ | |||
| from pydantic import SecretStr | ||||
| from pydantic_settings import BaseSettings, SettingsConfigDict | ||||
| from tpblite import CATEGORIES, ORDERS, TPB | ||||
| from tpblite.models.torrents import Torrent as TpbTorrent, Torrents | ||||
| import transmission_rpc | ||||
| 
 | ||||
| 
 | ||||
| class Settings(BaseSettings): | ||||
|     model_config = SettingsConfigDict(env_prefix="TD_", case_sensitive=False) | ||||
| 
 | ||||
|     tpb_site: str = "https://tpb.party" | ||||
|     transmission_host: str = "192.168.0.29" | ||||
|     transmission_port: int = 9091 | ||||
|     transmission_user: str = "transmission" | ||||
|     transmission_pass: SecretStr = SecretStr("1304c2ea35e4e3f685691998c67ab58e20cf5d29CLC0a8Wn") | ||||
| 
 | ||||
| 
 | ||||
| class TorrentDownloader: | ||||
|     def __init__(self, config: Settings = Settings()): | ||||
|         self.tpb = TPB(config.tpb_site) | ||||
|         self.transmission = transmission_rpc.Client( | ||||
|             host=config.transmission_host, | ||||
|             port=config.transmission_port, | ||||
|             username=config.transmission_user, | ||||
|             password=config.transmission_pass.get_secret_value(), | ||||
|         ) | ||||
| 
 | ||||
|     def search(self, term: str, category: int = CATEGORIES.ALL) -> Torrents: | ||||
|         return self.tpb.search( | ||||
|             term, | ||||
|             page=1, | ||||
|             order=ORDERS.SEEDERS.DES, | ||||
|             category=category, | ||||
|         ) | ||||
| 
 | ||||
|     def download(self, torrent: TpbTorrent): | ||||
|         if torrent is not None and torrent.magnetlink: | ||||
|             self.transmission.add_torrent(torrent.magnetlink) | ||||
| 
 | ||||
|     def download_magnet(self, magnetlink: str): | ||||
|         self.transmission.add_torrent(magnetlink) | ||||
| 
 | ||||
|     def get_active_torrents(self) -> list[transmission_rpc.Torrent]: | ||||
|         active, _ = self.transmission.get_recently_active_torrents() | ||||
|         return active | ||||
| 
 | ||||
| 
 | ||||
| def main(): | ||||
|     config = Settings() | ||||
|     td = TorrentDownloader(config) | ||||
| 
 | ||||
|     initial = input("Search torrent: ") | ||||
|     search_result: Torrents = td.search(initial, CATEGORIES.ALL) | ||||
| 
 | ||||
|     for idx, item in enumerate(search_result): | ||||
|         print(f"{idx+1}. {item}") | ||||
| 
 | ||||
|     try: | ||||
|         choice = int(input("Choose your destiny: ")) | ||||
|         if 30 > int(choice) > 0: | ||||
|             torrent = search_result[choice - 1] | ||||
|         else: | ||||
|             print("Wrong destiny :( getting best torrent by hand") | ||||
|             torrent = search_result.getBestTorrent(min_seeds=10) | ||||
|     except Exception as _: | ||||
|         torrent = search_result.getBestTorrent(min_seeds=10) | ||||
| 
 | ||||
|     if torrent is not None: | ||||
|         td.download(torrent) | ||||
|     else: | ||||
|         print("Cannot find good enough torrent :D") | ||||
							
								
								
									
										4
									
								
								src/torrent_downloader/server.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/torrent_downloader/server.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| import uvicorn | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     uvicorn.run("torrent_downloader.app:app", host="0.0.0.0", port=8000, reload=True) | ||||
							
								
								
									
										12
									
								
								templates/active_torrents.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								templates/active_torrents.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | |||
| {% extends 'index.html' %} | ||||
| 
 | ||||
| {% block content %} | ||||
| <ul> | ||||
| {% for torrent in torrents %} | ||||
| <li> | ||||
|     <span>{{ torrent.name }}</span> | ||||
|     <span>{{ torrent.format_eta() }}</span> | ||||
| </li> | ||||
| {% endfor %} | ||||
| </ul> | ||||
| {% endblock %} | ||||
							
								
								
									
										23
									
								
								templates/index.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								templates/index.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | |||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
|   <meta charset="UTF-8"> | ||||
|   <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|   <title>Search and download torrents directly to mediacenter</title> | ||||
|   <script src="https://unpkg.com/htmx.org@2.0.0" integrity="sha384-wS5l5IKJBvK6sPTKa2WZ1js3d947pvWXbPJ1OmWfEuxLgeHcEbjUUA5i9V5ZkpCw" crossorigin="anonymous"></script> | ||||
| </head> | ||||
| <body> | ||||
|     <div id="content"> | ||||
|     <menu> | ||||
|         <li hx-get="/" hx-replace-url="/" hx-target="#content">Search</li> | ||||
|         <li hx-get="/active" hx-replace-url="/active" hx-target="#content">Active downloads</li> | ||||
|     </menu> | ||||
|         {% block content %} | ||||
|         <form hx-get="/list" hx-replace-url="true" hx-target="#content"> | ||||
|             <input type=text name=query /> | ||||
|             <button type=submit>search</button> | ||||
|         </form> | ||||
|         {% endblock %} | ||||
|     </div> | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										15
									
								
								templates/torrents.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								templates/torrents.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| {% extends 'index.html' %} | ||||
| 
 | ||||
| {% block content %} | ||||
| <ul> | ||||
| {% for torrent in torrents %} | ||||
| <li hx-get="/download?{{ torrent.magnetlink | safe }}" hx-target="#content" hx-replace-url="/active"> | ||||
|     <span>{{ torrent.title }}</span> | ||||
|     <span>({{ torrent.seeds }})</span> | ||||
|     <span>({{ torrent.leeches }})</span> | ||||
|     <span>({{ torrent.filesize }})</span> | ||||
|     <span>{{ torrent.category }}</span> | ||||
| </li> | ||||
| {% endfor %} | ||||
| </ul> | ||||
| {% endblock %} | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue