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.
- {openwrite-0.16.1/openwrite.egg-info → openwrite-0.17.2}/PKG-INFO +21 -3
- {openwrite-0.16.1 → openwrite-0.17.2}/README.md +20 -2
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/__init__.py +12 -6
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/cli.py +4 -1
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/gemini_single.py +26 -25
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/routes/blog.py +34 -73
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/routes/dashboard.py +56 -173
- openwrite-0.17.2/openwrite/routes/federation.py +262 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/routes/main.py +2 -2
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/routes/upload.py +2 -7
- openwrite-0.17.2/openwrite/static/feed/default.xml +2 -0
- openwrite-0.17.2/openwrite/static/feed/test.xml +2 -0
- openwrite-0.17.2/openwrite/static/feed/testx.xml +2 -0
- openwrite-0.17.2/openwrite/static/script/post_preview.js +166 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/style.css +79 -0
- openwrite-0.17.2/openwrite/static/style/toc.css +28 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/blog.html +1 -1
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/blog_header.html +2 -1
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/blog_preview.html +1 -1
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/dashboard.html +2 -2
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/edit.html +6 -6
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/footer.html +1 -1
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/new_post.html +63 -18
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/page_preview.html +1 -1
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/post.html +2 -2
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/preview.html +3 -2
- openwrite-0.17.2/openwrite/utils/activitypub.py +154 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/utils/db.py +4 -1
- openwrite-0.17.2/openwrite/utils/helpers.py +200 -0
- openwrite-0.17.2/openwrite/utils/i18n.json +1 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/utils/migrations.py +3 -3
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/utils/models.py +44 -0
- openwrite-0.17.2/openwrite/utils/toc.py +47 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/utils/worker.py +37 -22
- {openwrite-0.16.1 → openwrite-0.17.2/openwrite.egg-info}/PKG-INFO +21 -3
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite.egg-info/SOURCES.txt +9 -27
- {openwrite-0.16.1 → openwrite-0.17.2}/pyproject.toml +1 -1
- openwrite-0.17.2/tests/test_federation.py +726 -0
- openwrite-0.16.1/openwrite/routes/federation.py +0 -329
- openwrite-0.16.1/openwrite/static/feed/74.xml +0 -12
- openwrite-0.16.1/openwrite/static/feed/a.xml +0 -2
- openwrite-0.16.1/openwrite/static/feed/anim3sh1t.xml +0 -12
- openwrite-0.16.1/openwrite/static/feed/b.xml +0 -2
- openwrite-0.16.1/openwrite/static/feed/corrode.xml +0 -4
- openwrite-0.16.1/openwrite/static/feed/dirtysanchez.xml +0 -6
- openwrite-0.16.1/openwrite/static/feed/eikerammen.xml +0 -165
- openwrite-0.16.1/openwrite/static/feed/fedi.xml +0 -9
- openwrite-0.16.1/openwrite/static/feed/friskt.xml +0 -2
- openwrite-0.16.1/openwrite/static/feed/gordi.xml +0 -6
- openwrite-0.16.1/openwrite/static/feed/igilq.xml +0 -226
- openwrite-0.16.1/openwrite/static/feed/io.xml +0 -50
- openwrite-0.16.1/openwrite/static/feed/isyuricunha.xml +0 -4
- openwrite-0.16.1/openwrite/static/feed/kenny.xml +0 -2
- openwrite-0.16.1/openwrite/static/feed/m0bi.xml +0 -6
- openwrite-0.16.1/openwrite/static/feed/main.xml +0 -236
- openwrite-0.16.1/openwrite/static/feed/mywritese.xml +0 -2
- openwrite-0.16.1/openwrite/static/feed/nay-thoughs.xml +0 -2
- openwrite-0.16.1/openwrite/static/feed/nintendoflesz.xml +0 -7
- openwrite-0.16.1/openwrite/static/feed/phronima.xml +0 -68
- openwrite-0.16.1/openwrite/static/feed/ram.xml +0 -75
- openwrite-0.16.1/openwrite/static/feed/samah.xml +0 -2
- openwrite-0.16.1/openwrite/static/feed/themes-test.xml +0 -18
- openwrite-0.16.1/openwrite/static/feed/totylkoja.xml +0 -78
- openwrite-0.16.1/openwrite/static/feed/vegafjord.xml +0 -48
- openwrite-0.16.1/openwrite/static/feed/vijayvaradharaj.xml +0 -2
- openwrite-0.16.1/openwrite/utils/helpers.py +0 -217
- openwrite-0.16.1/openwrite/utils/i18n.json +0 -1
- {openwrite-0.16.1 → openwrite-0.17.2}/LICENSE +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/MANIFEST.in +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/db/__init__.py +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/db/base.py +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/gemini_multi.py +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/routes/admin.py +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/routes/auth.py +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/IBMPlexSans-Regular.ttf +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/Merriweather.ttf +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/avatar.png +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/emoji.json +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/favicon.ico +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/favicon.png +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/feed/.gitkeep +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/logo.png +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/script/chart.js +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/script/highlight.min.js +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/script/nativeEmoji.js +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/script/openwrite.js +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/script/papaparse.min.js +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/script/showdown.min.js +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/script/showdown.min.js.map +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/script/site.compat.min.js +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/script/site.min.js +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/blog_design.css +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/default.css +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/hl.css +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/nativeEmoji.min.css +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/themes/cyberpunk.css +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/themes/light.css +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/themes/obscura.css +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/themes/paper.css +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/themes/sepia.css +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/themes/stickynote.css +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/themes/synthwave.css +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/themes/terminal.css +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/themes/warmnight.css +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/static/style/themes/windows98.css +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/admin.html +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/blog_footer.html +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/changepw.html +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/create.html +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/discover.html +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/header.html +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/import.html +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/index.html +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/instances.html +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/login.html +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/new_page.html +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/register.html +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/settings.html +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/stats.html +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/templates/translations.html +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/utils/create_db.py +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/utils/tags.py +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite/utils/valkey_client.py +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite.egg-info/dependency_links.txt +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite.egg-info/entry_points.txt +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite.egg-info/requires.txt +0 -0
- {openwrite-0.16.1 → openwrite-0.17.2}/openwrite.egg-info/top_level.txt +0 -0
- {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.
|
|
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
|
-
|
|
766
|
+
For `http://localhost:8081`, set these values in `.env`:
|
|
766
767
|
|
|
767
|
-
|
|
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
|
-
|
|
76
|
+
For `http://localhost:8081`, set these values in `.env`:
|
|
76
77
|
|
|
77
|
-
|
|
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"\n\n# Hello there! 👋\n\nYou can edit your blog home page in [dashboard](
|
|
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=\"
|
|
89
|
+
content_raw=f"\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(
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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'', 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"=> /
|
|
59
|
+
content = f"=> / 📓 {blog.title}\n\n"
|
|
59
60
|
content += page.content_raw
|
|
60
61
|
content = re.sub(r'!\[(.*?)\]\((.*?)\s*=\d+x\d+\)', r'', 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"=> /
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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"
|
|
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"
|
|
128
|
+
return redirect(f"{request.scheme}://{os.getenv('DOMAIN')}/")
|
|
174
129
|
|
|
175
130
|
if blog_obj.access == "path":
|
|
176
|
-
return redirect(f"
|
|
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"
|
|
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"
|
|
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"
|
|
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"
|
|
230
|
+
return redirect(f"{request.scheme}://{os.getenv('DOMAIN')}/b/{blog_obj.name}/{post}")
|
|
276
231
|
|
|
277
|
-
blog_obj.url = f"
|
|
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"
|
|
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
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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"
|
|
336
|
+
return redirect(f"{request.scheme}://{g.main_domain}/.well-known/webfinger?resource={corrected_resource}")
|
|
383
337
|
|
|
384
|
-
return redirect(f"
|
|
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
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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])
|