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/"