diff --git a/README.md b/README.md index ab542f9..efcc443 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ By popular demand, I'm building a better anonkun. It doesn't do much right now t Python 3.6+ PostgreSQL 10.4+ Redis 4.0.10+ -Python packages: `django psycopg2 channels channels_redis jinja2 argon2-cffi bleach` +Python packages: `django psycopg2 channels channels_redis jinja2 argon2-cffi bleach requests python-magic` ## Install ``` diff --git a/jinja2/base.html b/jinja2/base.html index 7660b6f..76b0723 100644 --- a/jinja2/base.html +++ b/jinja2/base.html @@ -29,12 +29,12 @@ {#
#}
diff --git a/quest/events.py b/quest/events.py index 64ec613..9e9f367 100644 --- a/quest/events.py +++ b/quest/events.py @@ -15,6 +15,7 @@ from django.db import IntegrityError from django.utils.timezone import localtime from quest.models import * +from quest.tools import handle_img from quest.forms import DiceCallForm, PollForm def message(socket, data): @@ -51,6 +52,7 @@ def message(socket, data): message = message.replace(quote, msg) # handle image + message = handle_img(message) # dice rolling if any(map(message.startswith, ["/dice", "/roll"])): diff --git a/quest/static/quest.css b/quest/static/quest.css index ef7829f..6183ce9 100644 --- a/quest/static/quest.css +++ b/quest/static/quest.css @@ -184,7 +184,7 @@ h3 { } #messageTextDiv { - padding-bottom: 10px; + padding-bottom: 1em; width: 100%; display: flex; flex-direction: column; @@ -193,6 +193,7 @@ h3 { #messageTextArea { resize: none; box-sizing: border-box; + height: 5em; } #preview { diff --git a/quest/tools.py b/quest/tools.py new file mode 100644 index 0000000..9db3ea1 --- /dev/null +++ b/quest/tools.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +""" +Some miscellaneous tools and helper functions. Primarily for quests. +""" +import os +import re +import hashlib + +import magic +import requests +from django.conf import settings + +IMG_DIR = "/usr/local/www/html/img/" +ALLOWED_MIMES = [ + "image/jpeg", + "image/png", + "image/gif", + "video/webm" +] + +def download_img(url): + """ + Downloads the requested URL, ensures the mimetype is an acceptable + type, and saves it to file with the hash as filename. Returns a + URL to image. + """ + # TODO: file size limits + # https://stackoverflow.com/questions/22346158/ + # TODO: prevent overwriting + try: + res = requests.get(url) + res.raise_for_status() + mime = magic.from_buffer(res.content, mime=True) + assert mime in ALLOWED_MIMES + h = hashlib.sha256() + h.update(res.content) + fname = h.hexdigest() + fname += "." + mime.partition("/")[2] + with open(os.path.join(IMG_DIR, fname), "wb") as file: + for chunk in res.iter_content(100000): + file.write(chunk) + return settings.IMG_SVR_URL + fname + except requests.exceptions.RequestException: + return "INVALID_URL" + except AssertionError: + return "INVALID_MIME_TYPE" + except Exception as e: + print(e) + return "UNKNOWN_ERROR" + + +def handle_img(text, limit=5): + """ + Finds all image urls in the given set of text and attempts to handle + them appropriately. `limit` will limit how many urls are processed. + The rest will be ignored. If an error occurs during handling, the raw + (unlinked) url will be inserted. + """ + # TODO: handle webms + urls = re.findall(r"\[img\](.*?)\[/img\]", text) + + for ext_url in urls: + int_url = download_img(ext_url) + if int_url in ["INVALID_URL", "INVALID_MIME_TYPE", "UNKNOWN_ERROR"]: + text = text.replace("[img]" + ext_url + "[/img]", ext_url, 1) + alt_text = os.path.basename(ext_url) + img_tag = f'' + + text = text.replace("[img]" + ext_url + "[/img]", img_tag, 1) + + return text diff --git a/titivillus/settings.py b/titivillus/settings.py index 62e599f..725835b 100644 --- a/titivillus/settings.py +++ b/titivillus/settings.py @@ -156,3 +156,6 @@ CHANNEL_LAYERS = { }, }, } + +# Image server url +IMG_SVR_URL = "https://img.steelbea.me/"