quasarr 1.26.7__py3-none-any.whl → 1.27.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 +23 -152
- quasarr/api/config/__init__.py +115 -3
- quasarr/providers/sessions/al.py +21 -0
- quasarr/providers/sessions/dd.py +8 -1
- quasarr/providers/sessions/dl.py +34 -23
- quasarr/providers/sessions/nx.py +8 -1
- quasarr/providers/utils.py +168 -0
- quasarr/providers/version.py +1 -1
- quasarr/storage/config.py +3 -0
- quasarr/storage/setup.py +456 -15
- {quasarr-1.26.7.dist-info → quasarr-1.27.0.dist-info}/METADATA +79 -93
- {quasarr-1.26.7.dist-info → quasarr-1.27.0.dist-info}/RECORD +16 -15
- {quasarr-1.26.7.dist-info → quasarr-1.27.0.dist-info}/WHEEL +0 -0
- {quasarr-1.26.7.dist-info → quasarr-1.27.0.dist-info}/entry_points.txt +0 -0
- {quasarr-1.26.7.dist-info → quasarr-1.27.0.dist-info}/licenses/LICENSE +0 -0
- {quasarr-1.26.7.dist-info → quasarr-1.27.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Quasarr
|
|
3
|
+
# Project by https://github.com/rix1337
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import socket
|
|
7
|
+
import sys
|
|
8
|
+
from urllib.parse import urlparse
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Unbuffered(object):
|
|
14
|
+
def __init__(self, stream):
|
|
15
|
+
self.stream = stream
|
|
16
|
+
|
|
17
|
+
def write(self, data):
|
|
18
|
+
self.stream.write(data)
|
|
19
|
+
self.stream.flush()
|
|
20
|
+
|
|
21
|
+
def writelines(self, datas):
|
|
22
|
+
self.stream.writelines(datas)
|
|
23
|
+
self.stream.flush()
|
|
24
|
+
|
|
25
|
+
def __getattr__(self, attr):
|
|
26
|
+
return getattr(self.stream, attr)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def is_valid_url(url):
|
|
30
|
+
"""Validate if a URL is properly formatted."""
|
|
31
|
+
if "/raw/eX4Mpl3" in url:
|
|
32
|
+
print("Example URL detected. Please provide a valid URL found on pastebin or any other public site!")
|
|
33
|
+
return False
|
|
34
|
+
|
|
35
|
+
parsed = urlparse(url)
|
|
36
|
+
return parsed.scheme in ("http", "https") and bool(parsed.netloc)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def extract_allowed_keys(config, section):
|
|
40
|
+
"""
|
|
41
|
+
Extracts allowed keys from the specified section in the configuration.
|
|
42
|
+
|
|
43
|
+
:param config: The configuration dictionary.
|
|
44
|
+
:param section: The section from which to extract keys.
|
|
45
|
+
:return: A list of allowed keys.
|
|
46
|
+
"""
|
|
47
|
+
if section not in config:
|
|
48
|
+
raise ValueError(f"Section '{section}' not found in configuration.")
|
|
49
|
+
return [key for key, *_ in config[section]]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def extract_kv_pairs(input_text, allowed_keys):
|
|
53
|
+
"""
|
|
54
|
+
Extracts key-value pairs from the given text where keys match allowed_keys.
|
|
55
|
+
|
|
56
|
+
:param input_text: The input text containing key-value pairs.
|
|
57
|
+
:param allowed_keys: A list of allowed two-letter shorthand keys.
|
|
58
|
+
:return: A dictionary of extracted key-value pairs.
|
|
59
|
+
"""
|
|
60
|
+
kv_pattern = re.compile(rf"^({'|'.join(map(re.escape, allowed_keys))})\s*=\s*(.*)$")
|
|
61
|
+
kv_pairs = {}
|
|
62
|
+
|
|
63
|
+
for line in input_text.splitlines():
|
|
64
|
+
match = kv_pattern.match(line.strip())
|
|
65
|
+
if match:
|
|
66
|
+
key, value = match.groups()
|
|
67
|
+
kv_pairs[key] = value
|
|
68
|
+
elif "[Hostnames]" in line:
|
|
69
|
+
pass
|
|
70
|
+
else:
|
|
71
|
+
print(f"Skipping line because it does not contain any supported hostname: {line}")
|
|
72
|
+
|
|
73
|
+
return kv_pairs
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def check_ip():
|
|
77
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
78
|
+
try:
|
|
79
|
+
s.connect(('10.255.255.255', 0))
|
|
80
|
+
ip = s.getsockname()[0]
|
|
81
|
+
except:
|
|
82
|
+
ip = '127.0.0.1'
|
|
83
|
+
finally:
|
|
84
|
+
s.close()
|
|
85
|
+
return ip
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def check_flaresolverr(shared_state, flaresolverr_url):
|
|
89
|
+
# Ensure it ends with /v<digit+>
|
|
90
|
+
if not re.search(r"/v\d+$", flaresolverr_url):
|
|
91
|
+
print(f"FlareSolverr URL does not end with /v#: {flaresolverr_url}")
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
# Try sending a simple test request
|
|
95
|
+
headers = {"Content-Type": "application/json"}
|
|
96
|
+
data = {
|
|
97
|
+
"cmd": "request.get",
|
|
98
|
+
"url": "http://www.google.com/",
|
|
99
|
+
"maxTimeout": 10000
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
response = requests.post(flaresolverr_url, headers=headers, json=data, timeout=10)
|
|
104
|
+
response.raise_for_status()
|
|
105
|
+
json_data = response.json()
|
|
106
|
+
|
|
107
|
+
# Check if the structure looks like a valid FlareSolverr response
|
|
108
|
+
if "status" in json_data and json_data["status"] == "ok":
|
|
109
|
+
solution = json_data["solution"]
|
|
110
|
+
solution_ua = solution.get("userAgent", None)
|
|
111
|
+
if solution_ua:
|
|
112
|
+
shared_state.update("user_agent", solution_ua)
|
|
113
|
+
return True
|
|
114
|
+
else:
|
|
115
|
+
print(f"Unexpected FlareSolverr response: {json_data}")
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
except Exception as e:
|
|
119
|
+
print(f"Failed to connect to FlareSolverr: {e}")
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def validate_address(address, name):
|
|
124
|
+
if not address.startswith("http"):
|
|
125
|
+
sys.exit(f"Error: {name} '{address}' is invalid. It must start with 'http'.")
|
|
126
|
+
|
|
127
|
+
colon_count = address.count(":")
|
|
128
|
+
if colon_count < 1 or colon_count > 2:
|
|
129
|
+
sys.exit(
|
|
130
|
+
f"Error: {name} '{address}' is invalid. It must contain 1 or 2 colons, but it has {colon_count}.")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def is_site_usable(shared_state, shorthand):
|
|
134
|
+
"""
|
|
135
|
+
Check if a site is fully configured and usable.
|
|
136
|
+
|
|
137
|
+
For sites that don't require login, just checks if hostname is set.
|
|
138
|
+
For login-required sites (al, dd, dl, nx), also checks that login wasn't skipped
|
|
139
|
+
and that credentials exist.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
shared_state: Shared state object
|
|
143
|
+
shorthand: Site shorthand (e.g., 'al', 'dd', etc.)
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
bool: True if site is usable, False otherwise
|
|
147
|
+
"""
|
|
148
|
+
shorthand = shorthand.lower()
|
|
149
|
+
|
|
150
|
+
# Check if hostname is set
|
|
151
|
+
hostname = shared_state.values["config"]('Hostnames').get(shorthand)
|
|
152
|
+
if not hostname:
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
login_required_sites = ['al', 'dd', 'dl', 'nx']
|
|
156
|
+
if shorthand not in login_required_sites:
|
|
157
|
+
return True # No login needed, hostname is enough
|
|
158
|
+
|
|
159
|
+
# Check if login was skipped
|
|
160
|
+
if shared_state.values["database"]("skip_login").retrieve(shorthand):
|
|
161
|
+
return False # Hostname set but login was skipped
|
|
162
|
+
|
|
163
|
+
# Check for credentials
|
|
164
|
+
config = shared_state.values["config"](shorthand.upper())
|
|
165
|
+
user = config.get('user')
|
|
166
|
+
password = config.get('password')
|
|
167
|
+
|
|
168
|
+
return bool(user and password)
|
quasarr/providers/version.py
CHANGED