init
This commit is contained in:
commit
7539db0b12
20
LICENSE
Normal file
20
LICENSE
Normal 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
30
README
Normal 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
6
imgs.ini
Normal 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
85
imgs.py
Normal 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
84
index.tpl
Normal 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
1
requirements.txt
Normal file
@ -0,0 +1 @@
|
||||
bottle==0.12.19
|
85
style.css
Normal file
85
style.css
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user