openwrite 0.16.1__tar.gz → 0.17.2__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.
Files changed (128) hide show
  1. {openwrite-0.16.1/openwrite.egg-info → openwrite-0.17.2}/PKG-INFO +21 -3
  2. {openwrite-0.16.1 → openwrite-0.17.2}/README.md +20 -2
  3. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/__init__.py +12 -6
  4. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/cli.py +4 -1
  5. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/gemini_single.py +26 -25
  6. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/routes/blog.py +34 -73
  7. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/routes/dashboard.py +56 -173
  8. openwrite-0.17.2/openwrite/routes/federation.py +262 -0
  9. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/routes/main.py +2 -2
  10. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/routes/upload.py +2 -7
  11. openwrite-0.17.2/openwrite/static/feed/default.xml +2 -0
  12. openwrite-0.17.2/openwrite/static/feed/test.xml +2 -0
  13. openwrite-0.17.2/openwrite/static/feed/testx.xml +2 -0
  14. openwrite-0.17.2/openwrite/static/script/post_preview.js +166 -0
  15. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/style.css +79 -0
  16. openwrite-0.17.2/openwrite/static/style/toc.css +28 -0
  17. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/blog.html +1 -1
  18. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/blog_header.html +2 -1
  19. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/blog_preview.html +1 -1
  20. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/dashboard.html +2 -2
  21. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/edit.html +6 -6
  22. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/footer.html +1 -1
  23. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/new_post.html +63 -18
  24. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/page_preview.html +1 -1
  25. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/post.html +2 -2
  26. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/preview.html +3 -2
  27. openwrite-0.17.2/openwrite/utils/activitypub.py +154 -0
  28. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/utils/db.py +4 -1
  29. openwrite-0.17.2/openwrite/utils/helpers.py +200 -0
  30. openwrite-0.17.2/openwrite/utils/i18n.json +1 -0
  31. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/utils/migrations.py +3 -3
  32. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/utils/models.py +44 -0
  33. openwrite-0.17.2/openwrite/utils/toc.py +47 -0
  34. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/utils/worker.py +37 -22
  35. {openwrite-0.16.1 → openwrite-0.17.2/openwrite.egg-info}/PKG-INFO +21 -3
  36. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite.egg-info/SOURCES.txt +9 -27
  37. {openwrite-0.16.1 → openwrite-0.17.2}/pyproject.toml +1 -1
  38. openwrite-0.17.2/tests/test_federation.py +726 -0
  39. openwrite-0.16.1/openwrite/routes/federation.py +0 -329
  40. openwrite-0.16.1/openwrite/static/feed/74.xml +0 -12
  41. openwrite-0.16.1/openwrite/static/feed/a.xml +0 -2
  42. openwrite-0.16.1/openwrite/static/feed/anim3sh1t.xml +0 -12
  43. openwrite-0.16.1/openwrite/static/feed/b.xml +0 -2
  44. openwrite-0.16.1/openwrite/static/feed/corrode.xml +0 -4
  45. openwrite-0.16.1/openwrite/static/feed/dirtysanchez.xml +0 -6
  46. openwrite-0.16.1/openwrite/static/feed/eikerammen.xml +0 -165
  47. openwrite-0.16.1/openwrite/static/feed/fedi.xml +0 -9
  48. openwrite-0.16.1/openwrite/static/feed/friskt.xml +0 -2
  49. openwrite-0.16.1/openwrite/static/feed/gordi.xml +0 -6
  50. openwrite-0.16.1/openwrite/static/feed/igilq.xml +0 -226
  51. openwrite-0.16.1/openwrite/static/feed/io.xml +0 -50
  52. openwrite-0.16.1/openwrite/static/feed/isyuricunha.xml +0 -4
  53. openwrite-0.16.1/openwrite/static/feed/kenny.xml +0 -2
  54. openwrite-0.16.1/openwrite/static/feed/m0bi.xml +0 -6
  55. openwrite-0.16.1/openwrite/static/feed/main.xml +0 -236
  56. openwrite-0.16.1/openwrite/static/feed/mywritese.xml +0 -2
  57. openwrite-0.16.1/openwrite/static/feed/nay-thoughs.xml +0 -2
  58. openwrite-0.16.1/openwrite/static/feed/nintendoflesz.xml +0 -7
  59. openwrite-0.16.1/openwrite/static/feed/phronima.xml +0 -68
  60. openwrite-0.16.1/openwrite/static/feed/ram.xml +0 -75
  61. openwrite-0.16.1/openwrite/static/feed/samah.xml +0 -2
  62. openwrite-0.16.1/openwrite/static/feed/themes-test.xml +0 -18
  63. openwrite-0.16.1/openwrite/static/feed/totylkoja.xml +0 -78
  64. openwrite-0.16.1/openwrite/static/feed/vegafjord.xml +0 -48
  65. openwrite-0.16.1/openwrite/static/feed/vijayvaradharaj.xml +0 -2
  66. openwrite-0.16.1/openwrite/utils/helpers.py +0 -217
  67. openwrite-0.16.1/openwrite/utils/i18n.json +0 -1
  68. {openwrite-0.16.1 → openwrite-0.17.2}/LICENSE +0 -0
  69. {openwrite-0.16.1 → openwrite-0.17.2}/MANIFEST.in +0 -0
  70. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/db/__init__.py +0 -0
  71. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/db/base.py +0 -0
  72. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/gemini_multi.py +0 -0
  73. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/routes/admin.py +0 -0
  74. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/routes/auth.py +0 -0
  75. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/IBMPlexSans-Regular.ttf +0 -0
  76. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/Merriweather.ttf +0 -0
  77. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/avatar.png +0 -0
  78. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/emoji.json +0 -0
  79. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/favicon.ico +0 -0
  80. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/favicon.png +0 -0
  81. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/feed/.gitkeep +0 -0
  82. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/logo.png +0 -0
  83. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/script/chart.js +0 -0
  84. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/script/highlight.min.js +0 -0
  85. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/script/nativeEmoji.js +0 -0
  86. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/script/openwrite.js +0 -0
  87. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/script/papaparse.min.js +0 -0
  88. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/script/showdown.min.js +0 -0
  89. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/script/showdown.min.js.map +0 -0
  90. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/script/site.compat.min.js +0 -0
  91. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/script/site.min.js +0 -0
  92. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/blog_design.css +0 -0
  93. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/default.css +0 -0
  94. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/hl.css +0 -0
  95. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/nativeEmoji.min.css +0 -0
  96. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/themes/cyberpunk.css +0 -0
  97. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/themes/light.css +0 -0
  98. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/themes/obscura.css +0 -0
  99. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/themes/paper.css +0 -0
  100. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/themes/sepia.css +0 -0
  101. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/themes/stickynote.css +0 -0
  102. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/themes/synthwave.css +0 -0
  103. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/themes/terminal.css +0 -0
  104. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/themes/warmnight.css +0 -0
  105. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/themes/windows98.css +0 -0
  106. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/admin.html +0 -0
  107. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/blog_footer.html +0 -0
  108. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/changepw.html +0 -0
  109. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/create.html +0 -0
  110. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/discover.html +0 -0
  111. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/header.html +0 -0
  112. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/import.html +0 -0
  113. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/index.html +0 -0
  114. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/instances.html +0 -0
  115. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/login.html +0 -0
  116. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/new_page.html +0 -0
  117. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/register.html +0 -0
  118. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/settings.html +0 -0
  119. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/stats.html +0 -0
  120. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/translations.html +0 -0
  121. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/utils/create_db.py +0 -0
  122. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/utils/tags.py +0 -0
  123. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/utils/valkey_client.py +0 -0
  124. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite.egg-info/dependency_links.txt +0 -0
  125. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite.egg-info/entry_points.txt +0 -0
  126. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite.egg-info/requires.txt +0 -0
  127. {openwrite-0.16.1 → openwrite-0.17.2}/openwrite.egg-info/top_level.txt +0 -0
  128. {openwrite-0.16.1 → openwrite-0.17.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openwrite
3
- Version: 0.16.1
3
+ Version: 0.17.2
4
4
  License: GNU AFFERO GENERAL PUBLIC LICENSE
5
5
  Version 3, 19 November 2007
6
6
 
@@ -761,7 +761,25 @@ to run it in background.
761
761
 
762
762
  5. Default user is `admin` with password `openwrite`. You can (and should!) change it in your dashboard. Enjoy!
763
763
 
764
+ ### Local development over HTTP
764
765
 
765
- #### Docker
766
+ For `http://localhost:8081`, set these values in `.env`:
766
767
 
767
- Docker image is still being prepared. Stay tuned!
768
+ ```dotenv
769
+ DOMAIN=localhost:8081
770
+ IP=127.0.0.1
771
+ PORT=8081
772
+ GEMINI=no
773
+ ```
774
+
775
+ When working from a checkout, install it with `./venv/bin/python -m pip install -e .`
776
+ so changes in this folder are used by the CLI.
777
+ Run `./venv/bin/openwrite debugrun` (or `./venv/bin/openwrite run`) and open
778
+ `http://localhost:8081`. `DOMAIN` must match the hostname and port in the browser,
779
+ without a protocol prefix. For a custom local hostname such as `openwrite.local`,
780
+ use `DOMAIN=openwrite.local:8081` and point that hostname to `127.0.0.1` in your hosts file.
781
+ Restart the server after changing `.env`.
782
+
783
+ Both commands serve plain HTTP. In an HTTPS deployment, terminate TLS at a reverse
784
+ proxy and forward the original host and scheme with `X-Forwarded-Host` and
785
+ `X-Forwarded-Proto` so browser links use the public address.
@@ -71,7 +71,25 @@ to run it in background.
71
71
 
72
72
  5. Default user is `admin` with password `openwrite`. You can (and should!) change it in your dashboard. Enjoy!
73
73
 
74
+ ### Local development over HTTP
74
75
 
75
- #### Docker
76
+ For `http://localhost:8081`, set these values in `.env`:
76
77
 
77
- Docker image is still being prepared. Stay tuned!
78
+ ```dotenv
79
+ DOMAIN=localhost:8081
80
+ IP=127.0.0.1
81
+ PORT=8081
82
+ GEMINI=no
83
+ ```
84
+
85
+ When working from a checkout, install it with `./venv/bin/python -m pip install -e .`
86
+ so changes in this folder are used by the CLI.
87
+ Run `./venv/bin/openwrite debugrun` (or `./venv/bin/openwrite run`) and open
88
+ `http://localhost:8081`. `DOMAIN` must match the hostname and port in the browser,
89
+ without a protocol prefix. For a custom local hostname such as `openwrite.local`,
90
+ use `DOMAIN=openwrite.local:8081` and point that hostname to `127.0.0.1` in your hosts file.
91
+ Restart the server after changing `.env`.
92
+
93
+ Both commands serve plain HTTP. In an HTTPS deployment, terminate TLS at a reverse
94
+ proxy and forward the original host and scheme with `X-Forwarded-Host` and
95
+ `X-Forwarded-Proto` so browser links use the public address.
@@ -6,7 +6,6 @@ import os
6
6
  from .utils.helpers import generate_nonce, get_ip
7
7
  import time
8
8
  from .utils.models import Settings, Home
9
- from sqlalchemy import distinct
10
9
  import bcrypt
11
10
  import json
12
11
  from cryptography.hazmat.primitives.asymmetric import rsa
@@ -87,8 +86,8 @@ def create_app(test_config=None):
87
86
  blog=1,
88
87
  name="Home",
89
88
  url="",
90
- content_raw=f"![hello](https://openwrite.b-cdn.net/hello.jpg =500x258)\n\n# Hello there! 👋\n\nYou can edit your blog home page in [dashboard](https://{domain}/dashboard/page/1/edit)\n\n---\n### Posts\n\n{{posts}}",
91
- content_html=f"<p><img src=\"https://openwrite.b-cdn.net/hello.jpg\" width=\"500\" height=\"258\"></p><h1>Hello there! 👋</h1><p>You can edit your blog home page in <a href=\"https://{domain}/dashboard/page/1/edit\">dashboard</a></p>\n\n<hr>\n<h3>Posts\n\n{{posts}}",
89
+ content_raw=f"![hello](https://openwrite.b-cdn.net/hello.jpg =500x258)\n\n# Hello there! 👋\n\nYou can edit your blog home page in [dashboard](/dashboard/page/1/edit)\n\n---\n### Posts\n\n{{posts}}",
90
+ content_html=f"<p><img src=\"https://openwrite.b-cdn.net/hello.jpg\" width=\"500\" height=\"258\"></p><h1>Hello there! 👋</h1><p>You can edit your blog home page in <a href=\"/dashboard/page/1/edit\">dashboard</a></p>\n\n<hr>\n<h3>Posts\n\n{{posts}}",
92
91
  show="0"
93
92
  )
