Compare commits
10
Commits
5523afa669
...
06b4cf08b9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06b4cf08b9 | ||
|
|
2fab3fa0d1 | ||
|
|
47933b7fea | ||
|
|
964a586f10 | ||
|
|
bcaa17765a | ||
|
|
c539325177 | ||
|
|
79e4aba172 | ||
|
|
10d603f1c6 | ||
|
|
33d50d1ecb | ||
|
|
d0f23aba5e |
@@ -122,4 +122,4 @@ This project was inspired by how much imgur sucks!
|
|||||||
|
|
||||||
Thanks to all the devs behind Flask, Pillow and Python.
|
Thanks to all the devs behind Flask, Pillow and Python.
|
||||||
|
|
||||||
Thanks Andrew and Jason for the Javascript.
|
Thanks Andrew and Jason for the Javascript, and geeknik for improvements.
|
||||||
|
|||||||
+15
-9
@@ -1,9 +1,15 @@
|
|||||||
click==8.1.3
|
blinker==1.8.2
|
||||||
Flask==2.1.2
|
certifi==2026.1.4
|
||||||
importlib-metadata==4.11.4
|
charset-normalizer==3.4.4
|
||||||
itsdangerous==2.1.2
|
click==8.1.7
|
||||||
Jinja2==3.1.2
|
flask==3.0.3
|
||||||
MarkupSafe==2.1.1
|
idna==3.11
|
||||||
Pillow==9.1.1
|
importlib-metadata==8.0.0
|
||||||
Werkzeug==2.1.2
|
itsdangerous==2.2.0
|
||||||
zipp==3.8.0
|
jinja2==3.1.4
|
||||||
|
MarkupSafe==2.1.5
|
||||||
|
pillow==10.3.0
|
||||||
|
requests==2.32.5
|
||||||
|
urllib3==2.6.3
|
||||||
|
werkzeug==3.0.3
|
||||||
|
zipp==3.19.2
|
||||||
|
|||||||
@@ -3,58 +3,73 @@
|
|||||||
|
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
|
import requests
|
||||||
|
|
||||||
from flask import abort, Flask, request, redirect
|
from flask import abort, Flask, request, redirect
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
PICS = Path('data')
|
app = Flask(__name__)
|
||||||
|
DATA_FOLDER = Path('data')
|
||||||
MAX_SIZE = 1920
|
MAX_SIZE = 1920
|
||||||
PORT = 5003
|
PORT = 5003
|
||||||
URL = 'https://pic.t0.vc'
|
URL = 'https://pic.t0.vc'
|
||||||
POST = 'pic'
|
POST_PARAM = 'pic'
|
||||||
MAX_ALLOWED_SIZE = 16 * 1024 * 1024 # 16 MB
|
MAX_FILE_SIZE = 16 * 1024 * 1024 # 16 MB
|
||||||
|
|
||||||
|
def controller_message(message):
|
||||||
|
print('Sending controller message: ' + message)
|
||||||
|
payload = dict(pic=message)
|
||||||
|
r = requests.post('https://tbot.tannercollin.com/message', data=payload, timeout=5)
|
||||||
|
if r.status_code == 200:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print('Unable to communicate with controller!')
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def help():
|
def help():
|
||||||
form = (
|
form = (
|
||||||
'<form action="{0}" method="POST" accept-charset="UTF-8" enctype="multipart/form-data">'
|
f'<form action="{URL}" method="POST" accept-charset="UTF-8" enctype="multipart/form-data">'
|
||||||
'<input name="web" type="hidden" value="true">'
|
'<input name="web" type="hidden" value="true">'
|
||||||
'<input name="pic" type="file" accept="image/*" />'
|
f'<input name="{POST_PARAM}" type="file" accept="image/*" />'
|
||||||
'<br><br><button type="submit">Submit</button></form>'.format(URL, POST)
|
'<br><br><button type="submit">Submit</button></form>'
|
||||||
)
|
)
|
||||||
return f"""
|
return f"""
|
||||||
|
<meta name="color-scheme" content="light dark" />
|
||||||
|
<meta name="viewport" content="width=1%" />
|
||||||
<pre>
|
<pre>
|
||||||
pic.t0.vc
|
pic.t0.vc
|
||||||
NAME
|
NAME
|
||||||
t0pic: command line image host.
|
t0pic: command line image host.
|
||||||
|
|
||||||
USAGE
|
USAGE
|
||||||
<image output> | curl -F '{0}=@/dev/stdin' {1}
|
<image output> | curl -F '{POST_PARAM}=@/dev/stdin' {URL}
|
||||||
or upload from the web:
|
or upload from the web:
|
||||||
|
|
||||||
{2}
|
{form}
|
||||||
|
|
||||||
or paste the image into this page.
|
or paste the image into this page.
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
I got sick of imgur not working on mobile, so I built this
|
I got sick of imgur not working on mobile, so I built this
|
||||||
Images are resized to a max dimension of {3} px
|
Images are resized to a max dimension of {MAX_SIZE} px
|
||||||
Don't use this for anything serious
|
Don't use this for anything serious
|
||||||
|
|
||||||
EXAMPLES
|
EXAMPLES
|
||||||
~$ cat kitten.jpg | curl -F '{0}=@/dev/stdin' {1}
|
~$ cat kitten.jpg | curl -F '{POST_PARAM}=@/dev/stdin' {URL}
|
||||||
{1}/YXKV.jpg
|
{URL}/YXKV.jpg
|
||||||
~$ firefox {1}/YXKV.jpg
|
~$ firefox {URL}/YXKV.jpg
|
||||||
|
|
||||||
Add this to your .bashrc:
|
Add this to your .bashrc:
|
||||||
|
|
||||||
alias {0}="curl -F '{0}=@/dev/stdin' {1}"
|
alias {POST_PARAM}="curl -F '{POST_PARAM}=@/dev/stdin' {URL}"
|
||||||
|
|
||||||
Now you can pipe directly into {0}!
|
Now you can pipe directly into {POST_PARAM}!
|
||||||
|
|
||||||
SOURCE CODE
|
SOURCE CODE
|
||||||
https://txt.t0.vc/CQQE
|
https://txt.t0.vc/CQQE
|
||||||
nginx config: https://txt.t0.vc/ZKEH
|
nginx config: https://txt.t0.vc/ZKEH
|
||||||
https://github.com/tannercollin/t0pic
|
|
||||||
|
|
||||||
SEE ALSO
|
SEE ALSO
|
||||||
https://txt.t0.vc
|
https://txt.t0.vc
|
||||||
@@ -62,23 +77,23 @@ SEE ALSO
|
|||||||
</pre>"""
|
</pre>"""
|
||||||
|
|
||||||
def paste():
|
def paste():
|
||||||
return """
|
return f"""
|
||||||
window.addEventListener('paste', e => {
|
window.addEventListener('paste', e => {{
|
||||||
const file = e.clipboardData.items[0].getAsFile();
|
const file = e.clipboardData.items[0].getAsFile();
|
||||||
const url = URL.createObjectURL(file);
|
const url = URL.createObjectURL(file);
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
image.src = url;
|
image.src = url;
|
||||||
|
|
||||||
const form = new FormData();
|
const form = new FormData();
|
||||||
form.append('pic', file);
|
form.append('{POST_PARAM}', file);
|
||||||
fetch('/', {
|
fetch('/', {{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: form
|
body: form
|
||||||
})
|
}})
|
||||||
.then(r => r.text())
|
.then(r => r.text())
|
||||||
.then(u => window.location = u)
|
.then(u => window.location = u)
|
||||||
.catch(e => alert(e.message));
|
.catch(e => alert(e.message));
|
||||||
});
|
}});
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def new_id():
|
def new_id():
|
||||||
@@ -92,24 +107,25 @@ def index():
|
|||||||
def new():
|
def new():
|
||||||
try:
|
try:
|
||||||
if 'pic' not in request.files or request.files['pic'].filename == '':
|
if 'pic' not in request.files or request.files['pic'].filename == '':
|
||||||
print("No file part")
|
print('No file part')
|
||||||
abort(400) # No file was uploaded
|
abort(400)
|
||||||
|
|
||||||
pic = request.files['pic']
|
pic = request.files['pic']
|
||||||
if pic.mimetype not in ['image/png', 'image/jpeg', 'image/gif']:
|
|
||||||
print(f"Unsupported image format: {pic.mimetype}")
|
|
||||||
abort(400) # Consider using a more specific status code or returning an error message
|
|
||||||
|
|
||||||
if pic.seek(0, 2) > MAX_ALLOWED_SIZE: # Move to end to get file size
|
if pic.mimetype not in ['image/png', 'image/jpeg', 'image/gif', 'application/octet-stream']:
|
||||||
print(f"File size exceeds maximum allowed limit: {pic.tell()} bytes")
|
print(f'Unsupported image format: {pic.mimetype}')
|
||||||
abort(413) # Payload Too Large
|
abort(400)
|
||||||
|
|
||||||
|
if pic.seek(0, 2) > MAX_FILE_SIZE: # Move to end to get file size
|
||||||
|
print(f'File size exceeds maximum allowed limit: {pic.tell()} bytes')
|
||||||
|
abort(413)
|
||||||
pic.seek(0) # Reset file pointer
|
pic.seek(0) # Reset file pointer
|
||||||
|
|
||||||
nid = new_id()
|
nid = new_id()
|
||||||
while nid in [p.stem for p in PICS.iterdir()]:
|
while nid in [p.stem for p in DATA_FOLDER.iterdir()]:
|
||||||
nid = new_id()
|
nid = new_id()
|
||||||
|
|
||||||
pic = Image.open(pic.stream) # Use .stream to read directly from Flask file storage
|
pic = Image.open(pic.stream)
|
||||||
|
|
||||||
if pic.format == 'PNG':
|
if pic.format == 'PNG':
|
||||||
ext = '.png'
|
ext = '.png'
|
||||||
@@ -118,25 +134,30 @@ def new():
|
|||||||
elif pic.format == 'GIF':
|
elif pic.format == 'GIF':
|
||||||
ext = '.gif'
|
ext = '.gif'
|
||||||
else:
|
else:
|
||||||
print(f"Unsupported image format: {pic.format}")
|
print(f'Unsupported image format: {pic.format}')
|
||||||
abort(400)
|
abort(400)
|
||||||
|
|
||||||
filename = nid + ext
|
filename = nid + ext
|
||||||
pic.thumbnail([MAX_SIZE, MAX_SIZE], Image.Resampling.LANCZOS)
|
pic.thumbnail([MAX_SIZE, MAX_SIZE], Image.Resampling.LANCZOS)
|
||||||
pic.save(PICS / filename) # Use pathlib's / operator for path concatenation
|
pic.save(DATA_FOLDER / filename)
|
||||||
|
|
||||||
print(f'Adding pic {nid}{ext}')
|
print(f'Adding pic {nid}{ext}')
|
||||||
|
|
||||||
url = f'{URL}/{nid}{ext}'
|
url = f'{URL}/{nid}{ext}'
|
||||||
|
|
||||||
|
if pic.size != (300, 150):
|
||||||
|
msg = 'New t0pic:\n\n' + url
|
||||||
|
controller_message(msg)
|
||||||
|
|
||||||
if 'web' in request.form:
|
if 'web' in request.form:
|
||||||
return redirect(url)
|
return redirect(url)
|
||||||
else:
|
else:
|
||||||
return f'{url}\n'
|
return f'{url}\n'
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error processing image upload: {e}")
|
print(f'Error processing image upload: {e}')
|
||||||
abort(400)
|
abort(400)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if not PICS.exists():
|
if not DATA_FOLDER.exists():
|
||||||
PICS.mkdir(parents=True)
|
DATA_FOLDER.mkdir(parents=True)
|
||||||
app.run(port=PORT, debug=False) # Turn off debug for production
|
app.run(port=PORT, debug=False)
|
||||||
|
|||||||
Reference in New Issue
Block a user