bt1337xearch 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: bt1337xearch
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Better search for 1337x[.]to with basic filters
|
|
5
|
+
Author: SimoneFelici
|
|
6
|
+
Author-email: SimoneFelici <felicisimone9@gmail.com>
|
|
7
|
+
Requires-Dist: scrapling[fetchers]>=0.3.8
|
|
8
|
+
Requires-Dist: textual>=6.5.0
|
|
9
|
+
Requires-Python: >=3.14
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# bt1337xearch
|
|
13
|
+
|
|
14
|
+
Better search for 1337x[.]to with basic filters
|
|
15
|
+
|
|
16
|
+
## What it does
|
|
17
|
+
|
|
18
|
+
bt1337xearch is a command-line tool that searches 1337x.to torrents with:
|
|
19
|
+
- Category filtering (Movies, TV, Games, etc.)
|
|
20
|
+
- Sorting options (by time, size, seeders, leechers)
|
|
21
|
+
- Keyword filtering (include/exclude keywords)
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
```bash
|
|
25
|
+
uv tool install bt1337xearch
|
|
26
|
+
```
|
|
27
|
+
or
|
|
28
|
+
```bash
|
|
29
|
+
git clone https://github.com/SimoneFelici/bt1337xearch.git
|
|
30
|
+
cd bt1337xearch
|
|
31
|
+
uv tool install .
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### Basic search (Probably broken)
|
|
37
|
+
```bash
|
|
38
|
+
bt1337xearch -n "Dexter"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Search with category (Recommended)
|
|
42
|
+
```bash
|
|
43
|
+
bt1337xearch -n "Lupin" -c ANIME
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Search with sorting
|
|
47
|
+
```bash
|
|
48
|
+
bt1337xearch -n "Alien" -c MOVIE -s TIME -o DESC
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Advanced filtering
|
|
52
|
+
Use `+` to include words and `~` to exclude words:
|
|
53
|
+
```bash
|
|
54
|
+
# Include only results with "1080p" or "BluRay"
|
|
55
|
+
bt1337xearch -n "Dexter" -f +1080p +BluRay
|
|
56
|
+
|
|
57
|
+
# Exclude results with "CAM" or "TS"
|
|
58
|
+
bt1337xearch -n "Movie" -f ~CAM ~TS
|
|
59
|
+
|
|
60
|
+
# Combine include and exclude
|
|
61
|
+
bt1337xearch -n "Dexter" -c TV -f +1080p "~x265 HEVC" ~CAM
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Options
|
|
65
|
+
|
|
66
|
+
| Option | Description | Choices |
|
|
67
|
+
|--------|-------------|---------|
|
|
68
|
+
| `-n, --name` | Name of the media (required) | - |
|
|
69
|
+
| `-c, --category` | Filter by category | MOVIE, TV, GAME, MUSIC, APP, DOCU, ANIME, OTHER, XXX |
|
|
70
|
+
| `-s, --sort` | Sort results by | TIME, SIZE, SEED, LEECH |
|
|
71
|
+
| `-o, --order` | Sort order (default: DESC) | ASC, DESC |
|
|
72
|
+
| `-f, --filter` | Filter by keywords | Use `+word` to include, `~word` to exclude |
|
|
73
|
+
|
|
74
|
+
## Examples
|
|
75
|
+
```bash
|
|
76
|
+
# Search for Dexter TV series, sorted by seeders
|
|
77
|
+
bt1337xearch -n Dexter -c TV -s SEED
|
|
78
|
+
|
|
79
|
+
# Search for movies with "1080p", exclude "CAM" releases
|
|
80
|
+
bt1337xearch -n "Angel's Egg" -c MOVIE -s TIME -o DESC -f +1080p '~CAM'
|
|
81
|
+
|
|
82
|
+
# Search for games, sorted by upload time
|
|
83
|
+
bt1337xearch -n "Peak" -c GAME -s TIME -o ASC
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Output
|
|
87
|
+
|
|
88
|
+
For each result, the tool displays:
|
|
89
|
+
- Name
|
|
90
|
+
- Seeders
|
|
91
|
+
- Leechers
|
|
92
|
+
- Upload date
|
|
93
|
+
- File size
|
|
94
|
+
- Uploader
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# bt1337xearch
|
|
2
|
+
|
|
3
|
+
Better search for 1337x[.]to with basic filters
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
bt1337xearch is a command-line tool that searches 1337x.to torrents with:
|
|
8
|
+
- Category filtering (Movies, TV, Games, etc.)
|
|
9
|
+
- Sorting options (by time, size, seeders, leechers)
|
|
10
|
+
- Keyword filtering (include/exclude keywords)
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
```bash
|
|
14
|
+
uv tool install bt1337xearch
|
|
15
|
+
```
|
|
16
|
+
or
|
|
17
|
+
```bash
|
|
18
|
+
git clone https://github.com/SimoneFelici/bt1337xearch.git
|
|
19
|
+
cd bt1337xearch
|
|
20
|
+
uv tool install .
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### Basic search (Probably broken)
|
|
26
|
+
```bash
|
|
27
|
+
bt1337xearch -n "Dexter"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Search with category (Recommended)
|
|
31
|
+
```bash
|
|
32
|
+
bt1337xearch -n "Lupin" -c ANIME
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Search with sorting
|
|
36
|
+
```bash
|
|
37
|
+
bt1337xearch -n "Alien" -c MOVIE -s TIME -o DESC
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Advanced filtering
|
|
41
|
+
Use `+` to include words and `~` to exclude words:
|
|
42
|
+
```bash
|
|
43
|
+
# Include only results with "1080p" or "BluRay"
|
|
44
|
+
bt1337xearch -n "Dexter" -f +1080p +BluRay
|
|
45
|
+
|
|
46
|
+
# Exclude results with "CAM" or "TS"
|
|
47
|
+
bt1337xearch -n "Movie" -f ~CAM ~TS
|
|
48
|
+
|
|
49
|
+
# Combine include and exclude
|
|
50
|
+
bt1337xearch -n "Dexter" -c TV -f +1080p "~x265 HEVC" ~CAM
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Options
|
|
54
|
+
|
|
55
|
+
| Option | Description | Choices |
|
|
56
|
+
|--------|-------------|---------|
|
|
57
|
+
| `-n, --name` | Name of the media (required) | - |
|
|
58
|
+
| `-c, --category` | Filter by category | MOVIE, TV, GAME, MUSIC, APP, DOCU, ANIME, OTHER, XXX |
|
|
59
|
+
| `-s, --sort` | Sort results by | TIME, SIZE, SEED, LEECH |
|
|
60
|
+
| `-o, --order` | Sort order (default: DESC) | ASC, DESC |
|
|
61
|
+
| `-f, --filter` | Filter by keywords | Use `+word` to include, `~word` to exclude |
|
|
62
|
+
|
|
63
|
+
## Examples
|
|
64
|
+
```bash
|
|
65
|
+
# Search for Dexter TV series, sorted by seeders
|
|
66
|
+
bt1337xearch -n Dexter -c TV -s SEED
|
|
67
|
+
|
|
68
|
+
# Search for movies with "1080p", exclude "CAM" releases
|
|
69
|
+
bt1337xearch -n "Angel's Egg" -c MOVIE -s TIME -o DESC -f +1080p '~CAM'
|
|
70
|
+
|
|
71
|
+
# Search for games, sorted by upload time
|
|
72
|
+
bt1337xearch -n "Peak" -c GAME -s TIME -o ASC
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Output
|
|
76
|
+
|
|
77
|
+
For each result, the tool displays:
|
|
78
|
+
- Name
|
|
79
|
+
- Seeders
|
|
80
|
+
- Leechers
|
|
81
|
+
- Upload date
|
|
82
|
+
- File size
|
|
83
|
+
- Uploader
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "bt1337xearch"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Better search for 1337x[.]to with basic filters"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "SimoneFelici", email = "felicisimone9@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.14"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"scrapling[fetchers]>=0.3.8",
|
|
12
|
+
"textual>=6.5.0",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
bt1337xearch = "bt1337xearch:main"
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["uv_build>=0.9.7,<0.10.0"]
|
|
20
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from scrapling.fetchers import Fetcher
|
|
3
|
+
import argparse
|
|
4
|
+
import logging
|
|
5
|
+
from curl_cffi.requests.exceptions import SessionClosed
|
|
6
|
+
from textual.app import App, ComposeResult
|
|
7
|
+
from textual.widgets import Header, Footer, Static, Label, LoadingIndicator
|
|
8
|
+
from textual.containers import VerticalScroll, Vertical
|
|
9
|
+
from textual.binding import Binding
|
|
10
|
+
import threading
|
|
11
|
+
|
|
12
|
+
Fetcher.adaptive = True
|
|
13
|
+
logging.getLogger('scrapling').setLevel(logging.WARNING)
|
|
14
|
+
logging.getLogger('scrapling').setLevel(logging.CRITICAL)
|
|
15
|
+
|
|
16
|
+
roles = [
|
|
17
|
+
"user",
|
|
18
|
+
'uploader',
|
|
19
|
+
"vip",
|
|
20
|
+
"trial-uploader"
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
class ResultWidget(Static):
|
|
24
|
+
def __init__(self, result: dict, **kwargs):
|
|
25
|
+
super().__init__(**kwargs)
|
|
26
|
+
self.result = result
|
|
27
|
+
|
|
28
|
+
def compose(self) -> ComposeResult:
|
|
29
|
+
yield Label(f"[bold cyan]{self.result['name']}[/]")
|
|
30
|
+
yield Label(f"[yellow]Link:[/]")
|
|
31
|
+
yield Label(f"{self.result['link']}")
|
|
32
|
+
yield Label(f"[green]Seeds:[/] {self.result['seeds']} | [red]Leeches:[/] {self.result['leeches']}")
|
|
33
|
+
yield Label(f"[blue]Size:[/] {self.result['size']} | [magenta]Date:[/] {self.result['date']}")
|
|
34
|
+
yield Label(f"[dim]Uploader: {self.result['uploader']}[/]")
|
|
35
|
+
yield Label("[dim]─" * 50 + "[/]")
|
|
36
|
+
|
|
37
|
+
class MyApp(App):
|
|
38
|
+
TITLE = "bt1337xearch"
|
|
39
|
+
|
|
40
|
+
BINDINGS = [
|
|
41
|
+
Binding("left,h", "prev_page", "Previous 5", show=True),
|
|
42
|
+
Binding("right,l", "next_page", "Next 5", show=True),
|
|
43
|
+
Binding("q", "quit", "Esci", show=True),
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
def __init__(self, kitchen, **kwargs):
|
|
47
|
+
super().__init__(**kwargs)
|
|
48
|
+
self.kitchen = kitchen
|
|
49
|
+
self.all_results = []
|
|
50
|
+
self.current_page = 0
|
|
51
|
+
self.results_per_page = 5
|
|
52
|
+
self.fetched_pages = set()
|
|
53
|
+
self.fetching_pages = set()
|
|
54
|
+
self.max_page_number = None
|
|
55
|
+
self.lock = threading.Lock()
|
|
56
|
+
self.is_fetching = False
|
|
57
|
+
|
|
58
|
+
def compose(self) -> ComposeResult:
|
|
59
|
+
yield Header()
|
|
60
|
+
with Vertical(id="results-container"):
|
|
61
|
+
yield LoadingIndicator(id="loading")
|
|
62
|
+
yield Static(id="status")
|
|
63
|
+
yield Footer()
|
|
64
|
+
|
|
65
|
+
def on_mount(self):
|
|
66
|
+
self.update_status("Loading...")
|
|
67
|
+
self.fetch_page_async(1)
|
|
68
|
+
|
|
69
|
+
def fetch_page_async(self, page_number: int):
|
|
70
|
+
if page_number in self.fetched_pages or page_number in self.fetching_pages:
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
if self.max_page_number and page_number > self.max_page_number:
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
self.fetching_pages.add(page_number)
|
|
77
|
+
thread = threading.Thread(
|
|
78
|
+
target=self.fetch_page,
|
|
79
|
+
args=(page_number,),
|
|
80
|
+
daemon=True,
|
|
81
|
+
name=f"fetch_page_{page_number}"
|
|
82
|
+
)
|
|
83
|
+
thread.start()
|
|
84
|
+
|
|
85
|
+
def fetch_page(self, page_number: int):
|
|
86
|
+
cook = self.kitchen.generate()
|
|
87
|
+
url = cook + str(page_number) + '/'
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
page = Fetcher.get(url)
|
|
91
|
+
|
|
92
|
+
if page.status != 200:
|
|
93
|
+
with self.lock:
|
|
94
|
+
self.max_page_number = page_number - 1
|
|
95
|
+
self.fetching_pages.discard(page_number)
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
if page.find_by_text('No results were returned.'):
|
|
99
|
+
with self.lock:
|
|
100
|
+
self.max_page_number = page_number - 1
|
|
101
|
+
self.fetching_pages.discard(page_number)
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
rows = page.xpath('//tbody/tr')
|
|
105
|
+
page_results = []
|
|
106
|
+
|
|
107
|
+
for row in rows:
|
|
108
|
+
name = row.css('td.coll-1.name a::text').get()
|
|
109
|
+
if self.kitchen.remove and any(word.lower() in name.lower() for word in self.kitchen.remove):
|
|
110
|
+
continue
|
|
111
|
+
if self.kitchen.search and not any(word.lower() in name.lower() for word in self.kitchen.search):
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
link = self.kitchen.base_url + row.css('td.coll-1.name a:not(.icon)::attr(href)').get()
|
|
115
|
+
seeds = row.css('td.coll-2.seeds::text').get()
|
|
116
|
+
leeches = row.css('td.coll-3.leeches::text').get()
|
|
117
|
+
date = row.css('td.coll-date::text').get()
|
|
118
|
+
|
|
119
|
+
size = None
|
|
120
|
+
uploader = None
|
|
121
|
+
for role in roles:
|
|
122
|
+
size = row.css(f'td.coll-4.size.mob-{role}::text').get()
|
|
123
|
+
uploader = row.css(f'td.coll-5.{role} a::text').get()
|
|
124
|
+
if size and uploader:
|
|
125
|
+
break
|
|
126
|
+
|
|
127
|
+
page_results.append({
|
|
128
|
+
'name': name,
|
|
129
|
+
'link': link,
|
|
130
|
+
'seeds': seeds,
|
|
131
|
+
'leeches': leeches,
|
|
132
|
+
'date': date,
|
|
133
|
+
'size': size,
|
|
134
|
+
'uploader': uploader,
|
|
135
|
+
'page': page_number
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
with self.lock:
|
|
139
|
+
self.all_results.extend(page_results)
|
|
140
|
+
self.fetched_pages.add(page_number)
|
|
141
|
+
self.fetching_pages.discard(page_number)
|
|
142
|
+
|
|
143
|
+
self.call_from_thread(self.show_current_page)
|
|
144
|
+
|
|
145
|
+
except SessionClosed:
|
|
146
|
+
with self.lock:
|
|
147
|
+
self.max_page_number = page_number - 1
|
|
148
|
+
self.fetching_pages.discard(page_number)
|
|
149
|
+
except Exception as e:
|
|
150
|
+
with self.lock:
|
|
151
|
+
self.fetching_pages.discard(page_number)
|
|
152
|
+
self.call_from_thread(self.update_status, f"Page error {page_number}: {str(e)}")
|
|
153
|
+
|
|
154
|
+
def get_results_for_display_page(self, display_page: int) -> list:
|
|
155
|
+
start_idx = display_page * self.results_per_page
|
|
156
|
+
end_idx = start_idx + self.results_per_page
|
|
157
|
+
|
|
158
|
+
with self.lock:
|
|
159
|
+
return self.all_results[start_idx:end_idx]
|
|
160
|
+
|
|
161
|
+
def show_current_page(self):
|
|
162
|
+
container = self.query_one("#results-container")
|
|
163
|
+
container.remove_children()
|
|
164
|
+
|
|
165
|
+
page_results = self.get_results_for_display_page(self.current_page)
|
|
166
|
+
|
|
167
|
+
if not page_results and self.current_page == 0:
|
|
168
|
+
container.mount(LoadingIndicator())
|
|
169
|
+
self.update_status("Loading...")
|
|
170
|
+
elif not page_results:
|
|
171
|
+
container.mount(Label("[yellow]No results[/]"))
|
|
172
|
+
else:
|
|
173
|
+
for result in page_results:
|
|
174
|
+
container.mount(ResultWidget(result))
|
|
175
|
+
|
|
176
|
+
with self.lock:
|
|
177
|
+
total_results = len(self.all_results)
|
|
178
|
+
fetched_count = len(self.fetched_pages)
|
|
179
|
+
fetching_count = len(self.fetching_pages)
|
|
180
|
+
|
|
181
|
+
total_pages = (total_results + self.results_per_page - 1) // self.results_per_page if total_results > 0 else 1
|
|
182
|
+
start_idx = self.current_page * self.results_per_page + 1
|
|
183
|
+
end_idx = min((self.current_page + 1) * self.results_per_page, total_results)
|
|
184
|
+
|
|
185
|
+
status_msg = f"Page {self.current_page + 1}/{total_pages} | Results {start_idx}-{end_idx} of {total_results}"
|
|
186
|
+
if fetching_count > 0:
|
|
187
|
+
status_msg += f" | Loading... (pages: {fetched_count})"
|
|
188
|
+
else:
|
|
189
|
+
status_msg += f" | Loaded pages: {fetched_count}"
|
|
190
|
+
|
|
191
|
+
self.update_status(status_msg)
|
|
192
|
+
|
|
193
|
+
self.prefetch_pages()
|
|
194
|
+
|
|
195
|
+
def prefetch_pages(self):
|
|
196
|
+
current_result_start = self.current_page * self.results_per_page
|
|
197
|
+
current_result_end = current_result_start + self.results_per_page
|
|
198
|
+
|
|
199
|
+
results_per_site_page = 20
|
|
200
|
+
|
|
201
|
+
pages_ahead = 3
|
|
202
|
+
future_result_end = current_result_end + (pages_ahead * self.results_per_page)
|
|
203
|
+
|
|
204
|
+
site_pages_needed = (future_result_end // results_per_site_page) + 2
|
|
205
|
+
|
|
206
|
+
for site_page in range(1, site_pages_needed + 1):
|
|
207
|
+
self.fetch_page_async(site_page)
|
|
208
|
+
|
|
209
|
+
def update_status(self, message: str):
|
|
210
|
+
try:
|
|
211
|
+
status = self.query_one("#status")
|
|
212
|
+
status.update(message)
|
|
213
|
+
except:
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
def action_next_page(self):
|
|
217
|
+
with self.lock:
|
|
218
|
+
total_results = len(self.all_results)
|
|
219
|
+
|
|
220
|
+
max_page = (total_results - 1) // self.results_per_page
|
|
221
|
+
|
|
222
|
+
if total_results == 0 or self.current_page < max_page:
|
|
223
|
+
self.current_page += 1
|
|
224
|
+
self.show_current_page()
|
|
225
|
+
|
|
226
|
+
def action_prev_page(self):
|
|
227
|
+
if self.current_page > 0:
|
|
228
|
+
self.current_page -= 1
|
|
229
|
+
self.show_current_page()
|
|
230
|
+
|
|
231
|
+
class Category(Enum):
|
|
232
|
+
MOVIE = "Movies"
|
|
233
|
+
TV = "TV"
|
|
234
|
+
GAME = "Games"
|
|
235
|
+
MUSIC = "Music"
|
|
236
|
+
APP = "Apps"
|
|
237
|
+
DOCU = "Documentaries"
|
|
238
|
+
ANIME = "Anime"
|
|
239
|
+
OTHER = "Other"
|
|
240
|
+
XXX = "XXX"
|
|
241
|
+
|
|
242
|
+
class Sort(Enum):
|
|
243
|
+
TIME = "time"
|
|
244
|
+
SIZE = "size"
|
|
245
|
+
SEED = "seeders"
|
|
246
|
+
LEECH = "leechers"
|
|
247
|
+
|
|
248
|
+
class Ord(Enum):
|
|
249
|
+
ASC = "asc"
|
|
250
|
+
DESC = "desc"
|
|
251
|
+
|
|
252
|
+
class Url:
|
|
253
|
+
base_url = "https://1337x.to"
|
|
254
|
+
|
|
255
|
+
def __init__(self, name: str, category: Category = None, sort: Sort = None, ord: Ord = Ord.DESC, search: list[str] = None, remove: list[str] = None):
|
|
256
|
+
self.name = name
|
|
257
|
+
self.category = category
|
|
258
|
+
self.sort = sort
|
|
259
|
+
self.ord = ord
|
|
260
|
+
self.search = [] or search
|
|
261
|
+
self.remove = [] or remove
|
|
262
|
+
|
|
263
|
+
def generate(self) -> str:
|
|
264
|
+
search_name = self.name.replace(" ", "+")
|
|
265
|
+
|
|
266
|
+
if self.sort and self.category:
|
|
267
|
+
return f"{self.base_url}/sort-category-search/{search_name}/{self.category.value}/{self.sort.value}/{self.ord.value}/"
|
|
268
|
+
elif self.sort:
|
|
269
|
+
return f"{self.base_url}/sort-search/{search_name}/{self.sort.value}/{self.ord.value}/"
|
|
270
|
+
elif self.category:
|
|
271
|
+
return f"{self.base_url}/category-search/{search_name}/{self.category.value}/"
|
|
272
|
+
else:
|
|
273
|
+
return f"{self.base_url}/search/{search_name}/"
|
|
274
|
+
|
|
275
|
+
def argo() -> Url:
|
|
276
|
+
parser = argparse.ArgumentParser(
|
|
277
|
+
prog='bt1337xearch',
|
|
278
|
+
description='Better search for 1337x[.]to',
|
|
279
|
+
epilog='Example:\nbt1337xearch -n Dexter -c TV -s TIME -o ASC'
|
|
280
|
+
)
|
|
281
|
+
parser.add_argument("-n", "--name", help="Name of the Media", required=True)
|
|
282
|
+
parser.add_argument("-c", "--category", help="Category", choices=['MOVIE', 'TV', 'GAME', 'MUSIC', 'APP', 'DOCU', 'ANIME', 'OTHER', 'XXX'])
|
|
283
|
+
parser.add_argument("-s", "--sort", help="Sort by", choices=['TIME', 'SIZE', 'SEED', 'LEECH'])
|
|
284
|
+
parser.add_argument("-o", "--order", help="Order by", choices=['ASC', 'DESC'], default='DESC')
|
|
285
|
+
parser.add_argument("-f", "--filter", nargs='+', help="Filter by words\nYou can use '~' and '+' to filter with or without that word.")
|
|
286
|
+
|
|
287
|
+
args = parser.parse_args()
|
|
288
|
+
|
|
289
|
+
search = []
|
|
290
|
+
remove = []
|
|
291
|
+
|
|
292
|
+
if (args.filter):
|
|
293
|
+
for filter in args.filter:
|
|
294
|
+
if (filter[0] == '+'):
|
|
295
|
+
search.append(filter[1:].strip())
|
|
296
|
+
elif (filter[0] == '~'):
|
|
297
|
+
remove.append(filter[1:].strip())
|
|
298
|
+
|
|
299
|
+
kitchen = Url(
|
|
300
|
+
args.name,
|
|
301
|
+
category=Category[args.category] if args.category else None,
|
|
302
|
+
sort=Sort[args.sort] if args.sort else None,
|
|
303
|
+
ord=Ord[args.order],
|
|
304
|
+
search=search,
|
|
305
|
+
remove=remove
|
|
306
|
+
)
|
|
307
|
+
return(kitchen)
|
|
308
|
+
|
|
309
|
+
def parser() -> None:
|
|
310
|
+
try:
|
|
311
|
+
kitchen = argo()
|
|
312
|
+
app = MyApp(kitchen, ansi_color=True)
|
|
313
|
+
app.run()
|
|
314
|
+
|
|
315
|
+
except KeyboardInterrupt:
|
|
316
|
+
print("\n\nSearch interrupted")
|
|
317
|
+
exit(0)
|