This commit is contained in:
gd 2021-08-08 00:33:09 +03:00
commit 7539db0b12
7 changed files with 311 additions and 0 deletions

20
LICENSE Normal file
View File

@ -0,0 +1,20 @@
Copyright (c) 2021 gd <gechandev@gmail.com>
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.

30
README Normal file
View File

@ -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.

6
imgs.ini Normal file
View File

@ -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']

85
imgs.py Normal file
View File

@ -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('/<image_name>')
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'])

84
index.tpl Normal file
View File

@ -0,0 +1,84 @@
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>imgs</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<main>
% if not_found:
<div class="not-found">
<p><h1>404 Not found</h1></p>
<h1>(╯°□°)╯︵ ┻━┻</h1>
</div>
% end
% if bad_mime_type:
<div class="bad-mime-type">
<p><h1>415 Bad file MIME type</h1></p>
<h1>(´•ω•̥`)</h1>
</div>
% end
<div id="drop-area" class="drop-area" ondragover="dragOverHover();" ondragleave="dragLeave()">
<form action="/" method="POST" enctype="multipart/form-data">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"· fill="none" stroke="#000000" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><path d="M3 15v4c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2v-4M17 8l-5-5-5 5M12 4.2v10.3"/></svg><br>
<label for="image_web" class="file-input-label"><b> Choose images</b> or drag and drop it here</label>
<input class="file-input" type="file" multiple onchange="this.form.submit();" name='image_web' id='image_web'>
</form>
</div>
<script>
let dropArea = document.getElementById('drop-area');
function dragOverHover() {
dropArea.className = "drop-area dragover";
}
function dragLeave() {
dropArea.className = "drop-area";
}
</script>
<p><b>or</b> upload images via cURL:</p>
<div class="curl">
<pre>curl -F 'image=@/path/to/image.jpg' {{ base_url }}</pre>
</div>
% if uploaded:
<div class="copy-to-clipboard">
<input type="text" value="{{ image_url }}" id="text-input">
<button onclick="CopyToClipboard()" onmouseout="mouseOut()">
<span id="copy-button">Copy URL</span>
</button>
</div>
<script>
let copyButton = document.getElementById("copy-button"),
copyText = document.getElementById("text-input");
function CopyToClipboard() {
copyText.select();
copyText.setSelectionRange(0, 99999); /*For mobile devices*/
document.execCommand("copy");
copyButton.innerHTML = "Copied!";
}
function mouseOut() {
copyButton.innerHTML = "Copy URL";
}
</script>
<img src="{{ image_url }}" alt="{{ image_url }}" width="640">
<p>- - -</p>
% end
<div class="logo">
<pre> __<br>|__| _____ ____ ______<br>| |/ \ / ___\/ ___/<br>| | Y Y / /_/ \___ \<br>|__|__|_| \___ /____ ><br> \/_____/ \/</pre>
</div>
<p><a href="https://gitea.gch.icu/gd/imgs" target="_blank">v1.0</a></p>
</main>
</body>
</html>

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
bottle==0.12.19

85
style.css Normal file
View File

@ -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;
}