94
93
  SessionLocal.add(new_page)
@@ -193,12 +192,20 @@ def create_app(test_config=None):
193
192
 
194
193
  app.jinja_env.globals['_'] = _
195
194
 
195
+ @app.template_filter('with_toc')
196
+ def with_toc(content, enabled):
197
+ if enabled != "1":
198
+ return content
199
+ from .utils.toc import add_table_of_contents
200
+ label = g.trans.get('table-of-contents', 'Spis treści' if g.lang == 'pl' else 'Table of contents')
201
+ return add_table_of_contents(content, label)
202
+
196
203
  @app.before_request
197
204
  def before():
198
205
  from flask import request, session
199
206
  from openwrite.utils.models import Home, Settings
200
207
  g.db = SessionLocal
201
- t_query = g.db.query(distinct(Home.language)).all()
208
+ t_query = g.db.query(Home.language).distinct().all()
202
209
  translations = [row[0] for row in t_query]
203
210
  lang = request.cookies.get("lang")
204
211
  if not lang or lang not in translations:
@@ -253,7 +260,6 @@ def create_app(test_config=None):
253
260
 
254
261
  @app.after_request
255
262
  def after(response):
256
- nonce = g.nonce
257
263
  #response.headers["Content-Security-Policy"] = (
258
264
  # f"default-src 'none'; "
