Skip to content

Commit da066f3

Browse files
committed
Add plugin to import books from bookstack
1 parent 8b5900e commit da066f3

File tree

5 files changed

+216
-0
lines changed

5 files changed

+216
-0
lines changed

v8/import_bookstack/README.md

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Plugin to import book, chapter or page from BookStack.
2+
3+
Usage:
4+
5+
```
6+
nikola import_bookstack --url=https://exampls.tld --key=API:KEY --book=1
7+
```
8+
9+
Imports all chapters and pages for the book with the id 1
10+
11+
12+
```
13+
nikola import_bookstack --url=https://exampls.tld --key=API:KEY --chapter=1
14+
```
15+
16+
Imports all pages for the chapter with the id 1
17+
18+
19+
```
20+
nikola import_bookstack --url=https://exampls.tld --key=API:KEY --page=1
21+
```
22+
23+
Imports the page with the id 1

v8/import_bookstack/conf.py.sample

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
PAGES = (
2+
("stories/*.html", "stories", "story.tmpl"),
3+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[Core]
2+
Name = import_bookstack
3+
Module = import_bookstack
4+
5+
[Nikola]
6+
PluginCategory = Command
7+
8+
[Documentation]
9+
Author = Harald Nezbeda
10+
Version = 0.1
11+
Website = http://plugins.getnikola.com/v8/import_bookstack
12+
Description = Try to import content from the Bookstack wiki software.
+177
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
# Copyright © 2024 Harald Nezbeda
2+
3+
# Permission is hereby granted, free of charge, to any
4+
# person obtaining a copy of this software and associated
5+
# documentation files (the "Software"), to deal in the
6+
# Software without restriction, including without limitation
7+
# the rights to use, copy, modify, merge, publish,
8+
# distribute, sublicense, and/or sell copies of the
9+
# 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
13+
# shall be included in all copies or substantial portions of
14+
# the Software.
15+
#
16+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17+
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
19+
# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
20+
# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
21+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
22+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24+
25+
import os
26+
27+
import requests
28+
import sys
29+
30+
from nikola.plugin_categories import Command
31+
from nikola.plugins.basic_import import ImportMixin
32+
from nikola import utils
33+
34+
LOGGER = utils.get_logger('import_bookstack', utils.STDERR_HANDLER)
35+
36+
37+
class CommandImportPage(Command, ImportMixin):
38+
"""Import a Page."""
39+
40+
name = "import_bookstack"
41+
needs_config = False
42+
doc_usage = "[options] page_url [page_url,...]"
43+
doc_purpose = "import arbitrary web pages"
44+
cmd_options = [
45+
{
46+
'name': 'output',
47+
'long': 'output',
48+
'short': 'o',
49+
'default': 'posts',
50+
'help': 'Location to write imported content.'
51+
},
52+
{
53+
'name': 'url',
54+
'long': 'url',
55+
'short': 'u',
56+
'default': None,
57+
'help': 'Bookstack Base URL.'
58+
},
59+
{
60+
'name': 'key',
61+
'long': 'key',
62+
'short': 'k',
63+
'default': None,
64+
'help': 'Bookstack API Key.'
65+
},
66+
{
67+
'name': 'book',
68+
'long': 'book',
69+
'short': 'b',
70+
'default': None,
71+
'help': 'Bookstack Book ID.'
72+
},
73+
{
74+
'name': 'chapter',
75+
'long': 'chapter',
76+
'short': 'c',
77+
'default': None,
78+
'help': 'Bookstack Chapter ID.'
79+
},
80+
{
81+
'name': 'page',
82+
'long': 'page',
83+
'short': 'p',
84+
'default': None,
85+
'help': 'Bookstack Page ID.'
86+
}
87+
]
88+
89+
def _execute(self, options, args):
90+
self.output = options['output']
91+
self.url = options['url']
92+
self.key = options['key']
93+
self.book = options['book']
94+
self.chapter = options['chapter']
95+
self.page = options['page']
96+
97+
if not self.url:
98+
LOGGER.error("Bookstack Base URL is required.")
99+
sys.exit(1)
100+
101+
if not self.key:
102+
LOGGER.error("Bookstack API Key is required.")
103+
sys.exit(1)
104+
105+
if sum([bool(self.book), bool(self.chapter), bool(self.page)]) != 1:
106+
LOGGER.error("Only one of Bookstack Book ID, Chapter ID, or Page ID should be provided.")
107+
sys.exit(1)
108+
109+
if self.page:
110+
self._import_page(self.page)
111+
elif self.chapter:
112+
self._import_chapter(self.chapter)
113+
elif self.book:
114+
self._import_book(self.book)
115+
116+
def _bookstack_request(self, url):
117+
headers = {
118+
'Authorization': f'Token {self.key}'
119+
}
120+
return requests.get(url, headers=headers)
121+
122+
def _import_page(self, page):
123+
response = self._bookstack_request(f"{self.url}/api/pages/{page}")
124+
125+
if response.status_code == 200:
126+
page_data = response.json()
127+
128+
if not page_data['draft']:
129+
slug = page_data['slug']
130+
title = page_data['name']
131+
content = page_data['html']
132+
post_date = page_data['created_at']
133+
description = page_data['name']
134+
tags = [tag['name'] for tag in page_data['tags']]
135+
136+
self.write_metadata(
137+
os.path.join(self.output,slug + '.meta'),
138+
title,
139+
slug,
140+
post_date,
141+
description,
142+
tags
143+
)
144+
self.write_content(
145+
os.path.join(self.output,slug + '.html'),
146+
content
147+
)
148+
149+
else:
150+
LOGGER.error(f"Failed to fetch page data: {response.status_code}")
151+
sys.exit(1)
152+
153+
def _import_chapter(self, chpater):
154+
response = self._bookstack_request(f"{self.url}/api/chapters/{chpater}")
155+
156+
if response.status_code == 200:
157+
chapter_data = response.json()
158+
for page in chapter_data['pages']:
159+
self._import_page(page['id'])
160+
161+
else:
162+
LOGGER.error(f"Failed to fetch chapter data: {response.status_code}")
163+
sys.exit(1)
164+
165+
def _import_book(self, book):
166+
response = self._bookstack_request(f"{self.url}/api/books/{book}")
167+
168+
if response.status_code == 200:
169+
book_data = response.json()
170+
for content in book_data['contents']:
171+
if content['type'] == 'page':
172+
self._import_page(content['id'])
173+
elif content['type'] == 'chapter':
174+
self._import_chapter(content['id'])
175+
else:
176+
LOGGER.error(f"Failed to fetch book data: {response.status_code}")
177+
sys.exit(1)

v8/import_bookstack/requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
requests

0 commit comments

Comments
 (0)