quasarr 0.1.6__py3-none-any.whl → 1.23.0__py3-none-any.whl
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.
Potentially problematic release.
This version of quasarr might be problematic. Click here for more details.
- quasarr/__init__.py +316 -42
- quasarr/api/__init__.py +187 -0
- quasarr/api/arr/__init__.py +387 -0
- quasarr/api/captcha/__init__.py +1189 -0
- quasarr/api/config/__init__.py +23 -0
- quasarr/api/sponsors_helper/__init__.py +166 -0
- quasarr/api/statistics/__init__.py +196 -0
- quasarr/downloads/__init__.py +319 -256
- quasarr/downloads/linkcrypters/__init__.py +0 -0
- quasarr/downloads/linkcrypters/al.py +237 -0
- quasarr/downloads/linkcrypters/filecrypt.py +444 -0
- quasarr/downloads/linkcrypters/hide.py +123 -0
- quasarr/downloads/packages/__init__.py +476 -0
- quasarr/downloads/sources/al.py +697 -0
- quasarr/downloads/sources/by.py +106 -0
- quasarr/downloads/sources/dd.py +76 -0
- quasarr/downloads/sources/dj.py +7 -0
- quasarr/downloads/sources/dl.py +199 -0
- quasarr/downloads/sources/dt.py +66 -0
- quasarr/downloads/sources/dw.py +14 -7
- quasarr/downloads/sources/he.py +112 -0
- quasarr/downloads/sources/mb.py +47 -0
- quasarr/downloads/sources/nk.py +54 -0
- quasarr/downloads/sources/nx.py +42 -83
- quasarr/downloads/sources/sf.py +159 -0
- quasarr/downloads/sources/sj.py +7 -0
- quasarr/downloads/sources/sl.py +90 -0
- quasarr/downloads/sources/wd.py +110 -0
- quasarr/downloads/sources/wx.py +127 -0
- quasarr/providers/cloudflare.py +204 -0
- quasarr/providers/html_images.py +22 -0
- quasarr/providers/html_templates.py +211 -104
- quasarr/providers/imdb_metadata.py +108 -3
- quasarr/providers/log.py +19 -0
- quasarr/providers/myjd_api.py +201 -40
- quasarr/providers/notifications.py +99 -11
- quasarr/providers/obfuscated.py +65 -0
- quasarr/providers/sessions/__init__.py +0 -0
- quasarr/providers/sessions/al.py +286 -0
- quasarr/providers/sessions/dd.py +78 -0
- quasarr/providers/sessions/dl.py +175 -0
- quasarr/providers/sessions/nx.py +76 -0
- quasarr/providers/shared_state.py +656 -79
- quasarr/providers/statistics.py +154 -0
- quasarr/providers/version.py +60 -1
- quasarr/providers/web_server.py +1 -1
- quasarr/search/__init__.py +144 -15
- quasarr/search/sources/al.py +448 -0
- quasarr/search/sources/by.py +204 -0
- quasarr/search/sources/dd.py +135 -0
- quasarr/search/sources/dj.py +213 -0
- quasarr/search/sources/dl.py +354 -0
- quasarr/search/sources/dt.py +265 -0
- quasarr/search/sources/dw.py +94 -67
- quasarr/search/sources/fx.py +89 -33
- quasarr/search/sources/he.py +196 -0
- quasarr/search/sources/mb.py +195 -0
- quasarr/search/sources/nk.py +188 -0
- quasarr/search/sources/nx.py +75 -21
- quasarr/search/sources/sf.py +374 -0
- quasarr/search/sources/sj.py +213 -0
- quasarr/search/sources/sl.py +246 -0
- quasarr/search/sources/wd.py +208 -0
- quasarr/search/sources/wx.py +337 -0
- quasarr/storage/config.py +39 -10
- quasarr/storage/setup.py +269 -97
- quasarr/storage/sqlite_database.py +6 -1
- quasarr-1.23.0.dist-info/METADATA +306 -0
- quasarr-1.23.0.dist-info/RECORD +77 -0
- {quasarr-0.1.6.dist-info → quasarr-1.23.0.dist-info}/WHEEL +1 -1
- quasarr/arr/__init__.py +0 -423
- quasarr/captcha_solver/__init__.py +0 -284
- quasarr-0.1.6.dist-info/METADATA +0 -81
- quasarr-0.1.6.dist-info/RECORD +0 -31
- {quasarr-0.1.6.dist-info → quasarr-1.23.0.dist-info}/entry_points.txt +0 -0
- {quasarr-0.1.6.dist-info → quasarr-1.23.0.dist-info/licenses}/LICENSE +0 -0
- {quasarr-0.1.6.dist-info → quasarr-1.23.0.dist-info}/top_level.txt +0 -0
|
@@ -2,133 +2,240 @@
|
|
|
2
2
|
# Quasarr
|
|
3
3
|
# Project by https://github.com/rix1337
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
justify-content: center;
|
|
9
|
-
align-items: center;
|
|
10
|
-
height: 100vh;
|
|
11
|
-
max-height: 100vh;
|
|
12
|
-
overflow-y: auto;
|
|
13
|
-
background-color: #212529;
|
|
14
|
-
color: #fff;
|
|
15
|
-
font-family: system-ui,-apple-system,'Segoe UI',Roboto,'Helvetica Neue',
|
|
16
|
-
'Noto Sans','Liberation Sans',Arial,sans-serif,'Apple Color Emoji',
|
|
17
|
-
'Segoe UI Emoji','Segoe UI Symbol','Noto Color Emoji';
|
|
18
|
-
"""
|
|
19
|
-
style_inner = """
|
|
20
|
-
background-color: #fff;
|
|
21
|
-
border-radius: 0.375rem;
|
|
22
|
-
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
|
23
|
-
padding: 20px;
|
|
24
|
-
text-align: center;
|
|
25
|
-
color: #212529;
|
|
26
|
-
font-size: 1rem;
|
|
27
|
-
font-weight: 400;
|
|
28
|
-
line-height: 1.5;
|
|
29
|
-
width: -webkit-fit-content; width: -moz-fit-content; width: fit-content;
|
|
30
|
-
margin: auto;
|
|
31
|
-
"""
|
|
5
|
+
import quasarr.providers.html_images as images
|
|
6
|
+
from quasarr.providers.version import get_version
|
|
7
|
+
|
|
32
8
|
|
|
33
|
-
|
|
34
|
-
|
|
9
|
+
def render_centered_html(inner_content):
|
|
10
|
+
head = '''
|
|
35
11
|
<head>
|
|
12
|
+
<meta charset="utf-8">
|
|
13
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
36
14
|
<title>Quasarr</title>
|
|
37
|
-
|
|
15
|
+
<link rel="icon" href="''' + images.logo + '''" type="image/png">
|
|
16
|
+
<style>
|
|
17
|
+
/* Theme variables */
|
|
18
|
+
:root {
|
|
19
|
+
--bg-color: #ffffff;
|
|
20
|
+
--fg-color: #212529;
|
|
21
|
+
--card-bg: #ffffff;
|
|
22
|
+
--card-shadow: rgba(0, 0, 0, 0.1);
|
|
23
|
+
--primary: #0d6efd;
|
|
24
|
+
--secondary: #6c757d;
|
|
25
|
+
--code-bg: #f8f9fa;
|
|
26
|
+
--spacing: 1rem;
|
|
27
|
+
}
|
|
28
|
+
@media (prefers-color-scheme: dark) {
|
|
29
|
+
:root {
|
|
30
|
+
--bg-color: #181a1b;
|
|
31
|
+
--fg-color: #f1f1f1;
|
|
32
|
+
--card-bg: #242526;
|
|
33
|
+
--card-shadow: rgba(0, 0, 0, 0.5);
|
|
34
|
+
--code-bg: #2c2f33;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/* Logo and heading alignment */
|
|
38
|
+
h1 {
|
|
39
|
+
display: inline-flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
justify-content: center;
|
|
42
|
+
margin-bottom: 0.5rem;
|
|
43
|
+
font-size: 2rem;
|
|
44
|
+
}
|
|
45
|
+
.logo {
|
|
46
|
+
width: 48px;
|
|
47
|
+
height: 48px;
|
|
48
|
+
margin-right: 0.5rem;
|
|
49
|
+
}
|
|
50
|
+
/* Form labels and inputs */
|
|
51
|
+
label {
|
|
52
|
+
display: block;
|
|
53
|
+
font-weight: 600;
|
|
54
|
+
margin-bottom: 0.5rem;
|
|
55
|
+
}
|
|
56
|
+
input, select {
|
|
57
|
+
display: block;
|
|
58
|
+
width: 100%;
|
|
59
|
+
padding: 0.5rem;
|
|
60
|
+
font-size: 1rem;
|
|
61
|
+
border: 1px solid #ced4da;
|
|
62
|
+
border-radius: 0.5rem;
|
|
63
|
+
background-color: var(--card-bg);
|
|
64
|
+
color: var(--fg-color);
|
|
65
|
+
box-sizing: border-box;
|
|
66
|
+
}
|
|
67
|
+
*, *::before, *::after {
|
|
68
|
+
box-sizing: border-box;
|
|
69
|
+
}
|
|
70
|
+
/* make body a column flex so footer can stick to bottom */
|
|
71
|
+
html, body {
|
|
72
|
+
margin: 0;
|
|
73
|
+
padding: 0;
|
|
74
|
+
width: 100%;
|
|
75
|
+
height: 100%;
|
|
76
|
+
background-color: var(--bg-color);
|
|
77
|
+
color: var(--fg-color);
|
|
78
|
+
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue',
|
|
79
|
+
'Noto Sans', Arial, sans-serif;
|
|
80
|
+
line-height: 1.6;
|
|
81
|
+
display: flex;
|
|
82
|
+
flex-direction: column;
|
|
83
|
+
min-height: 100vh;
|
|
84
|
+
}
|
|
85
|
+
.outer {
|
|
86
|
+
flex: 1;
|
|
87
|
+
display: flex;
|
|
88
|
+
justify-content: center;
|
|
89
|
+
align-items: center;
|
|
90
|
+
padding: var(--spacing);
|
|
91
|
+
}
|
|
92
|
+
.inner {
|
|
93
|
+
background-color: var(--card-bg);
|
|
94
|
+
border-radius: 1rem;
|
|
95
|
+
box-shadow: 0 0.5rem 1.5rem var(--card-shadow);
|
|
96
|
+
padding: calc(var(--spacing) * 2);
|
|
97
|
+
text-align: center;
|
|
98
|
+
width: 100%;
|
|
99
|
+
max-width: fit-content;
|
|
100
|
+
}
|
|
101
|
+
/* No padding on the sides for captcha view on small screens */
|
|
102
|
+
@media (max-width: 600px) {
|
|
103
|
+
body:has(iframe) .outer {
|
|
104
|
+
padding-left: 0;
|
|
105
|
+
padding-right: 0;
|
|
106
|
+
}
|
|
107
|
+
body:has(iframe) .inner {
|
|
108
|
+
padding-left: 0;
|
|
109
|
+
padding-right: 0;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
h2 {
|
|
113
|
+
margin-top: var(--spacing);
|
|
114
|
+
margin-bottom: 0.75rem;
|
|
115
|
+
font-size: 1.5rem;
|
|
116
|
+
}
|
|
117
|
+
h3 {
|
|
118
|
+
margin-top: var(--spacing);
|
|
119
|
+
margin-bottom: 0.5rem;
|
|
120
|
+
font-size: 1.125rem;
|
|
121
|
+
font-weight: 500;
|
|
122
|
+
}
|
|
123
|
+
p {
|
|
124
|
+
margin: 0.5rem 0;
|
|
125
|
+
}
|
|
126
|
+
.copy-input {
|
|
127
|
+
background-color: var(--code-bg);
|
|
128
|
+
}
|
|
129
|
+
.url-wrapper .api-key-wrapper {
|
|
130
|
+
display: flex;
|
|
131
|
+
gap: 0.5rem;
|
|
132
|
+
flex-wrap: wrap;
|
|
133
|
+
justify-content: center;
|
|
134
|
+
margin-bottom: var(--spacing);
|
|
135
|
+
}
|
|
136
|
+
.captcha-container {
|
|
137
|
+
background-color: var(--secondary);
|
|
138
|
+
}
|
|
139
|
+
button {
|
|
140
|
+
padding: 0.5rem 1rem;
|
|
141
|
+
font-size: 1rem;
|
|
142
|
+
border-radius: 0.5rem;
|
|
143
|
+
font-weight: 500;
|
|
144
|
+
cursor: pointer;
|
|
145
|
+
transition: background-color 0.2s ease, border-color 0.2s ease;
|
|
146
|
+
border: none;
|
|
147
|
+
margin-top: 0.5rem;
|
|
148
|
+
}
|
|
149
|
+
.btn-primary {
|
|
150
|
+
background-color: var(--primary);
|
|
151
|
+
color: #fff;
|
|
152
|
+
}
|
|
153
|
+
.btn-secondary {
|
|
154
|
+
background-color: var(--secondary);
|
|
155
|
+
color: #fff;
|
|
156
|
+
}
|
|
157
|
+
a {
|
|
158
|
+
color: var(--primary);
|
|
159
|
+
text-decoration: none;
|
|
160
|
+
}
|
|
161
|
+
a:hover {
|
|
162
|
+
|
|
163
|
+
}
|
|
164
|
+
/* footer styling */
|
|
165
|
+
footer {
|
|
166
|
+
text-align: center;
|
|
167
|
+
font-size: 0.75rem;
|
|
168
|
+
color: var(--secondary);
|
|
169
|
+
padding: 0.5rem 0;
|
|
170
|
+
}
|
|
171
|
+
</style>
|
|
172
|
+
</head>'''
|
|
173
|
+
|
|
174
|
+
body = f'''
|
|
175
|
+
{head}
|
|
38
176
|
<body>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
177
|
+
<div class="outer">
|
|
178
|
+
<div class="inner">
|
|
179
|
+
{inner_content}
|
|
180
|
+
</div>
|
|
42
181
|
</div>
|
|
43
|
-
|
|
182
|
+
<footer>
|
|
183
|
+
Quasarr v.{get_version()}
|
|
184
|
+
</footer>
|
|
44
185
|
</body>
|
|
45
186
|
'''
|
|
187
|
+
return f'<html>{body}</html>'
|
|
46
188
|
|
|
47
189
|
|
|
48
190
|
def render_button(text, button_type="primary", attributes=None):
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
"user-select: none; transition: color 0.15s ease-in-out, "
|
|
55
|
-
"background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, "
|
|
56
|
-
"box-shadow 0.15s ease-in-out; "
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
if button_type == "primary":
|
|
60
|
-
style = base_style + "background-color: #0d6efd; border: 1px solid #0d6efd; "
|
|
61
|
-
else:
|
|
62
|
-
style = base_style + "background-color: #6c757d; border: 1px solid #6c757d; "
|
|
63
|
-
|
|
64
|
-
attr_str = ' '.join(f'{key}="{value}"' for key, value in attributes.items()) if attributes else ""
|
|
65
|
-
|
|
66
|
-
return f'<button style="{style}" {attr_str}>{text}</button>'
|
|
191
|
+
cls = "btn-primary" if button_type == "primary" else "btn-secondary"
|
|
192
|
+
attr_str = ''
|
|
193
|
+
if attributes:
|
|
194
|
+
attr_str = ' '.join(f'{key}="{value}"' for key, value in attributes.items())
|
|
195
|
+
return f'<button class="{cls}" {attr_str}>{text}</button>'
|
|
67
196
|
|
|
68
197
|
|
|
69
198
|
def render_form(header, form="", script=""):
|
|
70
|
-
styles = """
|
|
71
|
-
<style>
|
|
72
|
-
input, select {
|
|
73
|
-
display: block;
|
|
74
|
-
padding: .375rem .75rem;
|
|
75
|
-
width: 100%;
|
|
76
|
-
font-size: 1rem;
|
|
77
|
-
font-weight: 400;
|
|
78
|
-
line-height: 1.5;
|
|
79
|
-
color: #212529;
|
|
80
|
-
background-color: #fff;
|
|
81
|
-
border: 1px solid #dee2e6;
|
|
82
|
-
border-radius: .375rem;
|
|
83
|
-
transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
|
|
84
|
-
text-align: center;
|
|
85
|
-
margin: 10px auto;
|
|
86
|
-
}
|
|
87
|
-
</style>
|
|
88
|
-
"""
|
|
89
199
|
content = f'''
|
|
90
|
-
<h1
|
|
91
|
-
<
|
|
92
|
-
{styles}
|
|
200
|
+
<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
201
|
+
<h2>{header}</h2>
|
|
93
202
|
{form}
|
|
94
203
|
{script}
|
|
95
204
|
'''
|
|
96
205
|
return render_centered_html(content)
|
|
97
206
|
|
|
98
207
|
|
|
99
|
-
def render_success(message, timeout=10):
|
|
208
|
+
def render_success(message, timeout=10, optional_text=""):
|
|
100
209
|
button_html = render_button(f"Wait time... {timeout}", "secondary", {"id": "nextButton", "disabled": "true"})
|
|
101
|
-
script = f
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
{script}
|
|
125
|
-
"""
|
|
210
|
+
script = f'''
|
|
211
|
+
<script>
|
|
212
|
+
let counter = {timeout};
|
|
213
|
+
const btn = document.getElementById('nextButton');
|
|
214
|
+
const interval = setInterval(() => {{
|
|
215
|
+
counter--;
|
|
216
|
+
btn.innerText = `Wait time... ${{counter}}`;
|
|
217
|
+
if (counter === 0) {{
|
|
218
|
+
clearInterval(interval);
|
|
219
|
+
btn.innerText = 'Continue';
|
|
220
|
+
btn.disabled = false;
|
|
221
|
+
btn.className = 'btn-primary';
|
|
222
|
+
btn.onclick = () => window.location.href = '/';
|
|
223
|
+
}}
|
|
224
|
+
}}, 1000);
|
|
225
|
+
</script>
|
|
226
|
+
'''
|
|
227
|
+
content = f'''<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
228
|
+
<h2>{message}</h2>
|
|
229
|
+
{optional_text}
|
|
230
|
+
{button_html}
|
|
231
|
+
{script}
|
|
232
|
+
'''
|
|
126
233
|
return render_centered_html(content)
|
|
127
234
|
|
|
128
235
|
|
|
129
236
|
def render_fail(message):
|
|
130
237
|
button_html = render_button("Back", "secondary", {"onclick": "window.location.href='/'"})
|
|
131
|
-
return render_centered_html(f"""<h1
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
238
|
+
return render_centered_html(f"""<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
239
|
+
<h2>{message}</h2>
|
|
240
|
+
{button_html}
|
|
241
|
+
""")
|
|
@@ -2,9 +2,37 @@
|
|
|
2
2
|
# Quasarr
|
|
3
3
|
# Project by https://github.com/rix1337
|
|
4
4
|
|
|
5
|
+
import html
|
|
5
6
|
import re
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
|
+
from json import loads
|
|
9
|
+
from urllib.parse import quote
|
|
6
10
|
|
|
7
11
|
import requests
|
|
12
|
+
from bs4 import BeautifulSoup
|
|
13
|
+
|
|
14
|
+
from quasarr.providers.log import info, debug
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_poster_link(shared_state, imdb_id):
|
|
18
|
+
poster_link = None
|
|
19
|
+
if imdb_id:
|
|
20
|
+
headers = {'User-Agent': shared_state.values["user_agent"]}
|
|
21
|
+
request = requests.get(f"https://www.imdb.com/title/{imdb_id}/", headers=headers, timeout=10).text
|
|
22
|
+
soup = BeautifulSoup(request, "html.parser")
|
|
23
|
+
try:
|
|
24
|
+
poster_set = soup.find('div', class_='ipc-poster').div.img[
|
|
25
|
+
"srcset"] # contains links to posters in ascending resolution
|
|
26
|
+
poster_links = [x for x in poster_set.split(" ") if
|
|
27
|
+
len(x) > 10] # extract all poster links ignoring resolution info
|
|
28
|
+
poster_link = poster_links[-1] # get the highest resolution poster
|
|
29
|
+
except:
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
if not poster_link:
|
|
33
|
+
debug(f"Could not get poster title for {imdb_id} from IMDb")
|
|
34
|
+
|
|
35
|
+
return poster_link
|
|
8
36
|
|
|
9
37
|
|
|
10
38
|
def get_localized_title(shared_state, imdb_id, language='de'):
|
|
@@ -16,9 +44,9 @@ def get_localized_title(shared_state, imdb_id, language='de'):
|
|
|
16
44
|
}
|
|
17
45
|
|
|
18
46
|
try:
|
|
19
|
-
response = requests.get(f"https://www.imdb.com/title/{imdb_id}/", headers=headers)
|
|
47
|
+
response = requests.get(f"https://www.imdb.com/title/{imdb_id}/", headers=headers, timeout=10)
|
|
20
48
|
except Exception as e:
|
|
21
|
-
|
|
49
|
+
info(f"Error loading IMDb metadata for {imdb_id}: {e}")
|
|
22
50
|
return localized_title
|
|
23
51
|
|
|
24
52
|
try:
|
|
@@ -32,6 +60,83 @@ def get_localized_title(shared_state, imdb_id, language='de'):
|
|
|
32
60
|
pass
|
|
33
61
|
|
|
34
62
|
if not localized_title:
|
|
35
|
-
|
|
63
|
+
debug(f"Could not get localized title for {imdb_id} in {language} from IMDb")
|
|
64
|
+
|
|
65
|
+
localized_title = html.unescape(localized_title)
|
|
66
|
+
localized_title = re.sub(r"[^a-zA-Z0-9äöüÄÖÜß&-']", ' ', localized_title).strip()
|
|
67
|
+
localized_title = localized_title.replace(" - ", "-")
|
|
68
|
+
localized_title = re.sub(r'\s{2,}', ' ', localized_title)
|
|
36
69
|
|
|
37
70
|
return localized_title
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_clean_title(title):
|
|
74
|
+
try:
|
|
75
|
+
extracted_title = re.findall(r"(.*?)(?:.(?!19|20)\d{2}|\.German|.GERMAN|\.\d{3,4}p|\.S(?:\d{1,3}))", title)[0]
|
|
76
|
+
leftover_tags_removed = re.sub(
|
|
77
|
+
r'(|.UNRATED.*|.Unrated.*|.Uncut.*|.UNCUT.*)(|.Directors.Cut.*|.Final.Cut.*|.DC.*|.REMASTERED.*|.EXTENDED.*|.Extended.*|.Theatrical.*|.THEATRICAL.*)',
|
|
78
|
+
"", extracted_title)
|
|
79
|
+
clean_title = leftover_tags_removed.replace(".", " ").strip().replace(" ", "+")
|
|
80
|
+
|
|
81
|
+
except:
|
|
82
|
+
clean_title = title
|
|
83
|
+
return clean_title
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_imdb_id_from_title(shared_state, title, language="de"):
|
|
87
|
+
imdb_id = None
|
|
88
|
+
|
|
89
|
+
if re.search(r"S\d{1,3}(E\d{1,3})?", title, re.IGNORECASE):
|
|
90
|
+
ttype = "tv"
|
|
91
|
+
else:
|
|
92
|
+
ttype = "ft"
|
|
93
|
+
|
|
94
|
+
title = get_clean_title(title)
|
|
95
|
+
|
|
96
|
+
threshold = 60 * 60 * 48 # 48 hours
|
|
97
|
+
context = "recents_imdb"
|
|
98
|
+
recently_searched = shared_state.get_recently_searched(shared_state, context, threshold)
|
|
99
|
+
if title in recently_searched:
|
|
100
|
+
title_item = recently_searched[title]
|
|
101
|
+
if title_item["timestamp"] > datetime.now() - timedelta(seconds=threshold):
|
|
102
|
+
return title_item["imdb_id"]
|
|
103
|
+
|
|
104
|
+
headers = {
|
|
105
|
+
'Accept-Language': language,
|
|
106
|
+
'User-Agent': shared_state.values["user_agent"]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
results = requests.get(f"https://www.imdb.com/find/?q={quote(title)}&s=tt&ttype={ttype}&ref_=fn_{ttype}",
|
|
110
|
+
headers=headers, timeout=10)
|
|
111
|
+
|
|
112
|
+
if results.status_code == 200:
|
|
113
|
+
soup = BeautifulSoup(results.text, "html.parser")
|
|
114
|
+
props = soup.find("script", text=re.compile("props"))
|
|
115
|
+
details = loads(props.string)
|
|
116
|
+
search_results = details['props']['pageProps']['titleResults']['results']
|
|
117
|
+
|
|
118
|
+
if len(search_results) > 0:
|
|
119
|
+
for result in search_results:
|
|
120
|
+
try:
|
|
121
|
+
found_title = result["listItem"]["titleText"]
|
|
122
|
+
found_id = result["listItem"]["titleId"]
|
|
123
|
+
except KeyError:
|
|
124
|
+
found_title = result["titleNameText"]
|
|
125
|
+
found_id = result['id']
|
|
126
|
+
|
|
127
|
+
if shared_state.search_string_in_sanitized_title(title, found_title):
|
|
128
|
+
imdb_id = found_id
|
|
129
|
+
break
|
|
130
|
+
else:
|
|
131
|
+
debug(f"Request on IMDb failed: {results.status_code}")
|
|
132
|
+
|
|
133
|
+
recently_searched[title] = {
|
|
134
|
+
"imdb_id": imdb_id,
|
|
135
|
+
"timestamp": datetime.now()
|
|
136
|
+
}
|
|
137
|
+
shared_state.update(context, recently_searched)
|
|
138
|
+
|
|
139
|
+
if not imdb_id:
|
|
140
|
+
debug(f"No IMDb-ID found for {title}")
|
|
141
|
+
|
|
142
|
+
return imdb_id
|
quasarr/providers/log.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Quasarr
|
|
3
|
+
# Project by https://github.com/rix1337
|
|
4
|
+
|
|
5
|
+
import datetime
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def timestamp():
|
|
10
|
+
return datetime.datetime.now().strftime("[%Y-%m-%d %H:%M:%S]")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def info(string):
|
|
14
|
+
print(f"{timestamp()} {string}")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def debug(string):
|
|
18
|
+
if os.getenv('DEBUG'):
|
|
19
|
+
info(string)
|