freeflix-cli 1.0.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.
- freeflix_cli/__init__.py +0 -0
- freeflix_cli/anilist.py +207 -0
- freeflix_cli/cli_utils.py +191 -0
- freeflix_cli/config_loader.py +62 -0
- freeflix_cli/defaults.py +150 -0
- freeflix_cli/handlers/__init__.py +0 -0
- freeflix_cli/handlers/anilist.py +325 -0
- freeflix_cli/handlers/anime_sama.py +371 -0
- freeflix_cli/handlers/coflix.py +308 -0
- freeflix_cli/handlers/french_stream.py +238 -0
- freeflix_cli/handlers/goldenanime.py +397 -0
- freeflix_cli/handlers/goldenms.py +401 -0
- freeflix_cli/handlers/nyaa.py +112 -0
- freeflix_cli/handlers/playback.py +211 -0
- freeflix_cli/handlers/wiflix.py +107 -0
- freeflix_cli/history_ui.py +95 -0
- freeflix_cli/i18n.py +129 -0
- freeflix_cli/languages.py +33 -0
- freeflix_cli/main.py +376 -0
- freeflix_cli/notifications.py +219 -0
- freeflix_cli/player_manager.py +799 -0
- freeflix_cli/providers_registry.py +50 -0
- freeflix_cli/proxy.py +594 -0
- freeflix_cli/scraping/__init__.py +0 -0
- freeflix_cli/scraping/anime_sama.py +291 -0
- freeflix_cli/scraping/coflix.py +224 -0
- freeflix_cli/scraping/config.py +17 -0
- freeflix_cli/scraping/deobfuscate.py +11 -0
- freeflix_cli/scraping/french_stream.py +171 -0
- freeflix_cli/scraping/goldenanime.py +347 -0
- freeflix_cli/scraping/goldenms.py +382 -0
- freeflix_cli/scraping/nyaa.py +89 -0
- freeflix_cli/scraping/objects.py +254 -0
- freeflix_cli/scraping/player.py +595 -0
- freeflix_cli/scraping/subtitles.py +127 -0
- freeflix_cli/scraping/utils.py +94 -0
- freeflix_cli/scraping/wiflix.py +176 -0
- freeflix_cli/subtitles.py +135 -0
- freeflix_cli/tracker.py +321 -0
- freeflix_cli/update_checker.py +66 -0
- freeflix_cli-1.0.0.data/data/share/freeflix-cli/data/source_portal.jsonc +7 -0
- freeflix_cli-1.0.0.dist-info/METADATA +261 -0
- freeflix_cli-1.0.0.dist-info/RECORD +46 -0
- freeflix_cli-1.0.0.dist-info/WHEEL +4 -0
- freeflix_cli-1.0.0.dist-info/entry_points.txt +2 -0
- freeflix_cli-1.0.0.dist-info/licenses/LICENSE +674 -0
freeflix_cli/__init__.py
ADDED
|
File without changes
|
freeflix_cli/anilist.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from curl_cffi import requests
|
|
3
|
+
from typing import Optional, Dict, Any, List
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AniListClient:
|
|
7
|
+
def __init__(self, token: Optional[str] = None):
|
|
8
|
+
self.url = "https://graphql.anilist.co"
|
|
9
|
+
self.token = token
|
|
10
|
+
self.headers = {
|
|
11
|
+
"Content-Type": "application/json",
|
|
12
|
+
"Accept": "application/json",
|
|
13
|
+
}
|
|
14
|
+
if self.token:
|
|
15
|
+
self.headers["Authorization"] = f"Bearer {self.token}"
|
|
16
|
+
|
|
17
|
+
def set_token(self, token: str):
|
|
18
|
+
"""Set the authorization token."""
|
|
19
|
+
self.token = token
|
|
20
|
+
self.headers["Authorization"] = f"Bearer {token}"
|
|
21
|
+
|
|
22
|
+
def _query(
|
|
23
|
+
self, query: str, variables: Dict[str, Any] = None
|
|
24
|
+
) -> Optional[Dict[str, Any]]:
|
|
25
|
+
"""Send a GraphQL query to AniList."""
|
|
26
|
+
try:
|
|
27
|
+
response = requests.post(
|
|
28
|
+
self.url,
|
|
29
|
+
json={"query": query, "variables": variables},
|
|
30
|
+
headers=self.headers,
|
|
31
|
+
timeout=10,
|
|
32
|
+
)
|
|
33
|
+
response.raise_for_status()
|
|
34
|
+
data = response.json()
|
|
35
|
+
if "errors" in data:
|
|
36
|
+
print(f"AniList API Error: {data['errors']}")
|
|
37
|
+
return None
|
|
38
|
+
return data["data"]
|
|
39
|
+
except Exception as e:
|
|
40
|
+
print(f"Request Error: {e}")
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
def validate_token(self) -> Optional[Dict[str, Any]]:
|
|
44
|
+
"""Check if the current token is valid and return user info."""
|
|
45
|
+
query = """
|
|
46
|
+
query {
|
|
47
|
+
Viewer {
|
|
48
|
+
id
|
|
49
|
+
name
|
|
50
|
+
avatar {
|
|
51
|
+
large
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
"""
|
|
56
|
+
data = self._query(query)
|
|
57
|
+
if data and data.get("Viewer"):
|
|
58
|
+
return data["Viewer"]
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
def search_media(self, search: str) -> List[Dict[str, Any]]:
|
|
62
|
+
"""Search for an anime/manga by title."""
|
|
63
|
+
query = """
|
|
64
|
+
query ($search: String) {
|
|
65
|
+
Page(page: 1, perPage: 10) {
|
|
66
|
+
media(search: $search, type: ANIME, sort: SEARCH_MATCH) {
|
|
67
|
+
id
|
|
68
|
+
title {
|
|
69
|
+
romaji
|
|
70
|
+
english
|
|
71
|
+
native
|
|
72
|
+
}
|
|
73
|
+
coverImage {
|
|
74
|
+
medium
|
|
75
|
+
}
|
|
76
|
+
format
|
|
77
|
+
episodes
|
|
78
|
+
status
|
|
79
|
+
seasonYear
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
"""
|
|
84
|
+
variables = {"search": search}
|
|
85
|
+
data = self._query(query, variables)
|
|
86
|
+
if data and data.get("Page") and data.get("Page", {}).get("media"):
|
|
87
|
+
return data["Page"]["media"]
|
|
88
|
+
return []
|
|
89
|
+
|
|
90
|
+
def get_media_status(self, media_id: int, user_id: int) -> Optional[Dict[str, Any]]:
|
|
91
|
+
"""Get the user's current status for a specific media."""
|
|
92
|
+
query = """
|
|
93
|
+
query ($mediaId: Int, $userId: Int) {
|
|
94
|
+
MediaList(mediaId: $mediaId, userId: $userId) {
|
|
95
|
+
id
|
|
96
|
+
status
|
|
97
|
+
progress
|
|
98
|
+
score
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
"""
|
|
102
|
+
variables = {"mediaId": media_id, "userId": user_id}
|
|
103
|
+
# Note: MediaList returns 404/null if not found, we need to handle that gracefully
|
|
104
|
+
try:
|
|
105
|
+
data = self._query(
|
|
106
|
+
query, variables
|
|
107
|
+
) # This might fail if not found depending on API behavior
|
|
108
|
+
if data:
|
|
109
|
+
return data.get("MediaList")
|
|
110
|
+
except Exception:
|
|
111
|
+
pass
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
def update_progress(self, media_id: int, progress: int) -> bool:
|
|
115
|
+
"""Update the progress for a media item. Also sets status to CURRENT if not already."""
|
|
116
|
+
query = """
|
|
117
|
+
mutation ($mediaId: Int, $progress: Int, $status: MediaListStatus) {
|
|
118
|
+
SaveMediaListEntry(mediaId: $mediaId, progress: $progress, status: $status) {
|
|
119
|
+
id
|
|
120
|
+
progress
|
|
121
|
+
status
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
"""
|
|
125
|
+
# We enforce status to CURRENT whenever we update progress,
|
|
126
|
+
# unless it's already completed (logic could be refined)
|
|
127
|
+
variables = {"mediaId": media_id, "progress": progress, "status": "CURRENT"}
|
|
128
|
+
data = self._query(query, variables)
|
|
129
|
+
return data is not None and "SaveMediaListEntry" in data
|
|
130
|
+
|
|
131
|
+
def get_user_watching(self, user_id: int) -> List[Dict[str, Any]]:
|
|
132
|
+
"""Get the user's current watching list."""
|
|
133
|
+
query = """
|
|
134
|
+
query ($userId: Int) {
|
|
135
|
+
MediaListCollection(userId: $userId, type: ANIME, status: CURRENT, sort: UPDATED_TIME_DESC) {
|
|
136
|
+
lists {
|
|
137
|
+
entries {
|
|
138
|
+
mediaId
|
|
139
|
+
progress
|
|
140
|
+
media {
|
|
141
|
+
title {
|
|
142
|
+
romaji
|
|
143
|
+
english
|
|
144
|
+
}
|
|
145
|
+
coverImage {
|
|
146
|
+
medium
|
|
147
|
+
}
|
|
148
|
+
format
|
|
149
|
+
episodes
|
|
150
|
+
seasonYear
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
"""
|
|
157
|
+
variables = {"userId": user_id}
|
|
158
|
+
data = self._query(query, variables)
|
|
159
|
+
if (
|
|
160
|
+
data
|
|
161
|
+
and data.get("MediaListCollection")
|
|
162
|
+
and data["MediaListCollection"].get("lists")
|
|
163
|
+
):
|
|
164
|
+
# Flatten lists (there should be only one for CURRENT usually, but structure is a list of lists)
|
|
165
|
+
entries = []
|
|
166
|
+
for lst in data["MediaListCollection"]["lists"]:
|
|
167
|
+
entries.extend(lst.get("entries", []))
|
|
168
|
+
return entries
|
|
169
|
+
return []
|
|
170
|
+
|
|
171
|
+
def get_media_with_relations(self, media_id: int) -> Optional[Dict[str, Any]]:
|
|
172
|
+
"""Get media details including episode count and relations (sequels)."""
|
|
173
|
+
query = """
|
|
174
|
+
query ($mediaId: Int) {
|
|
175
|
+
Media(id: $mediaId) {
|
|
176
|
+
id
|
|
177
|
+
title {
|
|
178
|
+
romaji
|
|
179
|
+
english
|
|
180
|
+
}
|
|
181
|
+
episodes
|
|
182
|
+
relations {
|
|
183
|
+
edges {
|
|
184
|
+
relationType
|
|
185
|
+
node {
|
|
186
|
+
id
|
|
187
|
+
title {
|
|
188
|
+
romaji
|
|
189
|
+
english
|
|
190
|
+
}
|
|
191
|
+
episodes
|
|
192
|
+
format
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
"""
|
|
199
|
+
variables = {"mediaId": media_id}
|
|
200
|
+
data = self._query(query, variables)
|
|
201
|
+
if data and data.get("Media"):
|
|
202
|
+
return data["Media"]
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
# Global instance
|
|
207
|
+
anilist_client = AniListClient()
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import readchar
|
|
3
|
+
import re
|
|
4
|
+
from rich.console import Console, Group
|
|
5
|
+
from rich.panel import Panel
|
|
6
|
+
from rich.text import Text
|
|
7
|
+
from rich.live import Live
|
|
8
|
+
from rich import print as rprint
|
|
9
|
+
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def clear_screen():
|
|
14
|
+
"""Clear the terminal screen."""
|
|
15
|
+
os.system("cls" if os.name == "nt" else "clear")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_user_input(prompt: str, default: str = None) -> str:
|
|
19
|
+
"""
|
|
20
|
+
Get user input with a styled prompt.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
prompt: The prompt text to display
|
|
24
|
+
default: The default value to display
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
The user's input as a string
|
|
28
|
+
"""
|
|
29
|
+
styled_prompt = Text(f"\n❯ {prompt}: ", style="bold cyan")
|
|
30
|
+
console.print(styled_prompt, end="")
|
|
31
|
+
return input().strip() or default
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def pause():
|
|
35
|
+
"""Wait for user input before continuing."""
|
|
36
|
+
console.input("\n[dim]Press Enter to continue...[/dim]")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def select_from_list(options: list[str], prompt: str, default_index: int = 0) -> int:
|
|
40
|
+
"""
|
|
41
|
+
Display an interactive menu where users can navigate with arrow keys.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
options: List of options to display
|
|
45
|
+
prompt: Header text for the menu
|
|
46
|
+
default_index: Index to select by default
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Index of the selected option (0-based)
|
|
50
|
+
"""
|
|
51
|
+
selected_index = max(0, min(default_index, len(options) - 1))
|
|
52
|
+
start_index = 0
|
|
53
|
+
|
|
54
|
+
def generate_renderable():
|
|
55
|
+
nonlocal start_index
|
|
56
|
+
|
|
57
|
+
# Calculate dynamic window size based on terminal height
|
|
58
|
+
term_height = console.size.height
|
|
59
|
+
# Reserve lines for prompt (2), header/spacing (2), arrows (2) -> ~6 lines reserve
|
|
60
|
+
reserved_lines = 6
|
|
61
|
+
available_height = max(3, term_height - reserved_lines)
|
|
62
|
+
window_size = min(len(options), available_height)
|
|
63
|
+
|
|
64
|
+
# Adjust start_index to keep selected_index in view
|
|
65
|
+
if selected_index < start_index:
|
|
66
|
+
start_index = selected_index
|
|
67
|
+
elif selected_index >= start_index + window_size:
|
|
68
|
+
start_index = selected_index - window_size + 1
|
|
69
|
+
|
|
70
|
+
# Ensure start_index is valid
|
|
71
|
+
start_index = max(0, min(start_index, len(options) - window_size))
|
|
72
|
+
end_index = min(len(options), start_index + window_size)
|
|
73
|
+
|
|
74
|
+
lines = [Text(f"\n❯ {prompt}", style="bold cyan")]
|
|
75
|
+
|
|
76
|
+
# Up arrow indicator
|
|
77
|
+
if start_index > 0:
|
|
78
|
+
lines.append(Text(" ↑ ...", style="dim"))
|
|
79
|
+
|
|
80
|
+
for idx in range(start_index, end_index):
|
|
81
|
+
option = options[idx]
|
|
82
|
+
if idx == selected_index:
|
|
83
|
+
lines.append(Text(f" ● {option}", style="green bold"))
|
|
84
|
+
else:
|
|
85
|
+
lines.append(Text(f" {option}", style="white"))
|
|
86
|
+
|
|
87
|
+
# Down arrow indicator
|
|
88
|
+
if end_index < len(options):
|
|
89
|
+
lines.append(Text(" ↓ ...", style="dim"))
|
|
90
|
+
|
|
91
|
+
return Group(*lines)
|
|
92
|
+
|
|
93
|
+
with Live(generate_renderable(), refresh_per_second=10, transient=True) as live:
|
|
94
|
+
while True:
|
|
95
|
+
key = readchar.readkey()
|
|
96
|
+
|
|
97
|
+
if key == readchar.key.UP:
|
|
98
|
+
selected_index = (selected_index - 1) % len(options)
|
|
99
|
+
live.update(generate_renderable())
|
|
100
|
+
elif key == readchar.key.DOWN:
|
|
101
|
+
selected_index = (selected_index + 1) % len(options)
|
|
102
|
+
live.update(generate_renderable())
|
|
103
|
+
elif key == readchar.key.ENTER:
|
|
104
|
+
break
|
|
105
|
+
elif key == readchar.key.CTRL_C:
|
|
106
|
+
raise KeyboardInterrupt("Menu cancelled by user")
|
|
107
|
+
|
|
108
|
+
console.print(
|
|
109
|
+
f"\n[bold cyan]❯ {prompt}[/bold cyan] [green]{options[selected_index]}[/green]"
|
|
110
|
+
)
|
|
111
|
+
return selected_index
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def print_header(text: str):
|
|
115
|
+
"""
|
|
116
|
+
Print a styled header with a decorative panel.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
text: Header text to display
|
|
120
|
+
"""
|
|
121
|
+
console.print()
|
|
122
|
+
panel = Panel(
|
|
123
|
+
Text(text, style="bold white", justify="center"),
|
|
124
|
+
style="cyan",
|
|
125
|
+
border_style="bright_cyan",
|
|
126
|
+
padding=(0, 2),
|
|
127
|
+
)
|
|
128
|
+
console.print(panel)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def print_success(message: str):
|
|
132
|
+
"""
|
|
133
|
+
Print a success message with a green checkmark.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
message: Success message to display
|
|
137
|
+
"""
|
|
138
|
+
console.print(f"[green]✓[/green] {message}")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def print_error(message: str):
|
|
142
|
+
"""
|
|
143
|
+
Print an error message with a red X.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
message: Error message to display
|
|
147
|
+
"""
|
|
148
|
+
console.print(f"[red]✗[/red] {message}")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def print_info(message: str):
|
|
152
|
+
"""
|
|
153
|
+
Print an info message with a blue icon.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
message: Info message to display
|
|
157
|
+
"""
|
|
158
|
+
console.print(f"[blue]ℹ[/blue] {message}")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def print_warning(message: str):
|
|
162
|
+
"""
|
|
163
|
+
Print a warning message with a yellow icon.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
message: Warning message to display
|
|
167
|
+
"""
|
|
168
|
+
console.print(f"[yellow]⚠[/yellow] {message}")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def clean_title(title: str) -> str:
|
|
172
|
+
"""
|
|
173
|
+
Remove season and part indicators from a title to help with search.
|
|
174
|
+
Example: "One Piece Season 4" -> "One Piece"
|
|
175
|
+
"""
|
|
176
|
+
# Remove Season X, S2, Part 2, etc. (case insensitive)
|
|
177
|
+
# Common patterns: Season 1, S1, Part 1, Cour 1, etc.
|
|
178
|
+
patterns = [
|
|
179
|
+
r"\s+Season\s+\d+",
|
|
180
|
+
r"\s+S\d+",
|
|
181
|
+
r"\s+Part\s+\d+",
|
|
182
|
+
r"\s+Cour\s+\d+",
|
|
183
|
+
r"\s+\d+(st|nd|rd|th)\s+Season",
|
|
184
|
+
r"\s+-\s+\d+", # Sometimes title - 2
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
cleaned = title
|
|
188
|
+
for pattern in patterns:
|
|
189
|
+
cleaned = re.sub(pattern, "", cleaned, flags=re.IGNORECASE)
|
|
190
|
+
|
|
191
|
+
return cleaned.strip()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
from curl_cffi import requests
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def strip_json_comments(json_str: str) -> str:
|
|
7
|
+
"""
|
|
8
|
+
Remove // and /* */ comments from JSON string.
|
|
9
|
+
"""
|
|
10
|
+
# Remove // comments
|
|
11
|
+
json_str = re.sub(r"(?<!:)\/\/.*$", "", json_str, flags=re.MULTILINE)
|
|
12
|
+
# Remove /* */ comments
|
|
13
|
+
json_str = re.sub(r"\/\*.*?\*\/", "", json_str, flags=re.DOTALL)
|
|
14
|
+
return json_str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def strip_trailing_commas(json_str: str) -> str:
|
|
18
|
+
"""
|
|
19
|
+
Remove trailing commas from JSON objects and arrays.
|
|
20
|
+
"""
|
|
21
|
+
return re.sub(r",\s*([\]}])", r"\1", json_str)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def load_remote_jsonc(url: str, default: dict) -> dict:
|
|
25
|
+
"""
|
|
26
|
+
Fetch a remote JSONC file, strip comments, and parse it.
|
|
27
|
+
Returns the default dictionary if fetching or parsing fails.
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
response = requests.get(url, impersonate="chrome", timeout=5)
|
|
31
|
+
response.raise_for_status()
|
|
32
|
+
|
|
33
|
+
clean_json = strip_json_comments(response.text)
|
|
34
|
+
clean_json = strip_trailing_commas(clean_json)
|
|
35
|
+
|
|
36
|
+
return json.loads(clean_json)
|
|
37
|
+
except Exception as e:
|
|
38
|
+
print(f"Warning: Failed to load remote config from {url}: {e}")
|
|
39
|
+
return default
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def load_local_jsonc(file_path: str, default: dict = None) -> dict:
|
|
43
|
+
"""
|
|
44
|
+
Load a local JSONC file, strip comments, and parse it.
|
|
45
|
+
Returns default if the file is missing or invalid.
|
|
46
|
+
"""
|
|
47
|
+
import os
|
|
48
|
+
|
|
49
|
+
if not os.path.exists(file_path):
|
|
50
|
+
return default or {}
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
54
|
+
content = f.read()
|
|
55
|
+
|
|
56
|
+
clean_json = strip_json_comments(content)
|
|
57
|
+
clean_json = strip_trailing_commas(clean_json)
|
|
58
|
+
|
|
59
|
+
return json.loads(clean_json)
|
|
60
|
+
except Exception as e:
|
|
61
|
+
print(f"Warning: Failed to load local config from {file_path}: {e}")
|
|
62
|
+
return default or {}
|
freeflix_cli/defaults.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Default configurations acting as fallback
|
|
2
|
+
# These values are used if remote configuration cannot be loaded
|
|
3
|
+
|
|
4
|
+
DEFAULT_PLAYERS = {
|
|
5
|
+
"wishonly": {
|
|
6
|
+
"type": "default",
|
|
7
|
+
"referrer": "full",
|
|
8
|
+
"alt-used": True,
|
|
9
|
+
"sec_headers": "Sec-Fetch-Dest:empty;Sec-Fetch-Mode:cors;Sec-Fetch-Site:cross-site;Content-Cache: no-cache",
|
|
10
|
+
"mode": "proxy",
|
|
11
|
+
},
|
|
12
|
+
"hgbazooka": {"type": "default"},
|
|
13
|
+
"hailindihg": {"type": "default"},
|
|
14
|
+
"gradehgplus": {"type": "default"},
|
|
15
|
+
"taylorplayer": {"type": "default"},
|
|
16
|
+
"vidmoly": {"type": "vidmoly"},
|
|
17
|
+
# "oneupload": {"type": "default"},
|
|
18
|
+
"tipfly": {"type": "default"},
|
|
19
|
+
# "luluvdoo": {
|
|
20
|
+
# "type": "b",
|
|
21
|
+
# "sec_headers": "Sec-Fetch-Dest:empty;Sec-Fetch-Mode:cors;Sec-Fetch-Site:cross-site",
|
|
22
|
+
# },
|
|
23
|
+
# "luluvdo": {
|
|
24
|
+
# "type": "b",
|
|
25
|
+
# "sec_headers": False,
|
|
26
|
+
# },
|
|
27
|
+
# "lulustream": {
|
|
28
|
+
# "type": "b",
|
|
29
|
+
# "sec_headers": "Sec-Fetch-Dest:empty;Sec-Fetch-Mode:cors;Sec-Fetch-Site:cross-site",
|
|
30
|
+
# },
|
|
31
|
+
"ups2up": {"type": "default"},
|
|
32
|
+
"ico3c": {"type": "default"},
|
|
33
|
+
"fsvid": {"type": "default", "m3u8-extractor": {"no-header": True}},
|
|
34
|
+
"darkibox": {"type": "default"},
|
|
35
|
+
"minochinos": {"type": "default"},
|
|
36
|
+
"movearnpre": {
|
|
37
|
+
"type": "default",
|
|
38
|
+
"referrer": "full",
|
|
39
|
+
"alt-used": False,
|
|
40
|
+
"sec_headers": "Sec-Fetch-Dest:empty;Sec-Fetch-Mode:cors;Sec-Fetch-Site:same-origin",
|
|
41
|
+
},
|
|
42
|
+
"smoothpre": {
|
|
43
|
+
"type": "default",
|
|
44
|
+
"referrer": "full",
|
|
45
|
+
"alt-used": True,
|
|
46
|
+
"sec_headers": "Sec-Fetch-Dest:empty;Sec-Fetch-Mode:cors;Sec-Fetch-Site:cross-site;Content-Cache: no-cache",
|
|
47
|
+
"mode": "proxy",
|
|
48
|
+
},
|
|
49
|
+
"vidhideplus": {"type": "default"},
|
|
50
|
+
"dinisglows": {
|
|
51
|
+
"type": "default",
|
|
52
|
+
"referrer": "full",
|
|
53
|
+
"alt-used": True,
|
|
54
|
+
"sec_headers": "Sec-Fetch-Dest:empty;Sec-Fetch-Mode:cors;Sec-Fetch-Site:same-origin",
|
|
55
|
+
},
|
|
56
|
+
"mivalyo": {"type": "default"},
|
|
57
|
+
"dingtezuni": {"type": "default"},
|
|
58
|
+
"bingezove": {"type": "default"},
|
|
59
|
+
"vidzy": {"type": "default"},
|
|
60
|
+
"ok.ru": {"type": "default"},
|
|
61
|
+
"videzz": {
|
|
62
|
+
"type": "vidoza",
|
|
63
|
+
"mode": "proxy",
|
|
64
|
+
"no-header": True,
|
|
65
|
+
"ext": "mp4",
|
|
66
|
+
},
|
|
67
|
+
"vidoza": {
|
|
68
|
+
"type": "vidoza",
|
|
69
|
+
"mode": "proxy",
|
|
70
|
+
"no-header": True,
|
|
71
|
+
"ext": "mp4",
|
|
72
|
+
},
|
|
73
|
+
"sendvid": {"type": "sendvid", "mode": "proxy", "ext": "mp4"},
|
|
74
|
+
"sibnet": {
|
|
75
|
+
"type": "sibnet",
|
|
76
|
+
"mode": "proxy",
|
|
77
|
+
"ext": "mp4",
|
|
78
|
+
"referrer": "full",
|
|
79
|
+
"no-header": True,
|
|
80
|
+
},
|
|
81
|
+
"uqload": {
|
|
82
|
+
"type": "uqload",
|
|
83
|
+
"sec_headers": "Sec-Fetch-Dest:video;Sec-Fetch-Mode:no-cors;Sec-Fetch-Site:same-site",
|
|
84
|
+
"ext": "mp4",
|
|
85
|
+
},
|
|
86
|
+
"filemoon": {
|
|
87
|
+
"type": "filemoon",
|
|
88
|
+
"referrer": "https://ico3c.com/",
|
|
89
|
+
"no-header": True,
|
|
90
|
+
},
|
|
91
|
+
"bysebuho": {
|
|
92
|
+
"type": "filemoon",
|
|
93
|
+
"referrer": "https://ico3c.com/",
|
|
94
|
+
"no-header": True,
|
|
95
|
+
},
|
|
96
|
+
"bysekoze": {
|
|
97
|
+
"type": "filemoon",
|
|
98
|
+
"referrer": "https://ico3c.com/",
|
|
99
|
+
"no-header": True,
|
|
100
|
+
},
|
|
101
|
+
"kakaflix": {"type": "kakaflix"},
|
|
102
|
+
# "myvidplay": {"type": "myvidplay", "referrer": "https://myvidplay.com/"},
|
|
103
|
+
"embed4me": {"type": "embed4me"},
|
|
104
|
+
"coflix.upn": {"type": "embed4me"},
|
|
105
|
+
"veev": {"type": "veev", "ext": "mp4"},
|
|
106
|
+
"xtremestream": {"type": "xtremestream"},
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
DEFAULT_NEW_URL = {
|
|
110
|
+
"lulustream": "luluvdo",
|
|
111
|
+
"vidoza.net": "videzz.net",
|
|
112
|
+
"oneupload.to": "oneupload.net",
|
|
113
|
+
"uqload.cx": "uqload.is",
|
|
114
|
+
# Dinisglows Player
|
|
115
|
+
"mivalyo": "dinisglows",
|
|
116
|
+
"vidhideplus": "dinisglows",
|
|
117
|
+
"dingtezuni": "dinisglows",
|
|
118
|
+
# Vidmoly Player
|
|
119
|
+
"vidmoly.to": "vidmoly.biz",
|
|
120
|
+
"vidmoly.me": "vidmoly.biz",
|
|
121
|
+
"vidmoly.net": "vidmoly.biz",
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
DEFAULT_KAKAFLIX_PLAYERS = {
|
|
125
|
+
"moon2": "ico3c",
|
|
126
|
+
"viper": "ico3c",
|
|
127
|
+
# "tokyo": "myvidplay"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
DEFAULT_SOURCE_PORTAL = {
|
|
131
|
+
"french-stream": "https://french-stream.one",
|
|
132
|
+
"anime-sama": "https://animes-sama.fr",
|
|
133
|
+
"coflix": "https://coflix.fans",
|
|
134
|
+
"sudatchi": "https://sudatchi.com",
|
|
135
|
+
"animetsu": "https://animetsu.live",
|
|
136
|
+
"animetsu-api": "https://b.animetsu.live",
|
|
137
|
+
"animetsu-proxy": "https://ani.metsu.site/proxy",
|
|
138
|
+
"allanime-api": "https://api.allanime.day/api",
|
|
139
|
+
"allanime-referer": "https://allmanga.to",
|
|
140
|
+
"allanime-base": "https://allanime.day",
|
|
141
|
+
"anizone": "https://anizone.to",
|
|
142
|
+
"videasy": "https://api.videasy.net",
|
|
143
|
+
"videasy-referer": "https://cineby.gd",
|
|
144
|
+
"vidlink": "https://vidlink.pro",
|
|
145
|
+
"hexa": "https://themoviedb.hexa.su",
|
|
146
|
+
"hexa-referer": "https://hexa.su",
|
|
147
|
+
"multi-decrypt": "https://enc-dec.app/api",
|
|
148
|
+
"mapple": "https://mapple.uk",
|
|
149
|
+
"xpass": "https://play.xpass.top",
|
|
150
|
+
}
|
|
File without changes
|