init
This commit is contained in:
		
							
								
								
									
										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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user