initialize repo

This commit is contained in:
Görkem 2026-01-29 11:02:44 -08:00
parent eb4dd15e36
commit da83e1e956
4 changed files with 164 additions and 1 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

9
Dockerfile Normal file
View File

@ -0,0 +1,9 @@
FROM python:3.9-slim
WORKDIR /openguestbook
RUN pip install flask flask-cors requests gunicorn
COPY . .
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "app:app"]

View File

@ -1,2 +1,38 @@
# OpenGuestbook
# 📖 OpenGuestbook
![Human coded](https://img.shields.io/badge/human-coded-green?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0ibHVjaWRlIGx1Y2lkZS1wZXJzb24tc3RhbmRpbmctaWNvbiBsdWNpZGUtcGVyc29uLXN0YW5kaW5nIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjUiIHI9IjEiLz48cGF0aCBkPSJtOSAyMCAzLTYgMyA2Ii8+PHBhdGggZD0ibTYgOCA2IDIgNi0yIi8+PHBhdGggZD0iTTEyIDEwdjQiLz48L3N2Zz4=)
[![License: GNU General Public License v3.0](https://img.shields.io/badge/License-GNU%20GPL--v3.0-yellow)](https://www.gnu.org/licenses/gpl-3.0.en.html#license-text)
OpenGuestbook is a self-hosted lightweight Guestbook for small static websites made using python and flask. OpenGuestbook does not use databases or an admin panel for managing entries. Instead it saves each guestbook entry as a file with a human readable format in a /guestbook folder in your server. The comments can be moderated by deleting or editing files manually.
## Features
- No login required
- No database required
- Limited total comments per day.
- Easy setup, and low CPU and RAM use.
- Privacy Friendly: No tracking, no cookies, AD blocker friendly.
- Notification support via Notfy.sh!
## Deployment
OpenGuestbook comes in two parts, a backend that needs to be hosted on a server, and a JS file that cn be embedded directly on your website.
### Backend deployment with Docker
For production, running via Docker Compose is recommended.
#### 1) Create docker-compose.yml
```yaml
version: '3.8'
services:
guestbook:
build: .
container_name: OpenGuestbook
restart: always
ports:
- "5000:5000"
volumes:
- ./guestbook_data:/openguestbook/guestbook # Persist comments on host to make it easily reachable
environment:
- FRONTEND_URL=<https://yourwebsite.com> # if a frontend url is not provided the app will default the allow all utl's which is not recommended!
- NTFY_TOPIC=<enter-ntfy-topic> # for psuh notification support (optional)
```
## Dependencies
## Configuration

118
app.py Normal file
View File

@ -0,0 +1,118 @@
import os
import json
import time
import requests
from datetime import datetime
from flask import Flask, request, jsonify
from flask_cors import CORS
app = Flask(__name__)
# configure daily limit and data directory.
DAILY_LIMIT = 50
DATA_DIR = 'guestbook'
currentDate = datetime.now().strftime('%Y-%m-%d')
submissionCountDay = 0
frontend_url = os.environ.get("FRONTEND_URL","*")
topic = os.environ.get("NTFY_TOPIC")
CORS(app, resources={r"/*": {"origins": frontend_url}})
if not os.path.exists(DATA_DIR):
os.makedirs(DATA_DIR)
@app.route('/comments', methods=['GET'])
def getComments():
comments = []
try:
files = sorted([f for f in os.listdir(DATA_DIR) if f.endswith('.json')], reverse=True)
for filename in files:
filepath = os.path.join(DATA_DIR, filename)
with open(filepath, 'r', encoding='utf-8') as f:
try:
data = json.load(f)
comments.append(data)
except json.JSONDecodeError:
continue
return jsonify(comments)
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/comments', methods=['POST'])
def addComment():
global currentDate, submissionCountDay
# Check date
today_str = datetime.now().strftime('%Y-%m-%d')
if today_str != currentDate:
currentDate = today_str
submissionCountDay = 0
# Check limit
if submissionCountDay >= DAILY_LIMIT:
return jsonify({"error": "Guestbook full for the day, try tomorrow!"}), 403
data = request.json
name = data.get('name', '').strip()
message = data.get('message', '').strip()
website = data.get('website', '').strip()
if not name or not message:
return jsonify({"error": "Missing fields"}), 400
#URL cleanup
if website:
# If forgot http://, add it for them
if not website.startswith(('http://', 'https://')):
website = 'https://' + website
entry = {
'name': name,
'message': message,
'website': website,
'date': time.strftime("%d-%m-%Y %H:%M")
}
now = datetime.now()
readable_time = now.strftime('%Y-%m-%d_%H-%M-%S')
filename = f"{readable_time}.json"
filepath = os.path.join(DATA_DIR, filename)
try:
with open(filepath, 'x', encoding='utf-8') as f:
json.dump(entry, f)
except FileExistsError:
filename = f"{readable_time}_2.json"
filepath = os.path.join(DATA_DIR, filename)
with open(filepath, 'x', encoding='utf-8') as f:
json.dump(entry, f)
send_ntfy_notification(name, message)
submissionCountDay += 1
return jsonify({"status": "success"})
def send_ntfy_notification(name, message):
if not topic:
return
try:
requests.post(f"https://ntfy.sh/{topic}",
data=f"{name} wrote: {message}",
headers={
"Title": "Someone Signed Your Guestbook!"
})
except Exception as e:
print(f"Notification failed: {e}")
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)