article-backup 0.1.0__tar.gz → 0.2.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.

Potentially problematic release.


This version of article-backup might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: article-backup
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Локальный бэкап статей с Sponsr.ru и Boosty.to в Markdown с Hugo-интеграцией
5
5
  Author-email: Eugene Chaykin <eugene@chayk.in>
6
6
  License: Apache-2.0
@@ -31,6 +31,10 @@ Dynamic: license-file
31
31
 
32
32
  # Article Backup
33
33
 
34
+ [![PyPI version](https://badge.fury.io/py/article-backup.svg)](https://pypi.org/project/article-backup/)
35
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
36
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
37
+
34
38
  Скрипт для локального бэкапа статей с платформ **Sponsr.ru** и **Boosty.to**.
35
39
 
36
40
  Конвертирует статьи в Markdown с YAML-метаданными, скачивает изображения и другие медиафайлы, поддерживает инкрементальную синхронизацию.
@@ -41,15 +45,24 @@ Dynamic: license-file
41
45
  - Инкрементальные обновления — скачивает только новые статьи
42
46
  - Конвертация в Markdown с frontmatter (title, date, tags, source)
43
47
  - Локальное сохранение изображений, видео, аудио, PDF
48
+ - Гибкая фильтрация типов скачиваемых файлов (image, video, audio, document)
44
49
  - Сохранение ссылок на встроенные видео (Rutube, YouTube, Vimeo, VK, OK.ru)
45
50
  - Исправление внутренних ссылок между статьями
46
- - Интеграция с Hugo для просмотра в браузере
51
+ - Интеграция с Hugo для просмотра в браузере (поддержка тем, улучшенная типографика)
47
52
  - SQLite-индекс для быстрого поиска
48
53
 
49
54
  ## Установка
50
55
 
51
56
  Требуется **Python 3.10+**
52
57
 
58
+ ### Вариант 1: Через pip (рекомендуется)
59
+
60
+ ```bash
61
+ pip install article-backup
62
+ ```
63
+
64
+ ### Вариант 2: Из исходников
65
+
53
66
  ```bash
54
67
  git clone https://github.com/strannick-ru/article-backup.git
55
68
  cd article-backup
@@ -86,7 +99,8 @@ sources:
86
99
  - platform: sponsr
87
100
  author: pushkin
88
101
  display_name: "Пушкин. Проза"
89
-
102
+ asset_types: ["image", "document"] # Скачивать только картинки и документы
103
+
90
104
  - platform: boosty
91
105
  author: lermontov
92
106
  display_name: "Лермонтов. Стихи"
@@ -120,44 +134,56 @@ console.log("Cookie:\n" + cookie + "\n\nAuthorization:\nBearer " + auth.accessTo
120
134
  ### Синхронизация всех авторов
121
135
 
122
136
  ```bash
137
+ # Если установлено через pip
138
+ article-backup
139
+
140
+ # Или из исходников
123
141
  python backup.py
124
142
  ```
125
143
 
126
144
  ### Скачать один пост по URL
127
145
 
128
146
  ```bash
129
- python backup.py "https://sponsr.ru/author/12345/post-title/"
130
- python backup.py "https://boosty.to/author/posts/uuid"
147
+ article-backup "https://sponsr.ru/author/12345/post-title/"
148
+ article-backup "https://boosty.to/author/posts/uuid"
131
149
  ```
132
150
 
133
151
  ### Указать другой конфиг
134
152
 
135
153
  ```bash
136
- python backup.py -c /path/to/config.yaml
154
+ article-backup -c /path/to/config.yaml
137
155
  ```
138
156
 
139
157
  ## Docker
140
158
 
141
159
  Для серверов с устаревшим Python можно использовать Docker.
142
160
 
143
- ```bash
144
- # Сборка образа
145
- docker compose build
161
+ Для удобства используйте скрипт `run-docker.sh`, который автоматически подхватывает `output_dir` из вашего `config.yaml` и монтирует правильный volume.
146
162
 
147
- # Синхронизация всех авторов
148
- docker compose run --rm backup
163
+ ```bash
164
+ # Синхронизация + сборка сайта (рекомендуемый способ)
165
+ ./run-docker.sh
149
166
 
150
167
  # Скачать один пост
151
- docker compose run --rm backup "https://sponsr.ru/author/123/"
168
+ ./run-docker.sh "https://sponsr.ru/author/123/"
152
169
 
153
- # Сборка Hugo-сайта
154
- docker compose run --rm hugo
170
+ # Только пересборка сайта
171
+ ./run-docker.sh hugo
155
172
 
156
- # Полная синхронизация (backup + hugo)
157
- docker compose run --rm backup && docker compose run --rm hugo
173
+ # Пересборка контейнеров
174
+ ./run-docker.sh build
175
+ ```
176
+
177
+ ### Ручной запуск (Advanced)
178
+
179
+ Если вы не хотите использовать скрипт, можно запускать через `docker compose`, но нужно вручную указывать путь к бэкапам, если он отличается от `./backup`.
158
180
 
159
- # Пересборка после изменений кода
160
- docker compose build --no-cache
181
+ ```bash
182
+ # Если output_dir в конфиге = ./backup
183
+ docker compose run --rm backup
184
+
185
+ # Если output_dir другой
186
+ HOST_BACKUP_DIR=/path/to/data docker compose run --rm backup
161
187
  ```
162
188
 
163
189
  ### Cron
@@ -211,8 +237,11 @@ hugo:
211
237
  base_url: "https://example.com/" # URL сайта для production
212
238
  title: "Мой архив статей" # Заголовок сайта
213
239
  language_code: "ru" # Язык контента
240
+ default_theme: "sepia" # Тема по умолчанию: light, dark, sepia, gruvbox, everforest
214
241
  ```
215
242
 
243
+ Сайт поддерживает переключение тем "на лету" (кнопки в углу экрана). Выбор пользователя сохраняется в браузере.
244
+
216
245
  Если секция `hugo:` не указана, используются значения по умолчанию (`http://localhost:1313/`).
217
246
 
218
247
  ### RSS-ленты
@@ -1,5 +1,9 @@
1
1
  # Article Backup
2
2
 
3
+ [![PyPI version](https://badge.fury.io/py/article-backup.svg)](https://pypi.org/project/article-backup/)
4
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
5
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
6
+
3
7
  Скрипт для локального бэкапа статей с платформ **Sponsr.ru** и **Boosty.to**.
4
8
 
5
9
  Конвертирует статьи в Markdown с YAML-метаданными, скачивает изображения и другие медиафайлы, поддерживает инкрементальную синхронизацию.
@@ -10,15 +14,24 @@
10
14
  - Инкрементальные обновления — скачивает только новые статьи
11
15
  - Конвертация в Markdown с frontmatter (title, date, tags, source)
12
16
  - Локальное сохранение изображений, видео, аудио, PDF
17
+ - Гибкая фильтрация типов скачиваемых файлов (image, video, audio, document)
13
18
  - Сохранение ссылок на встроенные видео (Rutube, YouTube, Vimeo, VK, OK.ru)
14
19
  - Исправление внутренних ссылок между статьями
15
- - Интеграция с Hugo для просмотра в браузере
20
+ - Интеграция с Hugo для просмотра в браузере (поддержка тем, улучшенная типографика)
16
21
  - SQLite-индекс для быстрого поиска
17
22
 
18
23
  ## Установка
19
24
 
20
25
  Требуется **Python 3.10+**
21
26
 
27
+ ### Вариант 1: Через pip (рекомендуется)
28
+
29
+ ```bash
30
+ pip install article-backup
31
+ ```
32
+
33
+ ### Вариант 2: Из исходников
34
+
22
35
  ```bash
23
36
  git clone https://github.com/strannick-ru/article-backup.git
24
37
  cd article-backup
@@ -55,7 +68,8 @@ sources:
55
68
  - platform: sponsr
56
69
  author: pushkin
57
70
  display_name: "Пушкин. Проза"
58
-
71
+ asset_types: ["image", "document"] # Скачивать только картинки и документы
72
+
59
73
  - platform: boosty
60
74
  author: lermontov
61
75
  display_name: "Лермонтов. Стихи"
@@ -89,44 +103,56 @@ console.log("Cookie:\n" + cookie + "\n\nAuthorization:\nBearer " + auth.accessTo
89
103
  ### Синхронизация всех авторов
90
104
 
91
105
  ```bash
106
+ # Если установлено через pip
107
+ article-backup
108
+
109
+ # Или из исходников
92
110
  python backup.py
93
111
  ```
94
112
 
95
113
  ### Скачать один пост по URL
96
114
 
97
115
  ```bash
98
- python backup.py "https://sponsr.ru/author/12345/post-title/"
99
- python backup.py "https://boosty.to/author/posts/uuid"
116
+ article-backup "https://sponsr.ru/author/12345/post-title/"
117
+ article-backup "https://boosty.to/author/posts/uuid"
100
118
  ```
101
119
 
102
120
  ### Указать другой конфиг
103
121
 
104
122
  ```bash
105
- python backup.py -c /path/to/config.yaml
123
+ article-backup -c /path/to/config.yaml
106
124
  ```
107
125
 
108
126
  ## Docker
109
127
 
110
128
  Для серверов с устаревшим Python можно использовать Docker.
111
129
 
112
- ```bash
113
- # Сборка образа
114
- docker compose build
130
+ Для удобства используйте скрипт `run-docker.sh`, который автоматически подхватывает `output_dir` из вашего `config.yaml` и монтирует правильный volume.
115
131
 
116
- # Синхронизация всех авторов
117
- docker compose run --rm backup
132
+ ```bash
133
+ # Синхронизация + сборка сайта (рекомендуемый способ)
134
+ ./run-docker.sh
118
135
 
119
136
  # Скачать один пост
120
- docker compose run --rm backup "https://sponsr.ru/author/123/"
137
+ ./run-docker.sh "https://sponsr.ru/author/123/"
121
138
 
122
- # Сборка Hugo-сайта
123
- docker compose run --rm hugo
139
+ # Только пересборка сайта
140
+ ./run-docker.sh hugo
124
141
 
125
- # Полная синхронизация (backup + hugo)
126
- docker compose run --rm backup && docker compose run --rm hugo
142
+ # Пересборка контейнеров
143
+ ./run-docker.sh build
144
+ ```
145
+
146
+ ### Ручной запуск (Advanced)
147
+
148
+ Если вы не хотите использовать скрипт, можно запускать через `docker compose`, но нужно вручную указывать путь к бэкапам, если он отличается от `./backup`.
127
149
 
128
- # Пересборка после изменений кода
129
- docker compose build --no-cache
150
+ ```bash
151
+ # Если output_dir в конфиге = ./backup
152
+ docker compose run --rm backup
153
+
154
+ # Если output_dir другой
155
+ HOST_BACKUP_DIR=/path/to/data docker compose run --rm backup
130
156
  ```
131
157
 
132
158
  ### Cron
@@ -180,8 +206,11 @@ hugo:
180
206
  base_url: "https://example.com/" # URL сайта для production
181
207
  title: "Мой архив статей" # Заголовок сайта
182
208
  language_code: "ru" # Язык контента
209
+ default_theme: "sepia" # Тема по умолчанию: light, dark, sepia, gruvbox, everforest
183
210
  ```
184
211
 
212
+ Сайт поддерживает переключение тем "на лету" (кнопки в углу экрана). Выбор пользователя сохраняется в браузере.
213
+
185
214
  Если секция `hugo:` не указана, используются значения по умолчанию (`http://localhost:1313/`).
186
215
 
187
216
  ### RSS-ленты
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: article-backup
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Локальный бэкап статей с Sponsr.ru и Boosty.to в Markdown с Hugo-интеграцией
5
5
  Author-email: Eugene Chaykin <eugene@chayk.in>
6
6
  License: Apache-2.0
@@ -31,6 +31,10 @@ Dynamic: license-file
31
31
 
32
32
  # Article Backup
33
33
 
34
+ [![PyPI version](https://badge.fury.io/py/article-backup.svg)](https://pypi.org/project/article-backup/)
35
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
36
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
37
+
34
38
  Скрипт для локального бэкапа статей с платформ **Sponsr.ru** и **Boosty.to**.
35
39
 
36
40
  Конвертирует статьи в Markdown с YAML-метаданными, скачивает изображения и другие медиафайлы, поддерживает инкрементальную синхронизацию.
@@ -41,15 +45,24 @@ Dynamic: license-file
41
45
  - Инкрементальные обновления — скачивает только новые статьи
42
46
  - Конвертация в Markdown с frontmatter (title, date, tags, source)
43
47
  - Локальное сохранение изображений, видео, аудио, PDF
48
+ - Гибкая фильтрация типов скачиваемых файлов (image, video, audio, document)
44
49
  - Сохранение ссылок на встроенные видео (Rutube, YouTube, Vimeo, VK, OK.ru)
45
50
  - Исправление внутренних ссылок между статьями
46
- - Интеграция с Hugo для просмотра в браузере
51
+ - Интеграция с Hugo для просмотра в браузере (поддержка тем, улучшенная типографика)
47
52
  - SQLite-индекс для быстрого поиска
48
53
 
49
54
  ## Установка
50
55
 
51
56
  Требуется **Python 3.10+**
52
57
 
58
+ ### Вариант 1: Через pip (рекомендуется)
59
+
60
+ ```bash
61
+ pip install article-backup
62
+ ```
63
+
64
+ ### Вариант 2: Из исходников
65
+
53
66
  ```bash
54
67
  git clone https://github.com/strannick-ru/article-backup.git
55
68
  cd article-backup
@@ -86,7 +99,8 @@ sources:
86
99
  - platform: sponsr
87
100
  author: pushkin
88
101
  display_name: "Пушкин. Проза"
89
-
102
+ asset_types: ["image", "document"] # Скачивать только картинки и документы
103
+
90
104
  - platform: boosty
91
105
  author: lermontov
92
106
  display_name: "Лермонтов. Стихи"
@@ -120,44 +134,56 @@ console.log("Cookie:\n" + cookie + "\n\nAuthorization:\nBearer " + auth.accessTo
120
134
  ### Синхронизация всех авторов
121
135
 
122
136
  ```bash
137
+ # Если установлено через pip
138
+ article-backup
139
+
140
+ # Или из исходников
123
141
  python backup.py
124
142
  ```
125
143
 
126
144
  ### Скачать один пост по URL
127
145
 
128
146
  ```bash
129
- python backup.py "https://sponsr.ru/author/12345/post-title/"
130
- python backup.py "https://boosty.to/author/posts/uuid"
147
+ article-backup "https://sponsr.ru/author/12345/post-title/"
148
+ article-backup "https://boosty.to/author/posts/uuid"
131
149
  ```
132
150
 
133
151
  ### Указать другой конфиг
134
152
 
135
153
  ```bash
136
- python backup.py -c /path/to/config.yaml
154
+ article-backup -c /path/to/config.yaml
137
155
  ```
138
156
 
139
157
  ## Docker
140
158
 
141
159
  Для серверов с устаревшим Python можно использовать Docker.
142
160
 
143
- ```bash
144
- # Сборка образа
145
- docker compose build
161
+ Для удобства используйте скрипт `run-docker.sh`, который автоматически подхватывает `output_dir` из вашего `config.yaml` и монтирует правильный volume.
146
162
 
147
- # Синхронизация всех авторов
148
- docker compose run --rm backup
163
+ ```bash
164
+ # Синхронизация + сборка сайта (рекомендуемый способ)
165
+ ./run-docker.sh
149
166
 
150
167
  # Скачать один пост
151
- docker compose run --rm backup "https://sponsr.ru/author/123/"
168
+ ./run-docker.sh "https://sponsr.ru/author/123/"
152
169
 
153
- # Сборка Hugo-сайта
154
- docker compose run --rm hugo
170
+ # Только пересборка сайта
171
+ ./run-docker.sh hugo
155
172
 
156
- # Полная синхронизация (backup + hugo)
157
- docker compose run --rm backup && docker compose run --rm hugo
173
+ # Пересборка контейнеров
174
+ ./run-docker.sh build
175
+ ```
176
+
177
+ ### Ручной запуск (Advanced)
178
+
179
+ Если вы не хотите использовать скрипт, можно запускать через `docker compose`, но нужно вручную указывать путь к бэкапам, если он отличается от `./backup`.
158
180
 
159
- # Пересборка после изменений кода
160
- docker compose build --no-cache
181
+ ```bash
182
+ # Если output_dir в конфиге = ./backup
183
+ docker compose run --rm backup
184
+
185
+ # Если output_dir другой
186
+ HOST_BACKUP_DIR=/path/to/data docker compose run --rm backup
161
187
  ```
162
188
 
163
189
  ### Cron
@@ -211,8 +237,11 @@ hugo:
211
237
  base_url: "https://example.com/" # URL сайта для production
212
238
  title: "Мой архив статей" # Заголовок сайта
213
239
  language_code: "ru" # Язык контента
240
+ default_theme: "sepia" # Тема по умолчанию: light, dark, sepia, gruvbox, everforest
214
241
  ```
215
242
 
243
+ Сайт поддерживает переключение тем "на лету" (кнопки в углу экрана). Выбор пользователя сохраняется в браузере.
244
+
216
245
  Если секция `hugo:` не указана, используются значения по умолчанию (`http://localhost:1313/`).
217
246
 
218
247
  ### RSS-ленты
@@ -6,8 +6,9 @@ import argparse
6
6
  import os
7
7
  import sys
8
8
  from pathlib import Path
9
+ from typing import cast
9
10
 
10
- from src.config import Config, load_config, Source
11
+ from src.config import Config, load_config, Source, Platform
11
12
  from src.database import Database
12
13
  from src.utils import is_post_url, parse_post_url
13
14
  from src.sponsr import SponsorDownloader
@@ -25,6 +26,9 @@ languageCode = '{config.hugo.language_code}'
25
26
  title = '{config.hugo.title}'
26
27
  relativeURLs = true
27
28
 
29
+ [params]
30
+ default_theme = '{config.hugo.default_theme}'
31
+
28
32
  [markup.goldmark.renderer]
29
33
  unsafe = true
30
34
 
@@ -43,6 +47,12 @@ relativeURLs = true
43
47
 
44
48
  def ensure_site_content_link(config: Config):
45
49
  """Создаёт симлинк site/content → output_dir."""
50
+ # В Docker-среде (когда задан BACKUP_OUTPUT_DIR) мы не создаем симлинк,
51
+ # так как пути внутри контейнера (/app/backup) не совпадают с хостовыми.
52
+ # Симлинк должен создаваться скриптом запуска (run-docker.sh) на хосте.
53
+ if os.environ.get('BACKUP_OUTPUT_DIR'):
54
+ return
55
+
46
56
  site_content = Path('site/content')
47
57
 
48
58
  # Если уже правильный симлинк — ничего не делаем
@@ -89,11 +99,18 @@ def sync_all(config: Config, db: Database):
89
99
 
90
100
  def download_single_post(url: str, config: Config, db: Database):
91
101
  """Скачивает один пост по URL."""
92
- platform, author, post_id = parse_post_url(url)
102
+ platform_str, author, post_id = parse_post_url(url)
103
+ platform = cast(Platform, platform_str)
93
104
 
94
105
  # Создаём Source для этого автора
95
106
  source = Source(platform=platform, author=author, download_assets=True)
96
107
 
108
+ # Пытаемся найти настройки источника в конфиге
109
+ for src in config.sources:
110
+ if src.platform == platform and src.author == author:
111
+ source = src
112
+ break
113
+
97
114
  downloader = get_downloader(platform, config, source, db)
98
115
  downloader.download_single(post_id)
99
116
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "article-backup"
3
- version = "0.1.0"
3
+ version = "0.2.0"
4
4
  description = "Локальный бэкап статей с Sponsr.ru и Boosty.to в Markdown с Hugo-интеграцией"
5
5
  readme = "README.md"
6
6
  license = {text = "Apache-2.0"}
@@ -132,7 +132,7 @@ class BoostyDownloader(BaseDownloader):
132
132
  elif block_type == "ok_video":
133
133
  # ok.ru видео требует отдельной обработки
134
134
  # Пока сохраняем только превью, если есть
135
- preview = block.get("previewUrl", "")
135
+ preview = block.get("previewUrl") or block.get("preview") or ""
136
136
  if preview:
137
137
  assets.append({
138
138
  "url": preview,
@@ -6,6 +6,7 @@ from pathlib import Path
6
6
  from typing import Literal
7
7
 
8
8
  import yaml
9
+ import os
9
10
 
10
11
  Platform = Literal['sponsr', 'boosty']
11
12
 
@@ -16,6 +17,7 @@ class Source:
16
17
  author: str
17
18
  download_assets: bool = True
18
19
  display_name: str | None = None
20
+ asset_types: list[str] | None = None
19
21
 
20
22
  @dataclass
21
23
  class Auth:
@@ -29,6 +31,7 @@ class HugoConfig:
29
31
  base_url: str = "http://localhost:1313/"
30
32
  title: str = "Бэкап статей"
31
33
  language_code: str = "ru"
34
+ default_theme: str = "light"
32
35
 
33
36
 
34
37
  @dataclass
@@ -45,7 +48,11 @@ def load_config(config_path: Path) -> Config:
45
48
  data = yaml.safe_load(f)
46
49
 
47
50
  # output_dir
48
- output_dir = Path(data.get('output_dir', './backup'))
51
+ env_output_dir = os.environ.get('BACKUP_OUTPUT_DIR')
52
+ if env_output_dir:
53
+ output_dir = Path(env_output_dir)
54
+ else:
55
+ output_dir = Path(data.get('output_dir', './backup'))
49
56
 
50
57
  # auth
51
58
  auth_data = data.get('auth', {})
@@ -63,6 +70,7 @@ def load_config(config_path: Path) -> Config:
63
70
  author=src['author'],
64
71
  download_assets=src.get('download_assets', True),
65
72
  display_name=src.get('display_name'),
73
+ asset_types=src.get('asset_types'),
66
74
  ))
67
75
 
68
76
  # hugo
@@ -71,6 +79,7 @@ def load_config(config_path: Path) -> Config:
71
79
  base_url=hugo_data.get('base_url', HugoConfig.base_url),
72
80
  title=hugo_data.get('title', HugoConfig.title),
73
81
  language_code=hugo_data.get('language_code', HugoConfig.language_code),
82
+ default_theme=hugo_data.get('default_theme', HugoConfig.default_theme),
74
83
  )
75
84
 
76
85
  return Config(output_dir=output_dir, auth=auth, sources=sources, hugo=hugo)
@@ -61,7 +61,9 @@ def retry_request(
61
61
  time.sleep(delay)
62
62
  delay = min(delay * backoff_factor, max_delay)
63
63
 
64
- raise last_exception
64
+ if last_exception:
65
+ raise last_exception
66
+ raise Exception("Max retries exceeded")
65
67
 
66
68
 
67
69
  @dataclass
@@ -135,6 +137,7 @@ class BaseDownloader(ABC):
135
137
  def download_single(self, post_id: str):
136
138
  """Скачивает один пост по ID."""
137
139
  print(f"[{self.PLATFORM}] Скачивание поста {post_id}...")
140
+ self._create_index_files() # Создаем индексы, чтобы не было "Boosties"
138
141
  post = self.fetch_post(post_id)
139
142
  if post:
140
143
  self._save_post(post)
@@ -251,9 +254,9 @@ class BaseDownloader(ABC):
251
254
  def download_one(asset: dict) -> tuple[str, str | None]:
252
255
  url = asset["url"]
253
256
  try:
254
- # Предварительная проверка только по расширению (если есть)
257
+ # Предварительная проверка (если расширение есть)
255
258
  ext = Path(urlparse(url).path).suffix.lower()
256
- if ext and ext not in ALLOWED_EXTENSIONS:
259
+ if ext and not should_download_asset(url, None, self.source.asset_types):
257
260
  return url, None
258
261
 
259
262
  def do_request():
@@ -266,7 +269,7 @@ class BaseDownloader(ABC):
266
269
  content_type = response.headers.get('Content-Type', '')
267
270
 
268
271
  # Полная проверка после получения Content-Type
269
- if not should_download_asset(url, content_type):
272
+ if not should_download_asset(url, content_type, self.source.asset_types):
270
273
  return url, None
271
274
 
272
275
  filename = self._make_asset_filename(url, content_type, asset.get('alt'))
@@ -150,17 +150,23 @@ class SponsorDownloader(BaseDownloader):
150
150
 
151
151
  def _parse_post(self, raw_data: dict) -> Post:
152
152
  """Парсит сырые данные API в Post."""
153
- post_id = str(raw_data['post_id'])
154
- title = raw_data.get('post_title', 'Без названия')
155
- post_date = raw_data.get('post_date', '')
153
+ post_id = str(raw_data.get('post_id') or raw_data.get('id'))
154
+ title = raw_data.get('post_title') or raw_data.get('title') or 'Без названия'
155
+ post_date = raw_data.get('post_date') or raw_data.get('date') or ''
156
156
 
157
157
  # URL поста
158
- post_url = raw_data.get('post_url', '')
158
+ post_url = raw_data.get('post_url') or f"/{self.source.author}/{post_id}/"
159
159
  if post_url and not post_url.startswith('http'):
160
160
  post_url = f"https://sponsr.ru{post_url}"
161
161
 
162
162
  # HTML контент
163
- content_html = raw_data.get('post_text', '')
163
+ content_obj = raw_data.get('post_text') or raw_data.get('text')
164
+ if isinstance(content_obj, dict):
165
+ content_html = content_obj.get('text', '')
166
+ elif isinstance(content_obj, str):
167
+ content_html = content_obj
168
+ else:
169
+ content_html = ''
164
170
 
165
171
  # Теги
166
172
  tags = raw_data.get('tags', [])
@@ -6,16 +6,24 @@ from pathlib import Path
6
6
  from urllib.parse import urlparse
7
7
  from slugify import slugify
8
8
 
9
- # Белый список расширений
10
- ALLOWED_EXTENSIONS = {
11
- '.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg',
12
- '.mp4', '.webm', '.mov', '.mkv', '.avi',
13
- '.mp3', '.wav', '.flac', '.ogg',
14
- '.pdf',
9
+ # Типы ассетов и их расширения
10
+ ASSET_TYPES = {
11
+ 'image': {'.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'},
12
+ 'video': {'.mp4', '.webm', '.mov', '.mkv', '.avi'},
13
+ 'audio': {'.mp3', '.wav', '.flac', '.ogg'},
14
+ 'document': {'.pdf'},
15
15
  }
16
16
 
17
- # Допустимые Content-Type
18
- ALLOWED_CONTENT_TYPES = {'image/', 'video/', 'audio/', 'application/pdf'}
17
+ # Глобальный список разрешенных расширений
18
+ ALLOWED_EXTENSIONS = set().union(*ASSET_TYPES.values())
19
+
20
+ # Префиксы Content-Type для категорий
21
+ CONTENT_TYPE_MAP = {
22
+ 'image': ['image/'],
23
+ 'video': ['video/'],
24
+ 'audio': ['audio/'],
25
+ 'document': ['application/pdf'],
26
+ }
19
27
 
20
28
  # Паттерны для внутренних ссылок
21
29
  SPONSR_LINK_PATTERN = re.compile(r'https?://sponsr\.ru/([^/]+)/(\d+)(?:/[^\s\)\]"\'<>]*)?')
@@ -60,21 +68,50 @@ def is_post_url(text: str) -> bool:
60
68
  return False
61
69
 
62
70
 
63
- def should_download_asset(url: str, content_type: str | None = None) -> bool:
71
+ def should_download_asset(
72
+ url: str,
73
+ content_type: str | None = None,
74
+ allowed_types: list[str] | None = None
75
+ ) -> bool:
64
76
  """
65
77
  Проверяет, нужно ли скачивать файл.
66
78
 
67
79
  Args:
68
80
  url: URL файла
69
81
  content_type: Content-Type из заголовков ответа (опционально)
82
+ allowed_types: Список разрешенных типов (image, video, audio, document).
83
+ Если None или пустой — разрешено всё из ALLOWED_EXTENSIONS.
70
84
  """
71
85
  ext = Path(urlparse(url).path).suffix.lower()
72
86
 
87
+ # Если типы не указаны, используем глобальный фильтр
88
+ if not allowed_types:
89
+ if ext:
90
+ return ext in ALLOWED_EXTENSIONS
91
+
92
+ # Fallback для content-type (старое поведение)
93
+ if content_type:
94
+ basic_types = ['image/', 'video/', 'audio/', 'application/pdf']
95
+ return any(ct in content_type for ct in basic_types)
96
+
97
+ return False
98
+
99
+ # Если типы указаны, проверяем строго по ним
100
+
101
+ # 1. Проверка по расширению
73
102
  if ext:
74
- return ext in ALLOWED_EXTENSIONS
103
+ for type_name in allowed_types:
104
+ if ext in ASSET_TYPES.get(type_name, set()):
105
+ return True
106
+ # Если расширение есть, но не совпало ни с одним разрешенным типом — запрещаем
107
+ return False
75
108
 
109
+ # 2. Проверка по Content-Type (если нет расширения)
76
110
  if content_type:
77
- return any(ct in content_type for ct in ALLOWED_CONTENT_TYPES)
111
+ for type_name in allowed_types:
112
+ prefixes = CONTENT_TYPE_MAP.get(type_name, [])
113
+ if any(prefix in content_type for prefix in prefixes):
114
+ return True
78
115
 
79
116
  return False
80
117
 
File without changes
File without changes