fap-dl 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.
- fap_dl-0.1.0/LICENSE +21 -0
- fap_dl-0.1.0/PKG-INFO +183 -0
- fap_dl-0.1.0/README.md +169 -0
- fap_dl-0.1.0/pyproject.toml +27 -0
- fap_dl-0.1.0/setup.cfg +4 -0
- fap_dl-0.1.0/src/fap_dl/__init__.py +0 -0
- fap_dl-0.1.0/src/fap_dl/browser.py +194 -0
- fap_dl-0.1.0/src/fap_dl/cli.py +298 -0
- fap_dl-0.1.0/src/fap_dl/downloader.py +260 -0
- fap_dl-0.1.0/src/fap_dl/gallery.py +205 -0
- fap_dl-0.1.0/src/fap_dl.egg-info/PKG-INFO +183 -0
- fap_dl-0.1.0/src/fap_dl.egg-info/SOURCES.txt +14 -0
- fap_dl-0.1.0/src/fap_dl.egg-info/dependency_links.txt +1 -0
- fap_dl-0.1.0/src/fap_dl.egg-info/entry_points.txt +2 -0
- fap_dl-0.1.0/src/fap_dl.egg-info/requires.txt +2 -0
- fap_dl-0.1.0/src/fap_dl.egg-info/top_level.txt +1 -0
fap_dl-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Reveren
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
fap_dl-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fap-dl
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A command-line downloader for Fapello profiles.
|
|
5
|
+
Author: Michael Horton
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/Reveren/fap-dl
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: playwright>=1.55
|
|
12
|
+
Requires-Dist: requests>=2.32
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# fap-dl
|
|
16
|
+
|
|
17
|
+
A command-line downloader for images and videos from Fapello profiles.
|
|
18
|
+
|
|
19
|
+
`fap-dl` uses Chromium to discover the contents of a profile, then closes the
|
|
20
|
+
browser and downloads the media directly.
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- Downloads images and videos
|
|
25
|
+
- Automatically discovers lazy-loaded posts
|
|
26
|
+
- Detects video posts
|
|
27
|
+
- Streams downloads directly to disk
|
|
28
|
+
- Skips media that has already been downloaded
|
|
29
|
+
- Uses `.part` files for incomplete downloads
|
|
30
|
+
- Separates images and videos by default
|
|
31
|
+
- Supports combined output
|
|
32
|
+
- Supports image-only and video-only downloads
|
|
33
|
+
- Automatically installs the required Playwright Chromium browser when needed
|
|
34
|
+
|
|
35
|
+
## Requirements
|
|
36
|
+
|
|
37
|
+
- Python 3.10 or newer
|
|
38
|
+
- Internet connection
|
|
39
|
+
|
|
40
|
+
### Platform support
|
|
41
|
+
|
|
42
|
+
`fap-dl` is currently developed and tested on macOS.
|
|
43
|
+
|
|
44
|
+
The underlying Python, Playwright, Chromium, and Requests components are
|
|
45
|
+
cross-platform, so the program is expected to work on Linux and Windows as
|
|
46
|
+
well. Linux and Windows have not yet been tested with this project.
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
Download a profile:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
fap-dl PROFILE
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
For example:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
fap-dl diamondnips-1
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
You can also provide the full profile URL:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
fap-dl https://fapello.com/diamondnips-1/
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
By default, files are stored in:
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
~/Downloads/fap-dl/PROFILE/
|
|
72
|
+
├── images/
|
|
73
|
+
└── videos/
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Combined folder
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
fap-dl PROFILE --combined
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Images only
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
fap-dl PROFILE --images-only
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Videos only
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
fap-dl PROFILE --videos-only
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Custom output directory
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
fap-dl PROFILE --output ~/Desktop/media
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
This creates:
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
~/Desktop/media/PROFILE/
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Why does Chromium open?
|
|
107
|
+
|
|
108
|
+
Fapello is protected by Cloudflare and may challenge or block ordinary
|
|
109
|
+
automated HTTP requests. During development, headless Chromium was also unable
|
|
110
|
+
to access profile content reliably even when using a previously established
|
|
111
|
+
browser session.
|
|
112
|
+
|
|
113
|
+
For that reason, `fap-dl` briefly launches a normal, visible Chromium window.
|
|
114
|
+
This allows the profile to load in a regular browser environment and also
|
|
115
|
+
allows you to complete a Cloudflare verification challenge if one appears.
|
|
116
|
+
|
|
117
|
+
Chromium is used only for the discovery stage:
|
|
118
|
+
|
|
119
|
+
1. Chromium opens the requested profile.
|
|
120
|
+
2. `fap-dl` scrolls through the profile to discover all available posts.
|
|
121
|
+
3. Images and video posts are identified.
|
|
122
|
+
4. Browser session information is transferred to the downloader.
|
|
123
|
+
5. Chromium closes.
|
|
124
|
+
6. Images and videos are streamed directly to disk without keeping the
|
|
125
|
+
browser open.
|
|
126
|
+
|
|
127
|
+
Most runs should therefore require no interaction with the Chromium window.
|
|
128
|
+
If Fapello presents a verification challenge, complete it in Chromium and
|
|
129
|
+
follow the prompt in the terminal.
|
|
130
|
+
|
|
131
|
+
Browser data is retained in:
|
|
132
|
+
|
|
133
|
+
```text
|
|
134
|
+
~/.fap-dl/browser/
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
This allows browser session information to persist between runs and can reduce
|
|
138
|
+
the need for repeated verification.
|
|
139
|
+
|
|
140
|
+
## Downloads and existing files
|
|
141
|
+
|
|
142
|
+
Existing non-empty media files are skipped. This makes it possible to run
|
|
143
|
+
`fap-dl` against the same profile later and download newly discovered media
|
|
144
|
+
without downloading the entire collection again.
|
|
145
|
+
|
|
146
|
+
Downloads are streamed directly to disk rather than being held entirely in
|
|
147
|
+
memory.
|
|
148
|
+
|
|
149
|
+
While a file is downloading, it uses the `.part` extension:
|
|
150
|
+
|
|
151
|
+
```text
|
|
152
|
+
example.mp4.part
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
After the download completes successfully, it becomes:
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
example.mp4
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
This prevents an interrupted or incomplete download from being mistaken for a
|
|
162
|
+
completed media file.
|
|
163
|
+
|
|
164
|
+
## Video fallback
|
|
165
|
+
|
|
166
|
+
Video URLs can normally be determined directly from the profile gallery.
|
|
167
|
+
|
|
168
|
+
If a predicted video URL fails, `fap-dl` can reopen Chromium and inspect the
|
|
169
|
+
individual post to locate its actual MP4 source. Multiple failed videos are
|
|
170
|
+
handled in the same fallback browser session rather than opening a separate
|
|
171
|
+
browser for each one.
|
|
172
|
+
|
|
173
|
+
## Disclaimer
|
|
174
|
+
|
|
175
|
+
This project is not affiliated with or endorsed by Fapello.
|
|
176
|
+
|
|
177
|
+
Users are responsible for ensuring that their use of this software complies
|
|
178
|
+
with applicable laws, copyright restrictions, website terms, and the rights
|
|
179
|
+
of content creators.
|
|
180
|
+
|
|
181
|
+
## License
|
|
182
|
+
|
|
183
|
+
MIT
|
fap_dl-0.1.0/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# fap-dl
|
|
2
|
+
|
|
3
|
+
A command-line downloader for images and videos from Fapello profiles.
|
|
4
|
+
|
|
5
|
+
`fap-dl` uses Chromium to discover the contents of a profile, then closes the
|
|
6
|
+
browser and downloads the media directly.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- Downloads images and videos
|
|
11
|
+
- Automatically discovers lazy-loaded posts
|
|
12
|
+
- Detects video posts
|
|
13
|
+
- Streams downloads directly to disk
|
|
14
|
+
- Skips media that has already been downloaded
|
|
15
|
+
- Uses `.part` files for incomplete downloads
|
|
16
|
+
- Separates images and videos by default
|
|
17
|
+
- Supports combined output
|
|
18
|
+
- Supports image-only and video-only downloads
|
|
19
|
+
- Automatically installs the required Playwright Chromium browser when needed
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
- Python 3.10 or newer
|
|
24
|
+
- Internet connection
|
|
25
|
+
|
|
26
|
+
### Platform support
|
|
27
|
+
|
|
28
|
+
`fap-dl` is currently developed and tested on macOS.
|
|
29
|
+
|
|
30
|
+
The underlying Python, Playwright, Chromium, and Requests components are
|
|
31
|
+
cross-platform, so the program is expected to work on Linux and Windows as
|
|
32
|
+
well. Linux and Windows have not yet been tested with this project.
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
Download a profile:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
fap-dl PROFILE
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
For example:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
fap-dl diamondnips-1
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
You can also provide the full profile URL:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
fap-dl https://fapello.com/diamondnips-1/
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
By default, files are stored in:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
~/Downloads/fap-dl/PROFILE/
|
|
58
|
+
├── images/
|
|
59
|
+
└── videos/
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Combined folder
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
fap-dl PROFILE --combined
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Images only
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
fap-dl PROFILE --images-only
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Videos only
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
fap-dl PROFILE --videos-only
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Custom output directory
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
fap-dl PROFILE --output ~/Desktop/media
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This creates:
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
~/Desktop/media/PROFILE/
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Why does Chromium open?
|
|
93
|
+
|
|
94
|
+
Fapello is protected by Cloudflare and may challenge or block ordinary
|
|
95
|
+
automated HTTP requests. During development, headless Chromium was also unable
|
|
96
|
+
to access profile content reliably even when using a previously established
|
|
97
|
+
browser session.
|
|
98
|
+
|
|
99
|
+
For that reason, `fap-dl` briefly launches a normal, visible Chromium window.
|
|
100
|
+
This allows the profile to load in a regular browser environment and also
|
|
101
|
+
allows you to complete a Cloudflare verification challenge if one appears.
|
|
102
|
+
|
|
103
|
+
Chromium is used only for the discovery stage:
|
|
104
|
+
|
|
105
|
+
1. Chromium opens the requested profile.
|
|
106
|
+
2. `fap-dl` scrolls through the profile to discover all available posts.
|
|
107
|
+
3. Images and video posts are identified.
|
|
108
|
+
4. Browser session information is transferred to the downloader.
|
|
109
|
+
5. Chromium closes.
|
|
110
|
+
6. Images and videos are streamed directly to disk without keeping the
|
|
111
|
+
browser open.
|
|
112
|
+
|
|
113
|
+
Most runs should therefore require no interaction with the Chromium window.
|
|
114
|
+
If Fapello presents a verification challenge, complete it in Chromium and
|
|
115
|
+
follow the prompt in the terminal.
|
|
116
|
+
|
|
117
|
+
Browser data is retained in:
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
~/.fap-dl/browser/
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
This allows browser session information to persist between runs and can reduce
|
|
124
|
+
the need for repeated verification.
|
|
125
|
+
|
|
126
|
+
## Downloads and existing files
|
|
127
|
+
|
|
128
|
+
Existing non-empty media files are skipped. This makes it possible to run
|
|
129
|
+
`fap-dl` against the same profile later and download newly discovered media
|
|
130
|
+
without downloading the entire collection again.
|
|
131
|
+
|
|
132
|
+
Downloads are streamed directly to disk rather than being held entirely in
|
|
133
|
+
memory.
|
|
134
|
+
|
|
135
|
+
While a file is downloading, it uses the `.part` extension:
|
|
136
|
+
|
|
137
|
+
```text
|
|
138
|
+
example.mp4.part
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
After the download completes successfully, it becomes:
|
|
142
|
+
|
|
143
|
+
```text
|
|
144
|
+
example.mp4
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
This prevents an interrupted or incomplete download from being mistaken for a
|
|
148
|
+
completed media file.
|
|
149
|
+
|
|
150
|
+
## Video fallback
|
|
151
|
+
|
|
152
|
+
Video URLs can normally be determined directly from the profile gallery.
|
|
153
|
+
|
|
154
|
+
If a predicted video URL fails, `fap-dl` can reopen Chromium and inspect the
|
|
155
|
+
individual post to locate its actual MP4 source. Multiple failed videos are
|
|
156
|
+
handled in the same fallback browser session rather than opening a separate
|
|
157
|
+
browser for each one.
|
|
158
|
+
|
|
159
|
+
## Disclaimer
|
|
160
|
+
|
|
161
|
+
This project is not affiliated with or endorsed by Fapello.
|
|
162
|
+
|
|
163
|
+
Users are responsible for ensuring that their use of this software complies
|
|
164
|
+
with applicable laws, copyright restrictions, website terms, and the rights
|
|
165
|
+
of content creators.
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "fap-dl"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A command-line downloader for Fapello profiles."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Michael Horton" }
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"playwright>=1.55",
|
|
17
|
+
"requests>=2.32"
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
fap-dl = "fap_dl.cli:main"
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Repository = "https://github.com/Reveren/fap-dl"
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.packages.find]
|
|
27
|
+
where = ["src"]
|
fap_dl-0.1.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from playwright.sync_api import (
|
|
6
|
+
Error as PlaywrightError,
|
|
7
|
+
TimeoutError as PlaywrightTimeoutError,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from .gallery import get_video_fallback_url
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_browser_data_dir():
|
|
14
|
+
return Path.home() / ".fap-dl" / "browser"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def install_chromium():
|
|
18
|
+
print()
|
|
19
|
+
print("Chromium is required for browser discovery.")
|
|
20
|
+
print("Installing Chromium for fap-dl...")
|
|
21
|
+
print()
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
subprocess.run(
|
|
25
|
+
[
|
|
26
|
+
sys.executable,
|
|
27
|
+
"-m",
|
|
28
|
+
"playwright",
|
|
29
|
+
"install",
|
|
30
|
+
"chromium",
|
|
31
|
+
],
|
|
32
|
+
check=True,
|
|
33
|
+
)
|
|
34
|
+
except subprocess.CalledProcessError as exc:
|
|
35
|
+
raise RuntimeError(
|
|
36
|
+
"Unable to install Chromium automatically. "
|
|
37
|
+
"Run: python -m playwright install chromium"
|
|
38
|
+
) from exc
|
|
39
|
+
|
|
40
|
+
print()
|
|
41
|
+
print("Chromium installation complete.")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def launch_browser(p):
|
|
45
|
+
browser_data_dir = get_browser_data_dir()
|
|
46
|
+
browser_data_dir.mkdir(
|
|
47
|
+
parents=True,
|
|
48
|
+
exist_ok=True,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
return p.chromium.launch_persistent_context(
|
|
53
|
+
user_data_dir=str(browser_data_dir),
|
|
54
|
+
headless=False,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
except PlaywrightError as exc:
|
|
58
|
+
message = str(exc)
|
|
59
|
+
|
|
60
|
+
# Playwright gives an "Executable doesn't exist"
|
|
61
|
+
# error when its Chromium build has not yet been installed.
|
|
62
|
+
if "Executable doesn't exist" not in message:
|
|
63
|
+
raise
|
|
64
|
+
|
|
65
|
+
install_chromium()
|
|
66
|
+
|
|
67
|
+
return p.chromium.launch_persistent_context(
|
|
68
|
+
user_data_dir=str(browser_data_dir),
|
|
69
|
+
headless=False,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_page(context):
|
|
74
|
+
if context.pages:
|
|
75
|
+
return context.pages[0]
|
|
76
|
+
|
|
77
|
+
return context.new_page()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def open_profile(
|
|
81
|
+
p,
|
|
82
|
+
profile_url,
|
|
83
|
+
model,
|
|
84
|
+
post_pattern,
|
|
85
|
+
get_post_count,
|
|
86
|
+
):
|
|
87
|
+
context = launch_browser(p)
|
|
88
|
+
page = get_page(context)
|
|
89
|
+
|
|
90
|
+
print(f"\nOpening {profile_url}")
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
page.goto(
|
|
94
|
+
profile_url,
|
|
95
|
+
wait_until="domcontentloaded",
|
|
96
|
+
timeout=60000,
|
|
97
|
+
)
|
|
98
|
+
except PlaywrightTimeoutError:
|
|
99
|
+
print(
|
|
100
|
+
"Initial page load timed out; "
|
|
101
|
+
"checking page anyway."
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
page.wait_for_timeout(2000)
|
|
105
|
+
|
|
106
|
+
initial_count = get_post_count(
|
|
107
|
+
page,
|
|
108
|
+
model,
|
|
109
|
+
post_pattern,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
if initial_count == 0:
|
|
113
|
+
print()
|
|
114
|
+
print("No profile posts detected yet.")
|
|
115
|
+
print(
|
|
116
|
+
"If browser verification is displayed, "
|
|
117
|
+
"complete it in the browser."
|
|
118
|
+
)
|
|
119
|
+
print()
|
|
120
|
+
|
|
121
|
+
input(
|
|
122
|
+
"Press Enter here when the profile is visible..."
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
page.wait_for_timeout(1000)
|
|
126
|
+
|
|
127
|
+
initial_count = get_post_count(
|
|
128
|
+
page,
|
|
129
|
+
model,
|
|
130
|
+
post_pattern,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
if initial_count == 0:
|
|
134
|
+
context.close()
|
|
135
|
+
|
|
136
|
+
raise RuntimeError(
|
|
137
|
+
"Still unable to detect profile posts."
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return context, page
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def resolve_video_fallbacks(
|
|
144
|
+
p,
|
|
145
|
+
failed_videos,
|
|
146
|
+
):
|
|
147
|
+
if not failed_videos:
|
|
148
|
+
return {}
|
|
149
|
+
|
|
150
|
+
print()
|
|
151
|
+
print("Resolving failed video URLs...")
|
|
152
|
+
print("Opening browser for fallback lookup...")
|
|
153
|
+
|
|
154
|
+
context = launch_browser(p)
|
|
155
|
+
page = get_page(context)
|
|
156
|
+
|
|
157
|
+
resolved = {}
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
for index, item in enumerate(
|
|
161
|
+
failed_videos,
|
|
162
|
+
start=1,
|
|
163
|
+
):
|
|
164
|
+
post_number = item["post_number"]
|
|
165
|
+
post_url = item["post_url"]
|
|
166
|
+
|
|
167
|
+
print(
|
|
168
|
+
f"[{index}/{len(failed_videos)}] "
|
|
169
|
+
f"Post {post_number}",
|
|
170
|
+
end=" ",
|
|
171
|
+
flush=True,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
video_url = get_video_fallback_url(
|
|
176
|
+
page,
|
|
177
|
+
post_url,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if video_url:
|
|
181
|
+
resolved[post_number] = video_url
|
|
182
|
+
print("✓ found")
|
|
183
|
+
else:
|
|
184
|
+
print("✗ no MP4 source")
|
|
185
|
+
|
|
186
|
+
except Exception as exc:
|
|
187
|
+
print(f"✗ {exc}")
|
|
188
|
+
|
|
189
|
+
finally:
|
|
190
|
+
context.close()
|
|
191
|
+
|
|
192
|
+
print("Fallback browser closed.")
|
|
193
|
+
|
|
194
|
+
return resolved
|