From 82def98e7e54efe3c7d5ce5f8b5e9aadc9e06873 Mon Sep 17 00:00:00 2001 From: Jake Charman Date: Thu, 19 Feb 2026 12:46:43 +0000 Subject: [PATCH] First implementation of comments --- src/jakecharman/__init__.py | 4 +- src/jakecharman/comments.py | 60 ++++++++++++++++++++++++++ src/jakecharman/content.py | 17 ++++++-- src/jakecharman/templates/article.html | 19 ++++++++ src/static/style/mobile.css | 11 +++++ src/templates/article.html | 20 +++++++++ 6 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 src/jakecharman/comments.py create mode 100755 src/templates/article.html diff --git a/src/jakecharman/__init__.py b/src/jakecharman/__init__.py index b5829e7..dafa2e0 100644 --- a/src/jakecharman/__init__.py +++ b/src/jakecharman/__init__.py @@ -16,6 +16,7 @@ from .content import ContentArea from .contact import ContactForm from .storage import LocalStorage from .links import Links +from .comments import * app = Flask(__name__) @@ -29,6 +30,7 @@ projects = ContentArea( app.register_blueprint(projects, url_prefix='/projects') 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(Approval(path.join(projects.md_directory.uri, 'comments.db'), 'comments', __name__), url_prefix='/comments') class DiscordLogger(logging.Handler): ''' Simple logging handler to send a message to Discord ''' @@ -140,7 +142,7 @@ def sitemap(): url = ET.SubElement(root, 'url') ET.SubElement(url, 'loc').text = base_url + route 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: continue url = ET.SubElement(root, 'url') diff --git a/src/jakecharman/comments.py b/src/jakecharman/comments.py new file mode 100644 index 0000000..8beb3de --- /dev/null +++ b/src/jakecharman/comments.py @@ -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/', 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) diff --git a/src/jakecharman/content.py b/src/jakecharman/content.py index ce9fb63..b42bb1a 100755 --- a/src/jakecharman/content.py +++ b/src/jakecharman/content.py @@ -6,8 +6,9 @@ from datetime import datetime import frontmatter from markdown import markdown 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 .comments import PostComments, Approval class ContentArea(Blueprint): 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('/category//', view_func=self.category) self.add_url_rule('/', view_func=self.article) - #self.add_url_rule('/image/', view_func=self.image) + self.add_url_rule('//comment', view_func=self.comment, methods=['POST']) def processor(self) -> dict: ''' Jninja processors ''' @@ -140,6 +141,16 @@ class ContentArea(Blueprint): return Response(status=500) 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), metadata=the_article.metadata, - page_title=f'{the_article.metadata["title"]} - ') \ No newline at end of file + 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') \ No newline at end of file diff --git a/src/jakecharman/templates/article.html b/src/jakecharman/templates/article.html index 74a9689..4d48950 100755 --- a/src/jakecharman/templates/article.html +++ b/src/jakecharman/templates/article.html @@ -22,4 +22,23 @@ {{post|safe}} +
+

{{comments | length}} Comments

+ {% for comment in comments %} +
+ {{ comment[0] }} - {{ comment[2] | human_date }} +

{{ comment[1] }}

+
+ {% endfor %} +

Leave a comment

+ {% if request.args.get('comment') is none %} +
+ + + +
+ {% else %} +

Thank you! Your comment will appear once it is approved

+ {% endif %} +
{% endblock %} \ No newline at end of file diff --git a/src/static/style/mobile.css b/src/static/style/mobile.css index bf8b704..d1f1ae0 100755 --- a/src/static/style/mobile.css +++ b/src/static/style/mobile.css @@ -328,4 +328,15 @@ pre{ padding: 5px; height: 31vw; object-fit: cover; +} + +#comments { + border-top: 1px solid #e5e5e5; +} + +.comment { + background-color: #4c4c4c; + margin: 10px; + padding: 5px; + border-radius: 10px; } \ No newline at end of file diff --git a/src/templates/article.html b/src/templates/article.html new file mode 100755 index 0000000..50fcafd --- /dev/null +++ b/src/templates/article.html @@ -0,0 +1,20 @@ +{% include 'header.html' %} + +
+
+

{{ metadata.title}}

+

{{ metadata.date | human_date }}

+
+ {{post|safe}} +
+
+
+
+ + + + + +
+
+{% include 'footer.html' %} \ No newline at end of file