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.
- {article_backup-0.1.0 → article_backup-0.2.0}/PKG-INFO +47 -18
- {article_backup-0.1.0 → article_backup-0.2.0}/README.md +46 -17
- {article_backup-0.1.0 → article_backup-0.2.0}/article_backup.egg-info/PKG-INFO +47 -18
- {article_backup-0.1.0 → article_backup-0.2.0}/backup.py +19 -2
- {article_backup-0.1.0 → article_backup-0.2.0}/pyproject.toml +1 -1
- {article_backup-0.1.0 → article_backup-0.2.0}/src/boosty.py +1 -1
- {article_backup-0.1.0 → article_backup-0.2.0}/src/config.py +10 -1
- {article_backup-0.1.0 → article_backup-0.2.0}/src/downloader.py +7 -4
- {article_backup-0.1.0 → article_backup-0.2.0}/src/sponsr.py +11 -5
- {article_backup-0.1.0 → article_backup-0.2.0}/src/utils.py +48 -11
- {article_backup-0.1.0 → article_backup-0.2.0}/LICENSE +0 -0
- {article_backup-0.1.0 → article_backup-0.2.0}/article_backup.egg-info/SOURCES.txt +0 -0
- {article_backup-0.1.0 → article_backup-0.2.0}/article_backup.egg-info/dependency_links.txt +0 -0
- {article_backup-0.1.0 → article_backup-0.2.0}/article_backup.egg-info/entry_points.txt +0 -0
- {article_backup-0.1.0 → article_backup-0.2.0}/article_backup.egg-info/requires.txt +0 -0
- {article_backup-0.1.0 → article_backup-0.2.0}/article_backup.egg-info/top_level.txt +0 -0
- {article_backup-0.1.0 → article_backup-0.2.0}/setup.cfg +0 -0
- {article_backup-0.1.0 → article_backup-0.2.0}/src/__init__.py +0 -0
- {article_backup-0.1.0 → article_backup-0.2.0}/src/database.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: article-backup
|
|
3
|
-
Version: 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
|
+
[](https://pypi.org/project/article-backup/)
|
|
35
|
+
[](https://www.python.org/downloads/)
|
|
36
|
+
[](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
|
-
|
|
130
|
-
|
|
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
|
-
|
|
154
|
+
article-backup -c /path/to/config.yaml
|
|
137
155
|
```
|
|
138
156
|
|
|
139
157
|
## Docker
|
|
140
158
|
|
|
141
159
|
Для серверов с устаревшим Python можно использовать Docker.
|
|
142
160
|
|
|
143
|
-
|
|
144
|
-
# Сборка образа
|
|
145
|
-
docker compose build
|
|
161
|
+
Для удобства используйте скрипт `run-docker.sh`, который автоматически подхватывает `output_dir` из вашего `config.yaml` и монтирует правильный volume.
|
|
146
162
|
|
|
147
|
-
|
|
148
|
-
|
|
163
|
+
```bash
|
|
164
|
+
# Синхронизация + сборка сайта (рекомендуемый способ)
|
|
165
|
+
./run-docker.sh
|
|
149
166
|
|
|
150
167
|
# Скачать один пост
|
|
151
|
-
docker
|
|
168
|
+
./run-docker.sh "https://sponsr.ru/author/123/"
|
|
152
169
|
|
|
153
|
-
#
|
|
154
|
-
docker
|
|
170
|
+
# Только пересборка сайта
|
|
171
|
+
./run-docker.sh hugo
|
|
155
172
|
|
|
156
|
-
#
|
|
157
|
-
|
|
173
|
+
# Пересборка контейнеров
|
|
174
|
+
./run-docker.sh build
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Ручной запуск (Advanced)
|
|
178
|
+
|
|
179
|
+
Если вы не хотите использовать скрипт, можно запускать через `docker compose`, но нужно вручную указывать путь к бэкапам, если он отличается от `./backup`.
|
|
158
180
|
|
|
159
|
-
|
|
160
|
-
|
|
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
|
+
[](https://pypi.org/project/article-backup/)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](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
|
-
|
|
99
|
-
|
|
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
|
-
|
|
123
|
+
article-backup -c /path/to/config.yaml
|
|
106
124
|
```
|
|
107
125
|
|
|
108
126
|
## Docker
|
|
109
127
|
|
|
110
128
|
Для серверов с устаревшим Python можно использовать Docker.
|
|
111
129
|
|
|
112
|
-
|
|
113
|
-
# Сборка образа
|
|
114
|
-
docker compose build
|
|
130
|
+
Для удобства используйте скрипт `run-docker.sh`, который автоматически подхватывает `output_dir` из вашего `config.yaml` и монтирует правильный volume.
|
|
115
131
|
|
|
116
|
-
|
|
117
|
-
|
|
132
|
+
```bash
|
|
133
|
+
# Синхронизация + сборка сайта (рекомендуемый способ)
|
|
134
|
+
./run-docker.sh
|
|
118
135
|
|
|
119
136
|
# Скачать один пост
|
|
120
|
-
docker
|
|
137
|
+
./run-docker.sh "https://sponsr.ru/author/123/"
|
|
121
138
|
|
|
122
|
-
#
|
|
123
|
-
docker
|
|
139
|
+
# Только пересборка сайта
|
|
140
|
+
./run-docker.sh hugo
|
|
124
141
|
|
|
125
|
-
#
|
|
126
|
-
|
|
142
|
+
# Пересборка контейнеров
|
|
143
|
+
./run-docker.sh build
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Ручной запуск (Advanced)
|
|
147
|
+
|
|
148
|
+
Если вы не хотите использовать скрипт, можно запускать через `docker compose`, но нужно вручную указывать путь к бэкапам, если он отличается от `./backup`.
|
|
127
149
|
|
|
128
|
-
|
|
129
|
-
|
|
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.
|
|
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
|
+
[](https://pypi.org/project/article-backup/)
|
|
35
|
+
[](https://www.python.org/downloads/)
|
|
36
|
+
[](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
|
-
|
|
130
|
-
|
|
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
|
-
|
|
154
|
+
article-backup -c /path/to/config.yaml
|
|
137
155
|
```
|
|
138
156
|
|
|
139
157
|
## Docker
|
|
140
158
|
|
|
141
159
|
Для серверов с устаревшим Python можно использовать Docker.
|
|
142
160
|
|
|
143
|
-
|
|
144
|
-
# Сборка образа
|
|
145
|
-
docker compose build
|
|
161
|
+
Для удобства используйте скрипт `run-docker.sh`, который автоматически подхватывает `output_dir` из вашего `config.yaml` и монтирует правильный volume.
|
|
146
162
|
|
|
147
|
-
|
|
148
|
-
|
|
163
|
+
```bash
|
|
164
|
+
# Синхронизация + сборка сайта (рекомендуемый способ)
|
|
165
|
+
./run-docker.sh
|
|
149
166
|
|
|
150
167
|
# Скачать один пост
|
|
151
|
-
docker
|
|
168
|
+
./run-docker.sh "https://sponsr.ru/author/123/"
|
|
152
169
|
|
|
153
|
-
#
|
|
154
|
-
docker
|
|
170
|
+
# Только пересборка сайта
|
|
171
|
+
./run-docker.sh hugo
|
|
155
172
|
|
|
156
|
-
#
|
|
157
|
-
|
|
173
|
+
# Пересборка контейнеров
|
|
174
|
+
./run-docker.sh build
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Ручной запуск (Advanced)
|
|
178
|
+
|
|
179
|
+
Если вы не хотите использовать скрипт, можно запускать через `docker compose`, но нужно вручную указывать путь к бэкапам, если он отличается от `./backup`.
|
|
158
180
|
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
18
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|