Files: 36dcea55c65ac21eda7d833b2616ab35da5cc6ad / scuttleblog.py
6153 bytesRaw
1 | #!/usr/bin/python3 |
2 | |
3 | __copyright__ = """ |
4 | |
5 | Scuttleblog |
6 | |
7 | Copyright (C) 2017 Greg K Nicholson |
8 | |
9 | This program is free software: |
10 | you can redistribute it and/or modify it |
11 | under the terms of the GNU Affero General Public License |
12 | as published by the Free Software Foundation, |
13 | either version 3 of the License, |
14 | or (at your option) any later version. |
15 | |
16 | This program is distributed in the hope that it will be useful, |
17 | but WITHOUT ANY WARRANTY; |
18 | without even the implied warranty |
19 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
20 | See the GNU Affero General Public License for more details. |
21 | |
22 | You should have received a copy |
23 | of the GNU Affero General Public License |
24 | along with this program. |
25 | If not, see <https://www.gnu.org/licenses/>. |
26 | |
27 | """ |
28 | |
29 | import datetime |
30 | import json |
31 | import os |
32 | import re |
33 | import time |
34 | import subprocess |
35 | |
36 | import conf |
37 | |
38 | scuttleblog_dir = os.path.dirname(__file__) |
39 | |
40 | def run_sbot(): |
41 | for attempt in range(100): |
42 | try: |
43 | subprocess.check_output(['sbot', 'status']) |
44 | except: |
45 | pid = subprocess.Popen(['sbot', 'server']).pid |
46 | time.sleep(1) |
47 | else: |
48 | break |
49 | |
50 | def get_user_posts(ssb_userid): |
51 | user_posts_args = ['sbot', 'createUserStream', '--id', ssb_userid] |
52 | json_posts_args = ['json', '--group', \ |
53 | '-c', 'this.value.content.type == "post"', \ |
54 | '-c', 'this.value.content.text != null', \ |
55 | '-c', 'this.value.content.text != ""', \ |
56 | '-c', 'this.value.content.root == null'] |
57 | user_posts_stream = subprocess.Popen(user_posts_args, |
58 | stdout = subprocess.PIPE) |
59 | user_posts_json = subprocess.check_output(json_posts_args, |
60 | stdin = user_posts_stream.stdout) |
61 | user_posts = json.loads(user_posts_json) |
62 | return user_posts |
63 | |
64 | def define_post_text(text): |
65 | text = text.strip() |
66 | title = text.splitlines()[0] |
67 | if re.match('#', text): |
68 | # The first line is a heading. |
69 | # Use it as the title and strip it from the body. |
70 | if len(text.splitlines()) > 1: |
71 | body = ''.join(text.splitlines(keepends=True)[1:]).strip() |
72 | else: |
73 | body = '' |
74 | else: |
75 | # Truncate the first line and use it as the title. |
76 | # Keep everything in the body. |
77 | maxlength = 100 |
78 | ellipsis = '...' |
79 | if len(title) > maxlength: |
80 | title = title[:maxlength].rsplit(' ', 1)[0] + ellipsis |
81 | body = text |
82 | title = re.sub('^#+\s+', '', title) |
83 | return {'title': title, 'body': body} |
84 | |
85 | def build_post_structure(p): |
86 | post = {} |
87 | post['frontmatter'] = {} |
88 | post['frontmatter']['key'] = p['key'] |
89 | post['frontmatter']['title'] = define_post_text( |
90 | p['value']['content']['text'])['title'] |
91 | time = datetime.datetime.fromtimestamp(int(p['value']['timestamp'] |
92 | / 1000), |
93 | datetime.timezone.utc) |
94 | post['frontmatter']['date'] = time.strftime('%Y-%m-%dT%H:%M:%SZ') |
95 | post['frontmatter']['sequence'] = p['value']['sequence'] |
96 | post['body'] = define_post_text(p['value']['content']['text'])['body'] |
97 | return (post) |
98 | |
99 | def format_post_file(post): |
100 | content = '' |
101 | content += str(json.dumps(post['frontmatter'], indent = 4)) |
102 | content += '\n\n' |
103 | content += str(post['body']) |
104 | return content |
105 | |
106 | def define_post_filename(post): |
107 | folder = 'hugo/content/posts' |
108 | slug = str(post['frontmatter']['sequence']) |
109 | filetype = 'md' |
110 | return folder + '/' + slug + '.' + filetype |
111 | |
112 | def write_post_file(post): |
113 | os.makedirs(os.path.dirname(define_post_filename(post)), exist_ok=True) |
114 | with open(define_post_filename(post), 'w') as f: |
115 | f.write (format_post_file(post)) |
116 | |
117 | def write_posts_from_user(ssb_userid): |
118 | posts = get_user_posts(ssb_userid) |
119 | for post in posts: |
120 | write_post_file(build_post_structure(post)) |
121 | |
122 | |
123 | def get_user_metadata(ssb_userid): |
124 | user_metadata_args = ['sbot', 'links', |
125 | '--source', ssb_userid, |
126 | '--dest', ssb_userid, |
127 | '--rel', 'about', |
128 | '--values'] |
129 | json_metadata_args = ['json', '--deep-merge', |
130 | '-c', 'this.value.content.type == "about"'] |
131 | user_metadata_stream = subprocess.Popen(user_metadata_args, |
132 | stdout = subprocess.PIPE) |
133 | user_metadata_json = subprocess.check_output(json_metadata_args, |
134 | stdin = user_metadata_stream.stdout) |
135 | user_metadata = json.loads(user_metadata_json) |
136 | return user_metadata |
137 | |
138 | def check_hugo_theme(theme, theme_clone_url): |
139 | hugo_theme_dir = os.path.join(scuttleblog_dir, 'hugo', 'themes', theme) |
140 | if not os.path.isdir(hugo_theme_dir): |
141 | subprocess.run(['git', 'submodule', 'add', '-f', |
142 | theme_clone_url, hugo_theme_dir]) |
143 | |
144 | def build_hugo_config(m): |
145 | hugo_config = {} |
146 | hugo_config['baseurl'] = conf.hugo_baseurl |
147 | hugo_config['theme'] = conf.hugo_theme |
148 | hugo_config['permalinks'] = {} |
149 | hugo_config['permalinks']['post'] = '/:filename/' |
150 | if 'name' in m['value']['content']: |
151 | hugo_config['title'] = m['value']['content']['name'] |
152 | hugo_config['params'] = {} |
153 | if 'description' in m['value']['content']: |
154 | description = m['value']['content']['description'] |
155 | hugo_config['params']['subtitle'] = description.replace('\n', ' ') |
156 | return (hugo_config) |
157 | |
158 | def write_hugo_config_from_user(ssb_userid): |
159 | metadata = get_user_metadata(ssb_userid) |
160 | for file in ['hugo/config.toml', 'hugo/config.yaml']: |
161 | if os.path.exists(file): |
162 | os.remove(file) |
163 | with open('hugo/config.json', 'w') as f: |
164 | f.write (json.dumps(build_hugo_config(metadata), indent = 4)) |
165 | |
166 | def run_hugo(): |
167 | subprocess.run(['hugo', '-s', 'hugo']) |
168 | |
169 | run_sbot() |
170 | write_posts_from_user(conf.ssb_userid) |
171 | write_hugo_config_from_user(conf.ssb_userid) |
172 | check_hugo_theme(conf.hugo_theme, conf.hugo_theme_clone_url) |
173 | run_hugo() |
174 | |
175 |
Built with git-ssb-web