259
265
  # f"script-src 'self' 'nonce-{nonce}' https://cdn.jsdelivr.net http://{g.main_domain}"
@@ -281,6 +287,6 @@ def create_app(test_config=None):
281
287
 
282
288
  @app.teardown_appcontext
283
289
  def shutdown_session(exception=None):
284
- g.db.remove()
290
+ SessionLocal.remove()
285
291
 
286
292
  return app
@@ -168,6 +168,9 @@ def init():
168
168
  @click.option("-d", "--daemon", is_flag=True, help="Run in background (daemon)")
169
169
  def run(daemon):
170
170
  print_banner()
171
+ # Initialize/migrate once before concurrent web workers and the queue worker start.
172
+ from openwrite import create_app
173
+ create_app()
171
174
 
172
175
  ip = os.getenv("IP", "0.0.0.0")
173
176
  port = int(os.getenv("PORT", 8080))
@@ -184,7 +187,7 @@ def run(daemon):
184
187
  gunicorn_error_log = os.path.join(logs_dir, "openwrite_error.log")
185
188
 
186
189
  gunicorn_cmd = [
187
- "gunicorn",
190
+ sys.executable, "-m", "gunicorn",
188
191
  "-w", "4",
189
192
  "openwrite:create_app()",
190
193
  "--bind", f"{ip}:{port}"
@@ -1,7 +1,8 @@
1
1
  import os, subprocess
2
+ import re
2
3
  from dotenv import load_dotenv
3
4
  from openwrite.utils.db import init_engine, SessionLocal
4
- from openwrite.utils.models import Blog, Post, User, View
5
+ from openwrite.utils.models import Blog, Post, User, View, Page
5
6
  from openwrite.utils.helpers import anonymize
6
7
  from md2gemini import md2gemini
7
8
  from datetime import datetime, timezone
@@ -11,28 +12,28 @@ def blog_index(req):
11
12
  global session
12
13
  try:
13
14
  blog = session.query(Blog).filter_by(name="default").first()
14
- if not blog:
15
- return "not found"
16
- posts = (session.query(Post)
17
- .filter_by(blog=blog.id)
18
- .order_by(Post.id.desc())
19
- .all())
20
- pages = (session.query(Page)
21
- .filter(Page.blog == blog.id, Page.show == "1").all())
22
- homepage = session.query(Page).filter(Page.blog == blog.id, Page.url == "").first()
23
- body = f"# 📓 {blog.title}\n\n"
24
- posts_data = ""
25
- for post in posts:
26
- posts_data += f"=> /b/{blogname}/{post.link} {post.title}\n\n"
27
- pages_data = ""
28
- for page in pages:
29
- pages_data += f"=> /b/{blogname}/{page.url} {page.name}\n\n"
30
- content = pages_data + "\n"
31
- content += homepage.content_raw.replace('{posts}', f"\n{posts_data}")
32
- content = re.sub(r'!\[(.*?)\]\((.*?)\s*=\d+x\d+\)', r'![\1](\2)', content)
33
- body += md2gemini(content)
34
-
35
- return body
15
+ if not blog:
16
+ return "not found"
17
+ posts = (session.query(Post)
18
+ .filter_by(blog=blog.id, isdraft="0")
19
+ .order_by(Post.id.desc())
20
+ .all())
21
+ pages = (session.query(Page)
22
+ .filter(Page.blog == blog.id, Page.show == "1").all())
23
+ homepage = session.query(Page).filter(Page.blog == blog.id, Page.url == "").first()
24
+ body = f"# 📓 {blog.title}\n\n"
25
+ posts_data = ""
26
+ for post in posts:
27
+ posts_data += f"=> /p/{post.link} {post.title}\n\n"
28
+ pages_data = ""
29
+ for page in pages:
30
+ pages_data += f"=> /p/{page.url} {page.name}\n\n"
31
+ content = pages_data + "\n"
32
+ content += homepage.content_raw.replace('{posts}', f"\n{posts_data}") if homepage else posts_data
33
+ content = re.sub(r'!\[(.*?)\]\((.*?)\s*=\d+x\d+\)', r'![\1](\2)', content)
34
+ body += md2gemini(content)
35
+
36
+ return body
36
37
  finally:
37
38
  session.close()
38
39
 
@@ -55,7 +56,7 @@ def blog_post(req):
55
56
  .filter_by(blog=blog.id, url=slug)
56
57
  .first())
