Add 8 new tools: Hash Verifier, URL Tool, String Utils, Cron Explainer, IP Calc, Lorem Ipsum, CSV Viewer, Notes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Nirodan
2026-05-06 09:10:25 +02:00
parent ef03e76950
commit 75062dbf5e
20 changed files with 1727 additions and 2 deletions
+8
View File
@@ -10,3 +10,11 @@ from .markdown_tool import markdown_blueprint
from .colorconverter import color_blueprint
from .jsonformatter import json_formatter_blueprint
from .regextester import regex_blueprint
from .hashverifier import hashverifier_blueprint
from .urltool import url_blueprint
from .stringutils import stringutils_blueprint
from .cronexplainer import cron_blueprint
from .ipcalc import ipcalc_blueprint
from .loremipsum import lorem_blueprint
from .csvviewer import csv_blueprint
from .notes import notes_blueprint
+166
View File
@@ -0,0 +1,166 @@
from flask import Blueprint, request, jsonify
from datetime import datetime, timedelta
from util.logger import logger
from auth.token import verify_token
cron_blueprint = Blueprint('cron_tool', __name__)
MONTH_NAMES = {
'1': 'Januar', '2': 'Februar', '3': 'März', '4': 'April',
'5': 'Mai', '6': 'Juni', '7': 'Juli', '8': 'August',
'9': 'September', '10': 'Oktober', '11': 'November', '12': 'Dezember',
}
WEEKDAY_NAMES = {
'0': 'Sonntag', '1': 'Montag', '2': 'Dienstag', '3': 'Mittwoch',
'4': 'Donnerstag', '5': 'Freitag', '6': 'Samstag', '7': 'Sonntag',
}
def parse_field(field, min_val, max_val):
values = set()
for part in field.split(','):
part = part.strip()
if part == '*':
values.update(range(min_val, max_val + 1))
elif '/' in part:
base, step = part.split('/', 1)
step = int(step)
if base == '*':
start, end = min_val, max_val
elif '-' in base:
s, e = base.split('-')
start, end = int(s), int(e)
else:
start, end = int(base), max_val
values.update(range(start, end + 1, step))
elif '-' in part:
s, e = part.split('-')
values.update(range(int(s), int(e) + 1))
else:
values.add(int(part))
return sorted(v for v in values if min_val <= v <= max_val)
def explain_field_text(field, unit, plural, labels=None):
if field == '*':
return f"Jede(n) {unit}"
parts = field.split(',')
described = []
for p in parts:
if p.startswith('*/'):
described.append(f"alle {p[2:]} {plural}")
elif '-' in p and '/' not in p:
a, b = p.split('-', 1)
a_l = labels.get(a, a) if labels else a
b_l = labels.get(b, b) if labels else b
described.append(f"{a_l} bis {b_l}")
elif '/' in p:
base, step = p.split('/', 1)
if '-' in base:
a, b = base.split('-', 1)
described.append(f"{a}{b} alle {step}")
else:
described.append(f"ab {base} alle {step}")
else:
described.append(labels.get(p, p) if labels else p)
return ', '.join(described)
def build_summary(minute, hour, day, month, weekday):
parts = []
if minute == '*' and hour == '*':
parts.append("Jede Minute")
elif minute.startswith('*/') and hour == '*':
parts.append(f"Alle {minute[2:]} Minuten")
elif hour != '*' and minute == '0':
h = explain_field_text(hour, 'Stunde', 'Stunden')
parts.append(f"Um {h}:00 Uhr")
else:
parts.append(f"Minute: {minute}, Stunde: {hour}")
if weekday != '*' and day == '*':
wd = explain_field_text(weekday, 'Wochentag', 'Wochentage', WEEKDAY_NAMES)
parts.append(f"jeden {wd}")
elif day != '*' and weekday == '*':
parts.append(f"am {day}. des Monats")
elif day != '*' and weekday != '*':
wd = explain_field_text(weekday, 'Wochentag', 'Wochentage', WEEKDAY_NAMES)
parts.append(f"am {day}. oder {wd}")
if month != '*':
m = explain_field_text(month, 'Monat', 'Monate', MONTH_NAMES)
parts.append(f"im {m}")
return ', '.join(parts)
def get_next_runs(minute_vals, hour_vals, day_vals, month_vals, weekday_vals):
# cron weekday 0=Sun..6=Sat,7=Sun -> python weekday 0=Mon..6=Sun
py_weekdays = set((wd + 6) % 7 for wd in weekday_vals)
minute_set = set(minute_vals)
hour_set = set(hour_vals)
day_set = set(day_vals)
month_set = set(month_vals)
now = datetime.now().replace(second=0, microsecond=0) + timedelta(minutes=1)
results = []
current = now
for _ in range(2 * 366 * 24 * 60):
if len(results) >= 5:
break
if (current.month in month_set and
current.day in day_set and
current.weekday() in py_weekdays and
current.hour in hour_set and
current.minute in minute_set):
results.append(current.isoformat())
current += timedelta(minutes=1)
return results
@cron_blueprint.route('/api/cron/explain', methods=['POST'])
def explain_cron():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json() or {}
expression = data.get("expression", "").strip()
fields = expression.split()
if len(fields) != 5:
return jsonify({"error": "Cron-Ausdruck muss genau 5 Felder haben (Minute Stunde Tag Monat Wochentag)"}), 400
minute_f, hour_f, day_f, month_f, weekday_f = fields
try:
minute_vals = parse_field(minute_f, 0, 59)
hour_vals = parse_field(hour_f, 0, 23)
day_vals = parse_field(day_f, 1, 31)
month_vals = parse_field(month_f, 1, 12)
weekday_vals = parse_field(weekday_f, 0, 7)
except (ValueError, ZeroDivisionError) as e:
return jsonify({"error": f"Ungültiger Cron-Ausdruck: {e}"}), 400
field_descriptions = {
"minute": explain_field_text(minute_f, 'Minute', 'Minuten'),
"hour": explain_field_text(hour_f, 'Stunde', 'Stunden'),
"day": explain_field_text(day_f, 'Tag', 'Tage'),
"month": explain_field_text(month_f, 'Monat', 'Monate', MONTH_NAMES),
"weekday": explain_field_text(weekday_f, 'Wochentag', 'Wochentage', WEEKDAY_NAMES),
}
next_runs = get_next_runs(minute_vals, hour_vals, day_vals, month_vals, weekday_vals)
return jsonify({
"explanation": build_summary(minute_f, hour_f, day_f, month_f, weekday_f),
"fields": field_descriptions,
"next_runs": next_runs,
})
except Exception as e:
logger.error(f"Fehler cron explain: {e}")
return jsonify({"error": "Fehler beim Verarbeiten des Ausdrucks"}), 500
+50
View File
@@ -0,0 +1,50 @@
from flask import Blueprint, request, jsonify
import csv
import io
from util.logger import logger
from auth.token import verify_token
csv_blueprint = Blueprint('csv_tool', __name__)
MAX_ROWS = 500
@csv_blueprint.route('/api/csv/parse', methods=['POST'])
def parse_csv():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json() or {}
text = data.get("text", "")
delimiter = data.get("delimiter", ",")
# Handle escaped tab
if delimiter == "\\t" or delimiter == "\t":
delimiter = "\t"
if not delimiter:
delimiter = ","
reader = csv.reader(io.StringIO(text), delimiter=delimiter)
all_rows = list(reader)
if not all_rows:
return jsonify({"headers": [], "rows": [], "total_rows": 0, "truncated": False})
headers = all_rows[0]
data_rows = all_rows[1:]
total_rows = len(data_rows)
truncated = total_rows > MAX_ROWS
return jsonify({
"headers": headers,
"rows": data_rows[:MAX_ROWS],
"total_rows": total_rows,
"truncated": truncated,
})
except csv.Error as e:
return jsonify({"message": f"Ungültiges CSV: {e}"}), 400
except Exception as e:
logger.error(f"Fehler csvviewer: {e}")
return jsonify({"message": "Fehler beim Verarbeiten des CSV"}), 500
+39
View File
@@ -0,0 +1,39 @@
from flask import Blueprint, request, jsonify
import hashlib
import bcrypt
from util.logger import logger
from auth.token import verify_token
hashverifier_blueprint = Blueprint('hashverifier_tool', __name__)
@hashverifier_blueprint.route('/api/hash/verify', methods=['POST'])
def verify_hash():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json() or {}
text = data.get("text", "")
hash_val = data.get("hash", "").strip()
algorithm = data.get("algorithm", "sha256")
if algorithm == "md5":
computed = hashlib.md5(text.encode()).hexdigest()
match = computed.lower() == hash_val.lower()
elif algorithm == "sha256":
computed = hashlib.sha256(text.encode()).hexdigest()
match = computed.lower() == hash_val.lower()
elif algorithm == "bcrypt":
try:
match = bcrypt.checkpw(text.encode(), hash_val.encode())
except Exception:
return jsonify({"message": "Ungültiger bcrypt-Hash"}), 400
else:
return jsonify({"message": "Unbekannter Algorithmus"}), 400
logger.info(f"Hash verify ({algorithm}) von {user['username']}: {match}")
return jsonify({"match": match})
except Exception as e:
logger.error(f"Fehler bei Hash-Verifikation: {e}")
return jsonify({"message": "Fehler bei der Verifikation"}), 500
+49
View File
@@ -0,0 +1,49 @@
from flask import Blueprint, request, jsonify
import ipaddress
from util.logger import logger
from auth.token import verify_token
ipcalc_blueprint = Blueprint('ipcalc_tool', __name__)
@ipcalc_blueprint.route('/api/ip/calculate', methods=['POST'])
def ip_calculate():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json() or {}
cidr = data.get("cidr", "").strip()
try:
network = ipaddress.IPv4Network(cidr, strict=False)
except ValueError as e:
return jsonify({"message": f"Ungültige CIDR-Notation: {e}"}), 400
hosts = list(network.hosts())
first_host = str(hosts[0]) if hosts else str(network.network_address)
last_host = str(hosts[-1]) if hosts else str(network.broadcast_address)
total_hosts = len(hosts)
# Wildcard = inverse of netmask
netmask_int = int(network.netmask)
wildcard_int = (~netmask_int) & 0xFFFFFFFF
wildcard = str(ipaddress.IPv4Address(wildcard_int))
ip_class = "Privat" if network.is_private else "Öffentlich"
return jsonify({
"network": str(network.network_address),
"broadcast": str(network.broadcast_address),
"netmask": str(network.netmask),
"wildcard": wildcard,
"first_host": first_host,
"last_host": last_host,
"total_hosts": total_hosts,
"prefix_length": network.prefixlen,
"ip_class": ip_class,
})
except Exception as e:
logger.error(f"Fehler ipcalc: {e}")
return jsonify({"message": "Fehler bei der Berechnung"}), 500
+63
View File
@@ -0,0 +1,63 @@
from flask import Blueprint, request, jsonify
import random
from util.logger import logger
from auth.token import verify_token
lorem_blueprint = Blueprint('lorem_tool', __name__)
WORDS = [
"lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit",
"sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore",
"magna", "aliqua", "enim", "ad", "minim", "veniam", "quis", "nostrud",
"exercitation", "ullamco", "laboris", "nisi", "aliquip", "ex", "ea", "commodo",
"consequat", "duis", "aute", "irure", "reprehenderit", "voluptate", "velit",
"esse", "cillum", "eu", "fugiat", "nulla", "pariatur", "excepteur", "sint",
"occaecat", "cupidatat", "non", "proident", "sunt", "culpa", "qui", "officia",
"deserunt", "mollit", "anim", "id", "est", "laborum", "perspiciatis", "unde",
"omnis", "iste", "natus", "error", "voluptatem", "accusantium", "doloremque",
"laudantium", "totam", "rem", "aperiam", "eaque", "ipsa", "quae", "ab", "illo",
"inventore", "veritatis", "quasi", "architecto", "beatae", "vitae", "dicta",
"explicabo", "nemo", "ipsam", "quia", "voluptas", "aspernatur", "odit",
"fugit", "magni", "dolores", "ratione", "sequi", "nesciunt", "neque", "porro",
"quisquam", "adipisci", "numquam", "eius", "modi", "tempora", "incidunt",
"soluta", "nobis", "eligendi", "optio", "cumque", "nihil", "impedit", "minus",
"maxime", "placeat", "facere", "possimus", "omnis", "assumenda", "repellendus",
]
def make_sentence():
word_count = random.randint(8, 15)
words = [random.choice(WORDS) for _ in range(word_count)]
return words[0].capitalize() + ' ' + ' '.join(words[1:]) + '.'
def make_paragraph():
sentence_count = random.randint(4, 6)
return ' '.join(make_sentence() for _ in range(sentence_count))
@lorem_blueprint.route('/api/lorem/generate', methods=['POST'])
def generate_lorem():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json() or {}
gen_type = data.get("type", "sentences")
count = int(data.get("count", 3))
count = max(1, min(20, count))
if gen_type == "words":
text = ' '.join(random.choice(WORDS) for _ in range(count))
elif gen_type == "sentences":
text = ' '.join(make_sentence() for _ in range(count))
elif gen_type == "paragraphs":
text = '\n\n'.join(make_paragraph() for _ in range(count))
else:
return jsonify({"message": "Ungültiger Typ"}), 400
return jsonify({"text": text})
except Exception as e:
logger.error(f"Fehler lorem ipsum: {e}")
return jsonify({"message": "Fehler bei der Generierung"}), 500
+139
View File
@@ -0,0 +1,139 @@
from flask import Blueprint, request, jsonify
from datetime import datetime, timezone
from util.logger import logger
from util.db_pool import get_connection
from auth.token import verify_token
notes_blueprint = Blueprint('notes_tool', __name__)
def ensure_table():
conn = get_connection()
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS notes (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT,
language VARCHAR(50) DEFAULT 'text',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
""")
conn.commit()
cursor.close()
conn.close()
@notes_blueprint.route('/api/notes', methods=['GET'])
def get_notes():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
ensure_table()
conn = get_connection()
cursor = conn.cursor(dictionary=True)
cursor.execute(
"SELECT id, title, content, language, created_at, updated_at FROM notes WHERE user_id = %s ORDER BY updated_at DESC",
(user['id'],)
)
notes = cursor.fetchall()
cursor.close()
conn.close()
for n in notes:
if n.get('created_at'):
n['created_at'] = n['created_at'].isoformat()
if n.get('updated_at'):
n['updated_at'] = n['updated_at'].isoformat()
return jsonify(notes)
except Exception as e:
logger.error(f"Fehler notes GET: {e}")
return jsonify({"message": "Fehler beim Laden"}), 500
@notes_blueprint.route('/api/notes', methods=['POST'])
def create_note():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
ensure_table()
data = request.get_json() or {}
title = data.get("title", "Neue Notiz").strip() or "Neue Notiz"
content = data.get("content", "")
language = data.get("language", "text")
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"INSERT INTO notes (user_id, title, content, language) VALUES (%s, %s, %s, %s)",
(user['id'], title, content, language)
)
conn.commit()
note_id = cursor.lastrowid
cursor.close()
conn.close()
logger.info(f"Notiz erstellt von {user['username']}: id={note_id}")
return jsonify({"id": note_id, "title": title, "content": content, "language": language})
except Exception as e:
logger.error(f"Fehler notes POST: {e}")
return jsonify({"message": "Fehler beim Erstellen"}), 500
@notes_blueprint.route('/api/notes/<int:note_id>', methods=['PUT'])
def update_note(note_id):
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json() or {}
title = data.get("title", "").strip() or "Neue Notiz"
content = data.get("content", "")
language = data.get("language", "text")
now = datetime.now(timezone.utc).replace(tzinfo=None)
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"UPDATE notes SET title=%s, content=%s, language=%s, updated_at=%s WHERE id=%s AND user_id=%s",
(title, content, language, now, note_id, user['id'])
)
conn.commit()
affected = cursor.rowcount
cursor.close()
conn.close()
if affected == 0:
return jsonify({"message": "Notiz nicht gefunden"}), 404
return jsonify({"ok": True})
except Exception as e:
logger.error(f"Fehler notes PUT: {e}")
return jsonify({"message": "Fehler beim Speichern"}), 500
@notes_blueprint.route('/api/notes/<int:note_id>', methods=['DELETE'])
def delete_note(note_id):
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"DELETE FROM notes WHERE id=%s AND user_id=%s",
(note_id, user['id'])
)
conn.commit()
affected = cursor.rowcount
cursor.close()
conn.close()
if affected == 0:
return jsonify({"message": "Notiz nicht gefunden"}), 404
return jsonify({"ok": True})
except Exception as e:
logger.error(f"Fehler notes DELETE: {e}")
return jsonify({"message": "Fehler beim Löschen"}), 500
+53
View File
@@ -0,0 +1,53 @@
from flask import Blueprint, request, jsonify
from collections import Counter
import re
from util.logger import logger
from auth.token import verify_token
stringutils_blueprint = Blueprint('stringutils_tool', __name__)
@stringutils_blueprint.route('/api/string/analyze', methods=['POST'])
def string_analyze():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json() or {}
text = data.get("text", "")
operation = data.get("operation", "stats")
if operation == "stats":
words = text.split() if text.strip() else []
lines = text.split('\n')
return jsonify({
"operation": "stats",
"chars": len(text),
"chars_no_spaces": len(text.replace(' ', '')),
"words": len(words),
"lines": len(lines),
"spaces": text.count(' '),
})
elif operation == "uppercase":
return jsonify({"operation": "uppercase", "result": text.upper()})
elif operation == "lowercase":
return jsonify({"operation": "lowercase", "result": text.lower()})
elif operation == "titlecase":
return jsonify({"operation": "titlecase", "result": text.title()})
elif operation == "reverse":
return jsonify({"operation": "reverse", "result": text[::-1]})
elif operation == "trim":
return jsonify({"operation": "trim", "result": text.strip()})
elif operation == "remove_spaces":
return jsonify({"operation": "remove_spaces", "result": text.replace(' ', '')})
elif operation == "count_words":
words = re.findall(r'\b\w+\b', text.lower())
counter = Counter(words)
top10 = [{"word": w, "count": c} for w, c in counter.most_common(10)]
return jsonify({"operation": "count_words", "words": top10})
else:
return jsonify({"message": "Unbekannte Operation"}), 400
except Exception as e:
logger.error(f"Fehler stringutils: {e}")
return jsonify({"message": "Fehler bei der Verarbeitung"}), 500
+36
View File
@@ -0,0 +1,36 @@
from flask import Blueprint, request, jsonify
from urllib.parse import quote, unquote
from util.logger import logger
from auth.token import verify_token
url_blueprint = Blueprint('url_tool', __name__)
@url_blueprint.route('/api/url/encode', methods=['POST'])
def url_encode():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json() or {}
text = data.get("text", "")
result = quote(text, safe='')
return jsonify({"result": result})
except Exception as e:
logger.error(f"Fehler URL encode: {e}")
return jsonify({"message": "Fehler beim Encoding"}), 500
@url_blueprint.route('/api/url/decode', methods=['POST'])
def url_decode():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json() or {}
text = data.get("text", "")
result = unquote(text)
return jsonify({"result": result})
except Exception as e:
logger.error(f"Fehler URL decode: {e}")
return jsonify({"message": "Fehler beim Decoding"}), 500