avmc 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.
- avmc-0.1.0/PKG-INFO +105 -0
- avmc-0.1.0/README.md +93 -0
- avmc-0.1.0/__init__.py +5 -0
- avmc-0.1.0/__main__.py +5 -0
- avmc-0.1.0/avmc.egg-info/PKG-INFO +105 -0
- avmc-0.1.0/avmc.egg-info/SOURCES.txt +38 -0
- avmc-0.1.0/avmc.egg-info/dependency_links.txt +1 -0
- avmc-0.1.0/avmc.egg-info/entry_points.txt +2 -0
- avmc-0.1.0/avmc.egg-info/requires.txt +4 -0
- avmc-0.1.0/avmc.egg-info/top_level.txt +1 -0
- avmc-0.1.0/config.json +29 -0
- avmc-0.1.0/context.py +38 -0
- avmc-0.1.0/http_client.py +60 -0
- avmc-0.1.0/io_ops.py +279 -0
- avmc-0.1.0/main.py +51 -0
- avmc-0.1.0/models.py +21 -0
- avmc-0.1.0/numbering.py +35 -0
- avmc-0.1.0/pipeline.py +140 -0
- avmc-0.1.0/pyproject.toml +31 -0
- avmc-0.1.0/settings.py +115 -0
- avmc-0.1.0/setup.cfg +4 -0
- avmc-0.1.0/sources/__init__.py +7 -0
- avmc-0.1.0/sources/base.py +12 -0
- avmc-0.1.0/sources/javbus.py +145 -0
- avmc-0.1.0/tests/test_io_ops.py +114 -0
- avmc-0.1.0/tests/test_javbus.py +86 -0
- avmc-0.1.0/tests/test_nfo.py +47 -0
- avmc-0.1.0/tests/test_numbering.py +25 -0
- avmc-0.1.0/tests/test_pipeline.py +76 -0
avmc-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: avmc
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Standalone AV metadata capture tool
|
|
5
|
+
Author: BossaMelon
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: requests>=2.31
|
|
9
|
+
Requires-Dist: beautifulsoup4>=4.12
|
|
10
|
+
Requires-Dist: lxml>=5.0
|
|
11
|
+
Requires-Dist: Pillow>=10.0
|
|
12
|
+
|
|
13
|
+
# AVMC
|
|
14
|
+
|
|
15
|
+
A standalone AV metadata capture tool (current source: `javbus`) with a clean pipeline.
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
- Accepts a single video file or a directory path.
|
|
19
|
+
- If a directory is provided, recursively scans video files and processes them one by one.
|
|
20
|
+
- Detects subtitle-style filenames and appends `-C` to output names.
|
|
21
|
+
- Organizes output as `success_output_folder/Actor1,Actor2,Actor3/Number` (or `success_output_folder/Number` when no actor).
|
|
22
|
+
- Optional subtitle badge on poster image.
|
|
23
|
+
- Uses local `config.json` by default; no dependency on parent project config.
|
|
24
|
+
|
|
25
|
+
## Project Structure
|
|
26
|
+
- `main.py`: CLI entry
|
|
27
|
+
- `pipeline.py`: scan + process pipeline
|
|
28
|
+
- `sources/javbus.py`: metadata scraper
|
|
29
|
+
- `io_ops.py`: image download/crop/badge, NFO writing, file move
|
|
30
|
+
- `config.json`: runtime config
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
```bash
|
|
34
|
+
cd avmc
|
|
35
|
+
pip install -r requirements.txt
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
Run as module:
|
|
40
|
+
```bash
|
|
41
|
+
python -m avmc /path/to/video_or_dir
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Run as script:
|
|
45
|
+
```bash
|
|
46
|
+
python avmc/main.py /path/to/video_or_dir
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Options:
|
|
50
|
+
- `-c, --config`: config file path (default: `avmc/config.json`)
|
|
51
|
+
- `-p, --proxy`: temporary proxy override (higher priority than config/env)
|
|
52
|
+
- `--debug`: dump raw HTML to `.adc_debug/`; keep source video in place and create a symlink in output
|
|
53
|
+
|
|
54
|
+
## Config
|
|
55
|
+
Default config file: `avmc/config.json`
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"success_output_folder": "output",
|
|
60
|
+
"failed": {
|
|
61
|
+
"move_enabled": false,
|
|
62
|
+
"output_folder": "failed"
|
|
63
|
+
},
|
|
64
|
+
"proxy": {
|
|
65
|
+
"proxy": "",
|
|
66
|
+
"timeout": 10,
|
|
67
|
+
"retry": 3
|
|
68
|
+
},
|
|
69
|
+
"javbus": {
|
|
70
|
+
"cookie": "existmag=all"
|
|
71
|
+
},
|
|
72
|
+
"scan": {
|
|
73
|
+
"escape_folders": ["output"]
|
|
74
|
+
},
|
|
75
|
+
"subtitle_badge": {
|
|
76
|
+
"enabled": true,
|
|
77
|
+
"backup_enabled": true
|
|
78
|
+
},
|
|
79
|
+
"image": {
|
|
80
|
+
"jpeg_quality": 85,
|
|
81
|
+
"optimize": true,
|
|
82
|
+
"progressive": true
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Subtitle Detection
|
|
88
|
+
Subtitle flag is inferred from filename patterns (case-insensitive), including compact forms like:
|
|
89
|
+
- `ABC123C`
|
|
90
|
+
- `ABC123CH`
|
|
91
|
+
- `ABC123CHS`
|
|
92
|
+
- `ABC123CHT`
|
|
93
|
+
|
|
94
|
+
When detected:
|
|
95
|
+
- output number becomes `NUMBER-C`
|
|
96
|
+
- NFO adds `中文字幕` tag/genre
|
|
97
|
+
- poster badge can be applied when enabled
|
|
98
|
+
|
|
99
|
+
## Notes
|
|
100
|
+
- If scraping fails, source video is kept in place by default.
|
|
101
|
+
- Set `failed.move_enabled=true` to move failed files into `failed.output_folder`.
|
|
102
|
+
- Image host may return 403 depending on network/proxy/cookie status.
|
|
103
|
+
|
|
104
|
+
## Development
|
|
105
|
+
- See `AGENTS.md` for coding-agent execution rules in this folder.
|
avmc-0.1.0/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# AVMC
|
|
2
|
+
|
|
3
|
+
A standalone AV metadata capture tool (current source: `javbus`) with a clean pipeline.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- Accepts a single video file or a directory path.
|
|
7
|
+
- If a directory is provided, recursively scans video files and processes them one by one.
|
|
8
|
+
- Detects subtitle-style filenames and appends `-C` to output names.
|
|
9
|
+
- Organizes output as `success_output_folder/Actor1,Actor2,Actor3/Number` (or `success_output_folder/Number` when no actor).
|
|
10
|
+
- Optional subtitle badge on poster image.
|
|
11
|
+
- Uses local `config.json` by default; no dependency on parent project config.
|
|
12
|
+
|
|
13
|
+
## Project Structure
|
|
14
|
+
- `main.py`: CLI entry
|
|
15
|
+
- `pipeline.py`: scan + process pipeline
|
|
16
|
+
- `sources/javbus.py`: metadata scraper
|
|
17
|
+
- `io_ops.py`: image download/crop/badge, NFO writing, file move
|
|
18
|
+
- `config.json`: runtime config
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
```bash
|
|
22
|
+
cd avmc
|
|
23
|
+
pip install -r requirements.txt
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
Run as module:
|
|
28
|
+
```bash
|
|
29
|
+
python -m avmc /path/to/video_or_dir
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Run as script:
|
|
33
|
+
```bash
|
|
34
|
+
python avmc/main.py /path/to/video_or_dir
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Options:
|
|
38
|
+
- `-c, --config`: config file path (default: `avmc/config.json`)
|
|
39
|
+
- `-p, --proxy`: temporary proxy override (higher priority than config/env)
|
|
40
|
+
- `--debug`: dump raw HTML to `.adc_debug/`; keep source video in place and create a symlink in output
|
|
41
|
+
|
|
42
|
+
## Config
|
|
43
|
+
Default config file: `avmc/config.json`
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"success_output_folder": "output",
|
|
48
|
+
"failed": {
|
|
49
|
+
"move_enabled": false,
|
|
50
|
+
"output_folder": "failed"
|
|
51
|
+
},
|
|
52
|
+
"proxy": {
|
|
53
|
+
"proxy": "",
|
|
54
|
+
"timeout": 10,
|
|
55
|
+
"retry": 3
|
|
56
|
+
},
|
|
57
|
+
"javbus": {
|
|
58
|
+
"cookie": "existmag=all"
|
|
59
|
+
},
|
|
60
|
+
"scan": {
|
|
61
|
+
"escape_folders": ["output"]
|
|
62
|
+
},
|
|
63
|
+
"subtitle_badge": {
|
|
64
|
+
"enabled": true,
|
|
65
|
+
"backup_enabled": true
|
|
66
|
+
},
|
|
67
|
+
"image": {
|
|
68
|
+
"jpeg_quality": 85,
|
|
69
|
+
"optimize": true,
|
|
70
|
+
"progressive": true
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Subtitle Detection
|
|
76
|
+
Subtitle flag is inferred from filename patterns (case-insensitive), including compact forms like:
|
|
77
|
+
- `ABC123C`
|
|
78
|
+
- `ABC123CH`
|
|
79
|
+
- `ABC123CHS`
|
|
80
|
+
- `ABC123CHT`
|
|
81
|
+
|
|
82
|
+
When detected:
|
|
83
|
+
- output number becomes `NUMBER-C`
|
|
84
|
+
- NFO adds `中文字幕` tag/genre
|
|
85
|
+
- poster badge can be applied when enabled
|
|
86
|
+
|
|
87
|
+
## Notes
|
|
88
|
+
- If scraping fails, source video is kept in place by default.
|
|
89
|
+
- Set `failed.move_enabled=true` to move failed files into `failed.output_folder`.
|
|
90
|
+
- Image host may return 403 depending on network/proxy/cookie status.
|
|
91
|
+
|
|
92
|
+
## Development
|
|
93
|
+
- See `AGENTS.md` for coding-agent execution rules in this folder.
|
avmc-0.1.0/__init__.py
ADDED
avmc-0.1.0/__main__.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: avmc
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Standalone AV metadata capture tool
|
|
5
|
+
Author: BossaMelon
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: requests>=2.31
|
|
9
|
+
Requires-Dist: beautifulsoup4>=4.12
|
|
10
|
+
Requires-Dist: lxml>=5.0
|
|
11
|
+
Requires-Dist: Pillow>=10.0
|
|
12
|
+
|
|
13
|
+
# AVMC
|
|
14
|
+
|
|
15
|
+
A standalone AV metadata capture tool (current source: `javbus`) with a clean pipeline.
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
- Accepts a single video file or a directory path.
|
|
19
|
+
- If a directory is provided, recursively scans video files and processes them one by one.
|
|
20
|
+
- Detects subtitle-style filenames and appends `-C` to output names.
|
|
21
|
+
- Organizes output as `success_output_folder/Actor1,Actor2,Actor3/Number` (or `success_output_folder/Number` when no actor).
|
|
22
|
+
- Optional subtitle badge on poster image.
|
|
23
|
+
- Uses local `config.json` by default; no dependency on parent project config.
|
|
24
|
+
|
|
25
|
+
## Project Structure
|
|
26
|
+
- `main.py`: CLI entry
|
|
27
|
+
- `pipeline.py`: scan + process pipeline
|
|
28
|
+
- `sources/javbus.py`: metadata scraper
|
|
29
|
+
- `io_ops.py`: image download/crop/badge, NFO writing, file move
|
|
30
|
+
- `config.json`: runtime config
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
```bash
|
|
34
|
+
cd avmc
|
|
35
|
+
pip install -r requirements.txt
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
Run as module:
|
|
40
|
+
```bash
|
|
41
|
+
python -m avmc /path/to/video_or_dir
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Run as script:
|
|
45
|
+
```bash
|
|
46
|
+
python avmc/main.py /path/to/video_or_dir
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Options:
|
|
50
|
+
- `-c, --config`: config file path (default: `avmc/config.json`)
|
|
51
|
+
- `-p, --proxy`: temporary proxy override (higher priority than config/env)
|
|
52
|
+
- `--debug`: dump raw HTML to `.adc_debug/`; keep source video in place and create a symlink in output
|
|
53
|
+
|
|
54
|
+
## Config
|
|
55
|
+
Default config file: `avmc/config.json`
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"success_output_folder": "output",
|
|
60
|
+
"failed": {
|
|
61
|
+
"move_enabled": false,
|
|
62
|
+
"output_folder": "failed"
|
|
63
|
+
},
|
|
64
|
+
"proxy": {
|
|
65
|
+
"proxy": "",
|
|
66
|
+
"timeout": 10,
|
|
67
|
+
"retry": 3
|
|
68
|
+
},
|
|
69
|
+
"javbus": {
|
|
70
|
+
"cookie": "existmag=all"
|
|
71
|
+
},
|
|
72
|
+
"scan": {
|
|
73
|
+
"escape_folders": ["output"]
|
|
74
|
+
},
|
|
75
|
+
"subtitle_badge": {
|
|
76
|
+
"enabled": true,
|
|
77
|
+
"backup_enabled": true
|
|
78
|
+
},
|
|
79
|
+
"image": {
|
|
80
|
+
"jpeg_quality": 85,
|
|
81
|
+
"optimize": true,
|
|
82
|
+
"progressive": true
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Subtitle Detection
|
|
88
|
+
Subtitle flag is inferred from filename patterns (case-insensitive), including compact forms like:
|
|
89
|
+
- `ABC123C`
|
|
90
|
+
- `ABC123CH`
|
|
91
|
+
- `ABC123CHS`
|
|
92
|
+
- `ABC123CHT`
|
|
93
|
+
|
|
94
|
+
When detected:
|
|
95
|
+
- output number becomes `NUMBER-C`
|
|
96
|
+
- NFO adds `中文字幕` tag/genre
|
|
97
|
+
- poster badge can be applied when enabled
|
|
98
|
+
|
|
99
|
+
## Notes
|
|
100
|
+
- If scraping fails, source video is kept in place by default.
|
|
101
|
+
- Set `failed.move_enabled=true` to move failed files into `failed.output_folder`.
|
|
102
|
+
- Image host may return 403 depending on network/proxy/cookie status.
|
|
103
|
+
|
|
104
|
+
## Development
|
|
105
|
+
- See `AGENTS.md` for coding-agent execution rules in this folder.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
__init__.py
|
|
3
|
+
__main__.py
|
|
4
|
+
config.json
|
|
5
|
+
context.py
|
|
6
|
+
http_client.py
|
|
7
|
+
io_ops.py
|
|
8
|
+
main.py
|
|
9
|
+
models.py
|
|
10
|
+
numbering.py
|
|
11
|
+
pipeline.py
|
|
12
|
+
pyproject.toml
|
|
13
|
+
settings.py
|
|
14
|
+
./__init__.py
|
|
15
|
+
./__main__.py
|
|
16
|
+
./config.json
|
|
17
|
+
./context.py
|
|
18
|
+
./http_client.py
|
|
19
|
+
./io_ops.py
|
|
20
|
+
./main.py
|
|
21
|
+
./models.py
|
|
22
|
+
./numbering.py
|
|
23
|
+
./pipeline.py
|
|
24
|
+
./settings.py
|
|
25
|
+
avmc.egg-info/PKG-INFO
|
|
26
|
+
avmc.egg-info/SOURCES.txt
|
|
27
|
+
avmc.egg-info/dependency_links.txt
|
|
28
|
+
avmc.egg-info/entry_points.txt
|
|
29
|
+
avmc.egg-info/requires.txt
|
|
30
|
+
avmc.egg-info/top_level.txt
|
|
31
|
+
sources/__init__.py
|
|
32
|
+
sources/base.py
|
|
33
|
+
sources/javbus.py
|
|
34
|
+
tests/test_io_ops.py
|
|
35
|
+
tests/test_javbus.py
|
|
36
|
+
tests/test_nfo.py
|
|
37
|
+
tests/test_numbering.py
|
|
38
|
+
tests/test_pipeline.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
avmc
|
avmc-0.1.0/config.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"success_output_folder": "output",
|
|
3
|
+
"failed": {
|
|
4
|
+
"move_enabled": false,
|
|
5
|
+
"output_folder": "failed"
|
|
6
|
+
},
|
|
7
|
+
"proxy": {
|
|
8
|
+
"proxy": "",
|
|
9
|
+
"timeout": 10,
|
|
10
|
+
"retry": 3
|
|
11
|
+
},
|
|
12
|
+
"javbus": {
|
|
13
|
+
"cookie": "existmag=all"
|
|
14
|
+
},
|
|
15
|
+
"scan": {
|
|
16
|
+
"escape_folders": [
|
|
17
|
+
"output"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"subtitle_badge": {
|
|
21
|
+
"enabled": true,
|
|
22
|
+
"backup_enabled": true
|
|
23
|
+
},
|
|
24
|
+
"image": {
|
|
25
|
+
"jpeg_quality": 85,
|
|
26
|
+
"optimize": true,
|
|
27
|
+
"progressive": true
|
|
28
|
+
}
|
|
29
|
+
}
|
avmc-0.1.0/context.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from .http_client import HttpClient
|
|
6
|
+
from .settings import AppConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class AppContext:
|
|
11
|
+
config: AppConfig
|
|
12
|
+
http: HttpClient
|
|
13
|
+
debug: bool = False
|
|
14
|
+
debug_dir: Path = Path(".adc_debug")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def build_context(config_path: str, proxy_override: str | None = None, debug: bool = False) -> AppContext:
|
|
18
|
+
conf = AppConfig.from_path(config_path)
|
|
19
|
+
proxy_from_config, timeout, retry = conf.proxy()
|
|
20
|
+
|
|
21
|
+
if proxy_override is not None:
|
|
22
|
+
proxy = proxy_override.strip()
|
|
23
|
+
else:
|
|
24
|
+
proxy = _env_https_proxy() or proxy_from_config
|
|
25
|
+
|
|
26
|
+
http = HttpClient(proxy=proxy, timeout=timeout, retry=retry)
|
|
27
|
+
ctx = AppContext(config=conf, http=http, debug=debug)
|
|
28
|
+
if ctx.debug:
|
|
29
|
+
ctx.debug_dir.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
return ctx
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _env_https_proxy() -> str | None:
|
|
34
|
+
value = os.getenv("https_proxy") or os.getenv("HTTPS_PROXY")
|
|
35
|
+
if value is None:
|
|
36
|
+
return None
|
|
37
|
+
value = value.strip()
|
|
38
|
+
return value if value else None
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class HttpClient:
|
|
5
|
+
def __init__(self, proxy: str, timeout: int, retry: int):
|
|
6
|
+
self.proxy = proxy
|
|
7
|
+
self.timeout = timeout
|
|
8
|
+
self.retry = retry
|
|
9
|
+
self.proxies = _build_proxies(proxy)
|
|
10
|
+
self.session = requests.Session()
|
|
11
|
+
|
|
12
|
+
def get_text(self, url: str, headers: dict | None = None, cookies: dict | None = None) -> str:
|
|
13
|
+
resp = self.get_response(url, headers=headers, cookies=cookies)
|
|
14
|
+
resp.encoding = "utf-8"
|
|
15
|
+
return resp.text
|
|
16
|
+
|
|
17
|
+
def get_bytes(self, url: str, headers: dict | None = None, cookies: dict | None = None) -> bytes:
|
|
18
|
+
resp = self.get_response(url, headers=headers, cookies=cookies)
|
|
19
|
+
return resp.content
|
|
20
|
+
|
|
21
|
+
def get_response(self, url: str, headers: dict | None = None, cookies: dict | None = None) -> requests.Response:
|
|
22
|
+
return self._request("get", url, headers=headers, cookies=cookies)
|
|
23
|
+
|
|
24
|
+
def _request(
|
|
25
|
+
self,
|
|
26
|
+
method: str,
|
|
27
|
+
url: str,
|
|
28
|
+
headers: dict | None = None,
|
|
29
|
+
cookies: dict | None = None,
|
|
30
|
+
data: dict | None = None,
|
|
31
|
+
) -> requests.Response:
|
|
32
|
+
last_error = None
|
|
33
|
+
for i in range(self.retry):
|
|
34
|
+
try:
|
|
35
|
+
resp = self.session.request(
|
|
36
|
+
method=method,
|
|
37
|
+
url=url,
|
|
38
|
+
timeout=self.timeout,
|
|
39
|
+
headers=headers,
|
|
40
|
+
cookies=cookies,
|
|
41
|
+
data=data,
|
|
42
|
+
proxies=self.proxies,
|
|
43
|
+
)
|
|
44
|
+
resp.raise_for_status()
|
|
45
|
+
return resp
|
|
46
|
+
except requests.RequestException as err:
|
|
47
|
+
last_error = err
|
|
48
|
+
print(f"[-] Connect retry {i + 1}/{self.retry}: {err}")
|
|
49
|
+
raise RuntimeError(f"Request failed: {method.upper()} {url}") from last_error
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _build_proxies(proxy: str | None) -> dict[str, str] | None:
|
|
53
|
+
if not proxy:
|
|
54
|
+
return None
|
|
55
|
+
proxy_value = proxy.strip()
|
|
56
|
+
if not proxy_value:
|
|
57
|
+
return None
|
|
58
|
+
if "://" not in proxy_value:
|
|
59
|
+
proxy_value = f"http://{proxy_value}"
|
|
60
|
+
return {"http": proxy_value, "https": proxy_value}
|