57
58
  if page:
58
- content = f"=> /b/{blog.name} 📓 {blog.title}\n\n"
59
+ content = f"=> / 📓 {blog.title}\n\n"
59
60
  content += page.content_raw
60
61
  content = re.sub(r'!\[(.*?)\]\((.*?)\s*=\d+x\d+\)', r'![\1](\2)', content)
61
62
  return md2gemini(content)
@@ -72,7 +73,7 @@ def blog_post(req):
72
73
  session.commit()
73
74
 
74
75
  post.authorname = user.username
75
- gemtext = f"=> /b/{blog.name} 📓 {blog.title}\n"
76
+ gemtext = f"=> / 📓 {blog.title}\n"
76
77
  gemtext += f"# 🧾 {post.title}\n\n"
77
78
  gemtext += f"{post.date} "
78
79
  if post.author != "0":
@@ -1,8 +1,9 @@
1
- from flask import Blueprint, render_template, redirect, request, g, Response, abort, send_file, jsonify
1
+ from flask import Blueprint, render_template, redirect, request, g, Response, abort, send_file, jsonify, current_app
2
2
  from openwrite.utils.models import Blog, Post, User, View, Like, Page, PostDraft, Comment
3
3
  from openwrite.utils.tags import get_post_tags, parse_tags_from_posts_syntax, get_posts_by_tags
