├── .gitignore ├── requirements.txt ├── README.md └── xtractor.py /.gitignore: -------------------------------------------------------------------------------- 1 | /venv 2 | *.txt 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.32.2 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Xtractor 2 | 3 | Xtractor is a simple script written in python to **bulk extract download links** for TV series from [`todaytvseries.com`](https://www.todaytvseries1.com). This allows you to avoid link shortener ads and use download managers like [**IDM**](https://www.internetdownloadmanager.com) or [**FDM**](https://www.freedownloadmanager.org) to download episodes in bulk instead of one by one. 4 | 5 | ## Why should i use Xtractor 6 | 7 | - [x] It's easy to use. 8 | - [x] You can skip annoying link shorteners. 9 | - [x] You can extract download links of an entire season at once. Therefore you don't have to waste your time on downloading episodes one by one. 10 | 11 | ## Git Installation 12 | ``` 13 | # clone the repo 14 | $ git clone https://github.com/sameera-madushan/Xtractor.git 15 | 16 | # change the working directory to Xtractor 17 | $ cd Xtractor 18 | 19 | # install the requirements 20 | $ pip3 install -r requirements.txt 21 | ``` 22 | 23 | > You can also download the compiled version from [Releases tab](https://github.com/sameera-madushan/Xtractor/releases) 24 | 25 | ## Usage 26 | ``` 27 | python xtractor.py 28 | ``` -------------------------------------------------------------------------------- /xtractor.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | import re 4 | 5 | BANNER = r''' 6 | ____ ___ __ __ 7 | \ \/ // |_____________ _____/ |_ ___________ 8 | \ /\ __\_ __ \__ \ _/ ___\ __\/ _ \_ __ \ 9 | / \ | | | | \// __ \\ \___| | ( <_> ) | \/ 10 | /___/\ \|__| |__| (____ /\___ >__| \____/|__| 11 | \_/ \/ \/ 12 | [@Sameera Madushan] 13 | ''' 14 | 15 | URL_PATTERN = re.compile( 16 | r'^https?://(?:www\.)?(?:[^/]+\.)*todaytvseries\d+\.com' 17 | ) 18 | 19 | TITLE_PATTERN = re.compile( 20 | r'uk-article-title uk-badge1">(.*?)' 30 | r'
\d+ Mb
' 31 | r'
str: 37 | return re.sub(r'[\\/:*?"<>|]', '', name).strip() 38 | 39 | 40 | def main(): 41 | print(BANNER) 42 | 43 | while True: 44 | url = input("\nEnter TV series URL (or 'q' to quit): ").strip() 45 | 46 | if url.lower() in ("q", "quit"): 47 | break 48 | 49 | if not URL_PATTERN.match(url): 50 | print("Invalid todaytvseries domain.") 51 | continue 52 | 53 | try: 54 | resp = requests.get(url, timeout=10) 55 | resp.raise_for_status() 56 | html = resp.text 57 | except requests.RequestException as e: 58 | print(f"Request failed: {e}") 59 | continue 60 | 61 | title_match = TITLE_PATTERN.search(html) 62 | if not title_match: 63 | print("Could not extract series title.") 64 | continue 65 | 66 | title = clean_filename(title_match.group(1)) 67 | seasons = sorted(set(SEASON_PATTERN.findall(html)), key=int) 68 | 69 | while True: 70 | print(f"\nAvailable seasons for: {title}") 71 | for s in seasons: 72 | print(f"Season {s}") 73 | 74 | print("\nOptions:") 75 | print(" Enter season numbers (e.g. 1 or 1 2 3)") 76 | print(" Enter 'all' to download ALL seasons") 77 | print(" Enter 'b' to go back (home)") 78 | print(" Enter 'q' to quit") 79 | 80 | choice = input("\nYour choice: ").strip().lower() 81 | 82 | if choice in ("q", "quit"): 83 | return 84 | 85 | if choice in ("b", "back"): 86 | break 87 | 88 | if choice == "all": 89 | selected_seasons = seasons 90 | filename = f"{title} - All Seasons.txt" 91 | 92 | with open(filename, "w", encoding="utf-8") as f: 93 | for season in selected_seasons: 94 | season_str = str(season).zfill(2) 95 | f.write(f"{title} - Season {season_str}\n") 96 | 97 | found = False 98 | for ep, link in LINK_PATTERN.findall(html): 99 | if ep.startswith(f"S{season_str}"): 100 | f.write(link + "\n") 101 | found = True 102 | 103 | if not found: 104 | f.write("No links found\n") 105 | f.write("\n") 106 | 107 | print(f'Saved: "{filename}"') 108 | 109 | else: 110 | selected_seasons = choice.split() 111 | if not all(s in seasons for s in selected_seasons): 112 | print("Invalid season selection.") 113 | continue 114 | 115 | for season in selected_seasons: 116 | season_str = str(season).zfill(2) 117 | filename = f"{title} - Season {season_str}.txt" 118 | 119 | with open(filename, "w", encoding="utf-8") as f: 120 | f.write(f"{title} - Season {season_str}\n\n") 121 | 122 | found = False 123 | for ep, link in LINK_PATTERN.findall(html): 124 | if ep.startswith(f"S{season_str}"): 125 | f.write(link + "\n") 126 | found = True 127 | 128 | if not found: 129 | f.write("No links found\n") 130 | 131 | print(f'Saved: "{filename}"') 132 | 133 | print("\nWhat next?") 134 | print(" 1 → Get more links from THIS series") 135 | print(" 2 → Go home (new TV series URL)") 136 | print(" q → Quit") 137 | 138 | next_action = input("\nChoose: ").strip().lower() 139 | 140 | if next_action == "1": 141 | continue 142 | elif next_action == "2": 143 | break 144 | elif next_action in ("q", "quit"): 145 | return 146 | else: 147 | print("Invalid choice. Returning to series menu.") 148 | 149 | if __name__ == "__main__": 150 | try: 151 | main() 152 | except KeyboardInterrupt: 153 | print("\nProgramme Interrupted") 154 | time.sleep(1) 155 | --------------------------------------------------------------------------------