First implementation of comments

This commit is contained in:
2026-02-19 12:46:43 +00:00
parent a68033df6e
commit 82def98e7e
6 changed files with 127 additions and 4 deletions

View File

@@ -16,6 +16,7 @@ from .content import ContentArea
from .contact import ContactForm from .contact import ContactForm
from .storage import LocalStorage from .storage import LocalStorage
from .links import Links from .links import Links
from .comments import *
app = Flask(__name__) app = Flask(__name__)
@@ -29,6 +30,7 @@ projects = ContentArea(
app.register_blueprint(projects, url_prefix='/projects') app.register_blueprint(projects, url_prefix='/projects')
app.register_blueprint(ContactForm('contact', __name__), url_prefix='/contact') app.register_blueprint(ContactForm('contact', __name__), url_prefix='/contact')
app.register_blueprint(Links(path.join(md_path, 'links.json'), 'links', __name__), url_prefix='/links') app.register_blueprint(Links(path.join(md_path, 'links.json'), 'links', __name__), url_prefix='/links')
app.register_blueprint(Approval(path.join(projects.md_directory.uri, 'comments.db'), 'comments', __name__), url_prefix='/comments')
class DiscordLogger(logging.Handler): class DiscordLogger(logging.Handler):
''' Simple logging handler to send a message to Discord ''' ''' Simple logging handler to send a message to Discord '''
@@ -140,7 +142,7 @@ def sitemap():
url = ET.SubElement(root, 'url') url = ET.SubElement(root, 'url')
ET.SubElement(url, 'loc').text = base_url + route ET.SubElement(url, 'loc').text = base_url + route
ET.SubElement(url, 'lastmod').text = date ET.SubElement(url, 'lastmod').text = date
for article in projects.get_all_posts(): for article in projects.get_live_posts():
if 'link' in article.metadata: if 'link' in article.metadata:
continue continue
url = ET.SubElement(root, 'url') url = ET.SubElement(root, 'url')

View File

@@ -0,0 +1,60 @@
#!/usr/bin/python3
import sqlite3
from flask import Blueprint, Response, request
from uuid import uuid4
from requests import post
from os import environ
class PostComments():
def __init__(self, post_id: str, db_path: str):
self.__db_path = db_path
self.__post_id = post_id
self._webhook = environ['DISCORD_WEBHOOK']
with sqlite3.connect(db_path) as db:
cursor = db.cursor()
cursor.execute("CREATE TABLE IF NOT EXISTS comments (name TEXT, comment TEXT, date INT, post_id TEXT, approved BOOL, key TEXT)")
self.comments = cursor.execute(
"SELECT name, comment, date FROM comments WHERE approved = 1 AND post_id = ? ORDER BY date DESC",
(post_id,)
).fetchall()
def send_to_discord(self, name: str, comment: str, comment_id: int, key: str):
''' Send the message '''
message_to_send = f'New comment from {name}\n\n{comment}'
if len(message_to_send) > 2000:
chars_to_lose = len(message_to_send) - 1900
message_to_send = message_to_send[-chars_to_lose:]
message_to_send += f'\n\n[Approve](https://jakecharman.co.uk/comments/approve/{comment_id}?key={key})'
post(self._webhook, data={'content': message_to_send}, timeout=30)
def make_comment(self, name: str, comment: str):
with sqlite3.connect(self.__db_path) as db:
key = str(uuid4())
cursor = db.cursor()
cursor.execute(
"INSERT INTO comments (name, comment, date, post_id, approved, key) VALUES (?, ?, datetime('now'), ?, 0, ?)",
(name, comment, self.__post_id, key)
)
db.commit()
self.send_to_discord(name, comment, cursor.lastrowid, key)
return cursor.lastrowid
class Approval(Blueprint):
def __init__(self, db_path: str, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__db_path = db_path
self.add_url_rule('/approve/<comment_id>', view_func=self.approve)
def approve(self, comment_id: str):
with sqlite3.connect(self.__db_path) as db:
cursor = db.cursor()
key = cursor.execute("SELECT key FROM comments WHERE rowid = ?", (comment_id,)).fetchone()[0]
if request.args.get('key') == key:
with sqlite3.connect(self.__db_path) as db:
cursor = db.cursor()
cursor.execute("UPDATE comments SET approved = 1 WHERE rowid = ?", (comment_id,))
db.commit()
return Response(status=200)
return Response(status=403)

View File

@@ -6,8 +6,9 @@ from datetime import datetime
import frontmatter import frontmatter
from markdown import markdown from markdown import markdown
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from flask import render_template, Response, Blueprint from flask import render_template, Response, Blueprint, request, redirect
from .storage import LocalStorage from .storage import LocalStorage
from .comments import PostComments, Approval
class ContentArea(Blueprint): class ContentArea(Blueprint):
def __init__(self, directory: LocalStorage, *args, **kwargs): def __init__(self, directory: LocalStorage, *args, **kwargs):
@@ -24,7 +25,7 @@ class ContentArea(Blueprint):
self.add_url_rule('/', view_func=self.projects) self.add_url_rule('/', view_func=self.projects)
self.add_url_rule('/category/<category_id>/', view_func=self.category) self.add_url_rule('/category/<category_id>/', view_func=self.category)
self.add_url_rule('/<article_id>', view_func=self.article) self.add_url_rule('/<article_id>', view_func=self.article)
#self.add_url_rule('/image/<image_name>', view_func=self.image) self.add_url_rule('/<article_id>/comment', view_func=self.comment, methods=['POST'])
def processor(self) -> dict: def processor(self) -> dict:
''' Jninja processors ''' ''' Jninja processors '''
@@ -140,6 +141,16 @@ class ContentArea(Blueprint):
return Response(status=500) return Response(status=500)
the_article = articles[0] the_article = articles[0]
comments = PostComments(the_article.metadata['id'], path.join(self.md_directory.uri, 'comments.db')).comments
return render_template('article.html', post=markdown(the_article.content), return render_template('article.html', post=markdown(the_article.content),
metadata=the_article.metadata, metadata=the_article.metadata,
page_title=f'{the_article.metadata["title"]} - ') comments = comments,
page_title=f'{the_article.metadata["title"]} - ')
def comment(self, article_id: str):
PostComments(article_id, path.join(self.md_directory.uri, 'comments.db')).make_comment(
request.form['name'],
request.form['comment']
)
return redirect(f'/projects/{article_id}?comment=true#comments')

View File

@@ -22,4 +22,23 @@
{{post|safe}} {{post|safe}}
</section> </section>
</main> </main>
<section id="comments">
<h2>{{comments | length}} Comments</h2>
{% for comment in comments %}
<div class="comment">
<strong>{{ comment[0] }} - {{ comment[2] | human_date }}</strong>
<p>{{ comment[1] }}</p>
</div>
{% endfor %}
<h3>Leave a comment</h3>
{% if request.args.get('comment') is none %}
<form action="./{{metadata.id}}/comment" method="post">
<input type="text", name="name" placeholder="Name" required>
<textarea name="comment" placeholder="Comment" required></textarea>
<input type="submit" value="Submit">
</form>
{% else %}
<p>Thank you! Your comment will appear once it is approved</p>
{% endif %}
</section>
{% endblock %} {% endblock %}

View File

@@ -328,4 +328,15 @@ pre{
padding: 5px; padding: 5px;
height: 31vw; height: 31vw;
object-fit: cover; object-fit: cover;
}
#comments {
border-top: 1px solid #e5e5e5;
}
.comment {
background-color: #4c4c4c;
margin: 10px;
padding: 5px;
border-radius: 10px;
} }

20
src/templates/article.html Executable file
View File

@@ -0,0 +1,20 @@
{% include 'header.html' %}
<script src="/static/js/get_comments.js"></script>
<main>
<section id="article">
<h1>{{ metadata.title}} </h1>
<p>{{ metadata.date | human_date }}</p>
<hr />
{{post|safe}}
</section>
<section id="comments">
</section>
<form action="/comments/{{ metadata.id }}" method="post">
<label for="name">Name:</label>
<input type="text" name="name" />
<label for="comment">Comment:</label>
<textarea name="comment"></textarea>
<input type="submit" name="submit" value="Submit">
</form>
</main>
{% include 'footer.html' %}