4
4
  from openwrite.utils.helpers import gen_link, sanitize_html, anonymize, get_ip
5
5
  from openwrite.utils.valkey_client import valkey_client
6
+ from openwrite.utils.activitypub import actor_url, create_activity_object, iso_date
6
7
  from feedgen.feed import FeedGenerator
7
8
  from sqlalchemy import desc
8
9
  import os
@@ -33,59 +34,11 @@ def process_posts_content(page, blog_id, blog_url, domain):
33
34
 
34
35
  return posts, processed_content
35
36
 
36
- def create_activity_object(post, blog, url, full_content=True):
37
- """Create ActivityPub object (Article or Note) based on post content length"""
38
- dt = datetime.strptime(str(post.date), "%Y-%m-%d %H:%M:%S")
39
- dt = dt.replace(tzinfo=timezone.utc)
40
- iso = dt.isoformat(timespec="seconds").replace("+00:00", "Z")
41
-
42
- clean_content = re.sub(r'<[^>]+>', '', post.content_html)
43
- content_length = len(clean_content)
44
-
45
- base_object = {
46
- "id": f"{url}/{post.link}",
47
- "attributedTo": url,
48
- "published": iso,
49
- "url": f"{url}/{post.link}",
50
- "to": ["https://www.w3.org/ns/activitystreams#Public"],
51
- "cc": [f"https://{g.main_domain}/followers/{blog}"],
52
- "sensitive": False,
53
- "atomUri": f"{url}/{post.link}",
54
- "attachment": [],
55
- "tag": []
56
- }
57
-
58
- if content_length > 200:
59
- words = clean_content.split()[:50]
60
- summary = ' '.join(words) + '...' if len(words) == 50 else ' '.join(words)
61
-
62
- if full_content:
63
- content = f"<h1>{post.title}</h1>{post.content_html}"
64
- else:
65
- content = f"<p><a href=\"{url}/{post.link}\">Read full article: {post.title}</a></p>"
66
-
67
- base_object.update({
68
- "type": "Article",
69
- "name": post.title,
70
- "summary": summary,
71
- "content": content
72
- })
73
- else:
74
- base_object.update({
75
- "type": "Note",
76
- "summary": None,
77
- "content": f"<h3>{post.title}</h3>{post.content_html}<p><a href=\"{url}/{post.link}\">View post</a></p>"
78
- })
79
-
80
- return base_object
81
-
82
37
  def create_person_object(blog, url):
