commit 7539db0b1222483a56080556b34bab6fc124c2f3 Author: gd Date: Sun Aug 8 00:33:09 2021 +0300 init diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e9f2148 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2021 gd + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README b/README new file mode 100644 index 0000000..7aab9e1 --- /dev/null +++ b/README @@ -0,0 +1,30 @@ + __ +|__| _____ ____ ______ +| |/ \ / ___\/ ___/ +| | Y Y / /_/ \___ \ +|__|__|_| \___ /____ > + \/_____/ \/ + +imgs is a minimalictic image sharing web app written with Bottle framework. + +No database. No image compression. No time limits. No additional dependencies. + +Features: +* Upload images via Drag&Drop +* Easy copy share link +* MIME type detecting + +Installation +============ + +See deployment options in Bottle documentation: https://bottlepy.org/docs/dev/deployment.html + +For local installation run: + +$ python3 -m venv env +$ source env/bin/activate +$ pip install bottle +$ git clone https://github.com/gechandesu/imgs.git +$ python3 imgs/imgs.py + +Edit the imgs.ini. diff --git a/imgs.ini b/imgs.ini new file mode 100644 index 0000000..42d70d9 --- /dev/null +++ b/imgs.ini @@ -0,0 +1,6 @@ +[imgs] +debug = False +base_url = http://localhost:5000/ +image_name_lenght = 3 +uploads_dir = uploads +allowed_mime_types = ['image/bmp', 'image/gif', 'image/jpeg', 'image/png', 'image/svg+xml', 'image/tiff', 'image/webp'] diff --git a/imgs.py b/imgs.py new file mode 100644 index 0000000..406f4a8 --- /dev/null +++ b/imgs.py @@ -0,0 +1,85 @@ +import os +import random +import string + +from bottle import default_app as app +from bottle import run, get, post, request, response, error, template, static_file + + +config = app().config.load_config('./imgs.ini') + +def generate_image_name(image: str) -> str: + name = '' + chars = string.ascii_letters + string.digits + '-_' + while len(name) <= int(config['imgs.image_name_lenght']) - 1: + name = name + random.choice(chars) + return name + os.path.splitext(image)[1].lower() + +def get_base_url(): + try: + base_url = config['imgs.base_url'] + except KeyError: + base_url = request.url + return base_url + +def get_image_url(image_name: str) -> str: + image_url = get_base_url() + '/' + image_name + return image_url.replace('//', '/').replace(':/', '://') + +def upload_file(file): + image_name = generate_image_name(file.filename) + file.save(os.path.join(config['imgs.uploads_dir'], image_name)) + return image_name + +@error(404) +def error404(error): + return template('index.tpl', + uploaded = False, not_found = True, bad_mime_type = False, + base_url = get_base_url()) + +@get('/') +def index(): + return template('index.tpl', + uploaded = False, not_found = False, bad_mime_type = False, + base_url = get_base_url()) + +@post('/') +def upload_image(): + # Handle request from CLI + if request.files.get('image'): + file = request.files.get('image') + if file.content_type in config['imgs.allowed_mime_types']: + image_name = upload_file(file) + return get_image_url(image_name) + '\n' + else: + # Prevent recource leek. Force close buffered file + request.body.close() + response.status = 415 + return 'Error: bad file MIME type\n' + # Handle request from web-browser + elif request.files.get('image_web'): + file = request.files.get('image_web') + if file.content_type in config['imgs.allowed_mime_types']: + image_name = upload_file(file) + return template('index.tpl', + uploaded = True, not_found = False, bad_mime_type = False, + base_url = get_base_url(), image_url = get_image_url(image_name)) + else: + # Prevent recource leek. Force close buffered file + request.body.close() + response.status = 415 + return template('index.tpl', + uploaded = False, not_found = False, bad_mime_type = True, + allowed_mime_types = config['imgs.allowed_mime_types'], + base_url = get_base_url(), image_url = 'None') + +@get('/') +def send_image(image_name): + return static_file(image_name, root = config['imgs.uploads_dir']) + +@get('/style.css') +def send_style(): + return static_file('style.css', root = './') + +if __name__ == '__main__': + run(debug = config['imgs.debug']) diff --git a/index.tpl b/index.tpl new file mode 100644 index 0000000..bfa1805 --- /dev/null +++ b/index.tpl @@ -0,0 +1,84 @@ + + + + + imgs + + + +
+ + % if not_found: +
+

404 Not found

+

(╯°□°)╯︵ ┻━┻

+
+ % end + + % if bad_mime_type: +
+

415 Bad file MIME type

+

(´•ω•̥`)

+
+ % end + +
+
+
+ + +
+
+ + + +

or upload images via cURL:

+ +
+
curl -F 'image=@/path/to/image.jpg' {{ base_url }}
+
+ + % if uploaded: +
+ + +
+ + + + {{ image_url }} + +

- - -

+ % end + + +

v1.0

+ +
+ + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5305531 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +bottle==0.12.19 diff --git a/style.css b/style.css new file mode 100644 index 0000000..2153372 --- /dev/null +++ b/style.css @@ -0,0 +1,85 @@ +body { + background-color: #fff; + font-family: 'Ubuntu Mono', monospace; + max-width: 720px; + margin: 0 auto; + text-align: center; +} + +main { margin: 4rem 2rem; } +.not-found, .bad-mime-type { margin-bottom: 2rem; } + +a, a:visited { color: #000; } + +img { width: 100%; } + +/* Drag and Drop */ +.drop-area { + position: relative; + display: flex; + align-items: center; + justify-content: center; + height: 8rem; + padding: 25px; + border: 3px dashed #e1e1e1; +} + +.drop-area.dragover { border-color: #000; } + +.file-input { + position: absolute; + left: 0; + top: 0; + height: 100%; + width: 100%; + cursor: pointer; + align-items: center; + opacity: 0; +} + +.file-input-label { + display: block; + margin-top: 1rem; +} + +/* Copy to clipboard */ +.copy-to-clipboard { + display: flex; + margin: 2rem 0; + border: 1px solid #000000; +} + +.copy-to-clipboard input[type=text] { + flex: 50%; + width: 100%; + padding: 12px 20px; + border: none; + outline: none; +} + +.copy-to-clipboard button { + padding: 12px 20px; + margin: 0; + min-width: 120px; + cursor: pointer; + text-align: center; + border: none; + background-color: #000000; + color: #ffffff; +} + +/* cURL command */ +.curl pre { + text-align: left; + padding: 12px 20px; + font-size: 14px; + background: #000000; + color: #ffffff; + overflow-x: auto; +} + +.logo pre { + display: flex; + justify-content: center; + text-align: left; +}