From b2eac9654bc0133d4d8fdcc1146490b084ce6aa6 Mon Sep 17 00:00:00 2001 From: mst Date: Mon, 16 Dec 2024 16:45:41 +0300 Subject: [PATCH] add ntfy support --- app.py | 40 ++++++++++--- config.example.json | 7 +++ functions.py | 43 ++++++++++++- templates/admin/base.html | 4 ++ templates/admin/categories/notifications.html | 60 +++++++++++++++++++ 5 files changed, 142 insertions(+), 12 deletions(-) create mode 100644 templates/admin/categories/notifications.html diff --git a/app.py b/app.py index 06c59d7..6b161cc 100644 --- a/app.py +++ b/app.py @@ -5,6 +5,7 @@ from mysql.connector import errorcode from functools import wraps from werkzeug.utils import secure_filename from pathlib import Path +import threading import requests import secrets import shutil @@ -232,6 +233,11 @@ def information(): def accessibility(): return render_template('admin/categories/accessibility.html') +@admin_bp.route('/notifications/', methods=['GET', 'POST']) +@loginRequired +def notifications(): + return render_template('admin/categories/notifications.html') + @admin_bp.route('/general/', methods=['GET', 'POST']) @loginRequired def general(): @@ -343,20 +349,36 @@ def pwaManifest(): "background_color": "" }) +# wip, scheduled for 1.8.0 release +# @api_bp.route('/hyper/widget/', methods=['GET']) +# def widget(): +# func_val = func.getAllQuestions() +# combined = func_val[0] +# metadata = func_val[1] +# return render_template('widget.html', combined=combined, urllib=urllib, trimContent=func.trimContent, metadata=metadata, getRandomWord=func.getRandomWord, formatRelativeTime=func.formatRelativeTime) + # -- question routes -- @api_bp.route('/add_question/', methods=['POST']) def addQuestion(): - from_who = request.form.get('from_who', cfg['anonName']) - question = request.form.get('question', '') - cw = request.form.get('cw', '') + try: + app.logger.debug("[CatAsk/API/add_question] started question flow") + from_who = request.form.get('from_who', cfg['anonName']) + question = request.form.get('question', '') + cw = request.form.get('cw', '') - if not question: - abort(400, "Question field must not be empty") - if len(question) > int(cfg['charLimit']) or len(from_who) > int(cfg['charLimit']): - abort(400, "Question exceeds the character limit") - - return func.addQuestion(from_who, question, cw) + if not question: + abort(400, "Question field must not be empty") + if len(question) > int(cfg['charLimit']) or len(from_who) > int(cfg['charLimit']): + abort(400, "Question exceeds the character limit") + return_val = func.addQuestion(from_who, question, cw) + app.logger.debug("[CatAsk/API/add_question] finished question flow") + return return_val[0] + finally: + if cfg['ntfy']['enabled']: + # cw, return_val, from_who, question + ntfy_thread = threading.Thread(target=func.ntfySend, name=f"ntfy thread for {return_val[2]}", args=(cw, return_val, from_who, question,)) + ntfy_thread.start() @api_bp.route('/delete_question/', methods=['DELETE']) @loginRequired diff --git a/config.example.json b/config.example.json index 917f34f..5566016 100644 --- a/config.example.json +++ b/config.example.json @@ -41,6 +41,13 @@ "apikey": "" } }, + "ntfy": { + "enabled": false, + "host": "https://ntfy.sh", + "user": "", + "pass": "", + "topic": "" + }, "trimContentAfter": "150", "charLimit": "512", "anonName": "Anonymous", diff --git a/functions.py b/functions.py index d4e9187..225d0a1 100644 --- a/functions.py +++ b/functions.py @@ -1,10 +1,11 @@ -from flask import url_for, request, jsonify, Flask +from flask import url_for, request, jsonify, Flask, abort from markupsafe import Markup from bleach.sanitizer import Cleaner from datetime import datetime, timezone from pathlib import Path from mistune import HTMLRenderer, escape from PIL import Image +import base64 import time import zipfile import shutil @@ -191,11 +192,12 @@ def addQuestion(from_who, question, cw, noAntispam=False): app.logger.debug("[CatAsk/API/add_question] INSERT'ing new question into database") - cursor.execute("INSERT INTO questions (from_who, content, answered, cw) VALUES (%s, %s, %s, %s)", (from_who, question, False, cw)) + cursor.execute("INSERT INTO questions (from_who, content, answered, cw) VALUES (%s, %s, %s, %s) RETURNING id", (from_who, question, False, cw)) + question_id = cursor.fetchone()[0] cursor.close() conn.close() - return {'message': 'Question asked successfully!'}, 201 + return {'message': 'Question asked successfully!'}, 201, question_id def getAnswer(question_id: int): conn = connectToDb() @@ -229,6 +231,41 @@ def addAnswer(question_id, answer, cw): return jsonify({'message': 'Answer added successfully!'}), 201 +def ntfySend(cw, return_val, from_who, question): + app.logger.debug("[CatAsk/functions/ntfySend] started ntfy flow") + ntfy_cw = f" [CW: {cw}]" if cw else "" + ntfy_host = cfg['ntfy']['host'] + ntfy_topic = cfg['ntfy']['topic'] + question_id = return_val[2] + # doesn't work otherwise + from_who = from_who if from_who else cfg['anonName'] + + if cfg['ntfy']['user'] and cfg['ntfy']['pass']: + ntfy_user = cfg['ntfy']['user'] + ntfy_pass = cfg['ntfy']['pass'] + ascii_auth = f"{ntfy_user}:{ntfy_pass}".encode('ascii') + b64_auth = base64.b64encode(ascii_auth) + # there's probably a better way to do this without duplicated code + headers={ + "Authorization": f"Basic {b64_auth.decode('ascii')}", + "Title": f"New question from {from_who}{ntfy_cw}", + "Actions": f"view, View question, {cfg['instance']['fullBaseUrl']}/inbox/#question-{question_id}", + "Tags": "question" + } + else: + headers={ + "Title": f"New question from {from_who}{ntfy_cw}", + "Actions": f"view, View question, {cfg['instance']['fullBaseUrl']}/inbox/#question-{question_id}", + "Tags": "question" + } + + r = requests.put( + f"{ntfy_host}/{ntfy_topic}".encode('utf-8'), + data=trimContent(question, int(cfg['trimContentAfter'])), + headers=headers + ) + app.logger.debug("[CatAsk/functions/ntfySend] finished ntfy flow") + def readPlainFile(file, split=False): if os.path.exists(file): with open(file, 'r', encoding="utf-8") as file: diff --git a/templates/admin/base.html b/templates/admin/base.html index 34e618b..397531d 100644 --- a/templates/admin/base.html +++ b/templates/admin/base.html @@ -45,6 +45,10 @@ Accessibility + + + Notifications + Customize diff --git a/templates/admin/categories/notifications.html b/templates/admin/categories/notifications.html new file mode 100644 index 0000000..b12c428 --- /dev/null +++ b/templates/admin/categories/notifications.html @@ -0,0 +1,60 @@ +{% extends 'admin/base.html' %} +{% block _title %}Notifications{% endblock %} +{% set notif_link = 'active' %} +{% block _content %} +
+

Notifications

+

Configure notifications for new questions using ntfy

+
+ + + +
+

Server & Topic

+
+ + / + +
+

Credentials (optional)

+

Set credentials if the topic is protected

+
+ + +

+ Topic user +

+
+
+ + +

+ Topic password +

+
+
+ +
+
+{% endblock %} +{% block _scripts %} + +{% endblock %}