83
38
  """Create ActivityPub Person object for the blog"""
84
- published = blog.created
85
- dt = datetime.strptime(str(published), "%Y-%m-%d %H:%M:%S")
86
- dt = dt.replace(tzinfo=timezone.utc)
87
- iso = dt.isoformat(timespec="seconds").replace("+00:00", "Z")
88
- if blog.fedi_description != "":
39
+ url = actor_url(blog)
40
+ iso = iso_date(blog.created)
41
+ if blog.fedi_description:
89
42
  summary = blog.fedi_description
90
43
  else:
91
44
  summary = f"{blog.name} - Blog on {g.main_domain}"
@@ -131,6 +84,8 @@ def create_person_object(blog, url):
131
84
  "url": f"https://{g.main_domain}/static/avatar.png"
132
85
  }
133
86
  }
87
+ if blog.updated:
88
+ actor['updated'] = iso_date(blog.updated)
134
89
  return actor
135
90
 
136
91
  def is_activitypub_request():
@@ -147,7 +102,7 @@ def show_blog(blog):
147
102
  return redirect("/")
148
103
 
149
104
  if blog_obj.access == "domain":
150
- return redirect(f"https://{blog_obj.name}.{os.getenv('DOMAIN')}/")
105
+ return redirect(f"{request.scheme}://{blog_obj.name}.{os.getenv('DOMAIN')}/")
151
106
 
152
107
  if is_activitypub_request():
153
108
  url = f"https://{g.main_domain}/b/{blog}"
@@ -170,10 +125,10 @@ def show_subblog(blog):
170
125
  return redirect("/")
171
126
  blog_obj = g.db.query(Blog).filter_by(name=blog).first()
172
127
  if blog_obj is None:
173
- return redirect(f"https://{os.getenv('DOMAIN')}/")
128
+ return redirect(f"{request.scheme}://{os.getenv('DOMAIN')}/")
174
129
 
175
130
  if blog_obj.access == "path":
176
- return redirect(f"https://{os.getenv('DOMAIN')}/b/{blog_obj.name}")
131
+ return redirect(f"{request.scheme}://{os.getenv('DOMAIN')}/b/{blog_obj.name}")
177
132
 
178
133
  if is_activitypub_request():
179
134
  url = f"https://{blog}.{g.main_domain}"
@@ -183,7 +138,7 @@ def show_subblog(blog):
183
138
  'Vary': 'Accept'
184
139
  })
185
140
 
186
- blog_obj.url = f"https://{blog_obj.name}.{os.getenv('DOMAIN')}"
141
+ blog_obj.url = f"{request.scheme}://{blog_obj.name}.{os.getenv('DOMAIN')}"
187
142
  pages = g.db.query(Page).filter_by(blog=blog_obj.id).all()
188
143
  homepage = g.db.query(Page).filter(Page.blog == blog_obj.id, Page.url == "").first()
189
144
 
@@ -202,7 +157,7 @@ def show_post(blog, post):
202
157
  return serve_rss(blog_obj)
203
158
 
204
159
  if blog_obj.access == "domain":
205
- return redirect(f"https://{blog_obj.name}.{os.getenv('DOMAIN')}/{post}")
160
+ return redirect(f"{request.scheme}://{blog_obj.name}.{os.getenv('DOMAIN')}/{post}")
206
161
 
207
162
  blog_obj.url = f"/b/{blog_obj.name}"
208
163
  pages = g.db.query(Page).filter_by(blog=blog_obj.id).all()
@@ -266,15 +221,15 @@ def show_subpost(blog, post):
266
221
  return redirect("/")
267
222
  blog_obj = g.db.query(Blog).filter_by(name=blog).first()
268
223
  if blog_obj is None:
269
- return redirect(f"https://{os.getenv('DOMAIN')}/")
224
+ return redirect(f"{request.scheme}://{os.getenv('DOMAIN')}/")
270
225
 
271
226
  if post == "rss":
272
227
  return serve_rss(blog_obj)
273
228
 
274
229
  if blog_obj.access == "path":
275
- return redirect(f"https://{os.getenv('DOMAIN')}/b/{blog_obj.name}/{post}")
230
+ return redirect(f"{request.scheme}://{os.getenv('DOMAIN')}/b/{blog_obj.name}/{post}")
276
231
 
277
- blog_obj.url = f"https://{blog_obj.name}.{os.getenv('DOMAIN')}"
232
+ blog_obj.url = f"{request.scheme}://{blog_obj.name}.{os.getenv('DOMAIN')}"
278
233
 
279
234
  pages = g.db.query(Page).filter_by(blog=blog_obj.id).all()
280
235
  one_post = g.db.query(Post).filter(Post.blog == blog_obj.id, Post.link == post, Post.isdraft == "0").first()
@@ -298,7 +253,7 @@ def show_subpost(blog, post):
298
253
 
299
254
  post_author = g.db.query(User).filter_by(id=blog_obj.owner).first()
300
255
  one_post.authorname = post_author.username
301
- blog_obj.url = f"https://{blog_obj.name}.{os.getenv('DOMAIN')}"
256
+ blog_obj.url = f"{request.scheme}://{blog_obj.name}.{os.getenv('DOMAIN')}"
302
257
 
303
258
  user_agent = request.headers.get('User-Agent')
304
259
  ip = anonymize(get_ip())
@@ -347,11 +302,10 @@ def webfinger():
347
302
  if not blog_obj:
348
303
  abort(404)
349
304
 
350
- if blog_obj.access == "domain":
351
- blog_url = f"https://{username}.{g.main_domain}"
352
- else:
353
- blog_url = f"https://{g.main_domain}/b/{username}"
354
-
305
+ if domain != g.main_domain:
306
+ abort(404)
307
+ blog_url = actor_url(blog_obj)
308
+
355
309
  data = {
356
310
  "subject": f"acct:{username}@{g.main_domain}",
357
311
  "links": [{
@@ -379,9 +333,9 @@ def subdomain_webfinger(blog):
379
333
  if "@" in account_part:
380
334
  username = account_part.split("@")[0]
381
335
  corrected_resource = f"acct:{username}@{g.main_domain}"
382
- return redirect(f"https://{g.main_domain}/.well-known/webfinger?resource={corrected_resource}")
336
+ return redirect(f"{request.scheme}://{g.main_domain}/.well-known/webfinger?resource={corrected_resource}")
383
337
 
384
- return redirect(f"https://{g.main_domain}/.well-known/webfinger?resource={resource}")
338
+ return redirect(f"{request.scheme}://{g.main_domain}/.well-known/webfinger?resource={resource}")
385
339
 
386
340
  @blog_bp.route("/p/<post>")
387
341
  def single_showpost(post):
@@ -503,10 +457,15 @@ def like():
503
457
 
504
458
 
505
459
  def serve_rss(blog):
506
- """
507
- Here we just serve static RSS files :)
508
- """
509
- return send_file(f"{g.mainpath}/static/feed/{blog.name}.xml")
460
+ """Generate a missing feed, then serve the cached file as usual."""
461
+ if blog is None:
462
+ abort(404)
463
+ directory = current_app.config.get('RSS_DIRECTORY', f'{g.mainpath}/static/feed')
464
+ path = os.path.join(directory, f'{blog.name}.xml')
465
+ if not os.path.isfile(path):
466
+ from openwrite.routes.dashboard import generate_rss
467
+ generate_rss(blog.name)
468
+ return send_file(path, mimetype='application/rss+xml')
510
469
 
511
470
 
512
471
  @blog_bp.route("/comment", methods=['POST'])
@@ -574,7 +533,8 @@ def autosave_post(blog_name):
574
533
  session_key = data.get('session_key') if post_id is None else None
575
534
 
576
535
  now = datetime.now(timezone.utc).replace(microsecond=0)
577
- draft = PostDraft(post_id=post_id, blog_id=blog.id, session_key=session_key, title=title, content_raw=content_raw, saved_at=now)
536
+ draft = PostDraft(post_id=post_id, blog_id=blog.id, session_key=session_key, title=title, content_raw=content_raw, saved_at=now,
537
+ toc_enabled='1' if data.get('toc_enabled') == '1' else '0')
578
538
  g.db.add(draft)
579
539
  g.db.flush()
580
540
 
@@ -613,5 +573,6 @@ def get_autosaves(blog_name):
613
573
  'id': d.id,
614
574
  'title': d.title,
615
575
  'content_raw': d.content_raw,
576
+ 'toc_enabled': d.toc_enabled,
616
577
  'saved_at': d.saved_at.strftime('%Y-%m-%dT%H:%M:%SZ')
617
578
  } for d in drafts])