Added the files of the project,
Added README.md
60
README.md
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# Little Prynter
|
||||||
|
---
|
||||||
|
|
||||||
|
> Print out shit from the cloud.
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
This project started when I got a Thermal Printer from a friend. I don't really know if you can do anything more, but I guess it's fun.
|
||||||
|
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
To make this project work, you will need :
|
||||||
|
- [A Thermal Printer]( https://www.adafruit.com/product/597 )
|
||||||
|
- A Raspberry Pi
|
||||||
|
- Some electric wires.
|
||||||
|
|
||||||
|
Start by following the guide [here](https://learn.adafruit.com/networked-thermal-printer-using-cups-and-raspberry-pi) to install the CUPS software needed to print images. You can also get some information from [here](https://learn.adafruit.com/mini-thermal-receipt-printer) and [here](https://learn.adafruit.com/instant-camera-using-raspberry-pi-and-thermal-printer) if you're stuck.
|
||||||
|
|
||||||
|
Then, setup the project :
|
||||||
|
```
|
||||||
|
git clone https://github.com/N07070/LittlePrynter
|
||||||
|
cd LittlePrynter
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
Now, edit `the users.json` and add a user. **Don't forget to remove the test user.**
|
||||||
|
|
||||||
|
You can now start the web server with
|
||||||
|
|
||||||
|
```
|
||||||
|
export FLASK_APP=littleprynter.py
|
||||||
|
flask run
|
||||||
|
```
|
||||||
|
|
||||||
|
Voilà !
|
||||||
|
|
||||||
|
## More
|
||||||
|
If you liked this project, feel free to support my work !
|
||||||
|
|
||||||
|
BTC : `1GYEiFSS7vbZXxDjKQavdaGsAEtvGjNWqb`
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
```
|
||||||
|
LittlePrynter
|
||||||
|
Copyright (C) 2018 N07070
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
```
|
114
littleprynter.py
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
import os, random, sys, json
|
||||||
|
from datetime import datetime
|
||||||
|
from subprocess import call
|
||||||
|
from Adafruit_Thermal import *
|
||||||
|
from flask import Flask, render_template, jsonify, make_response, request, redirect, url_for, abort, session, flash
|
||||||
|
from flask_limiter import Limiter
|
||||||
|
from pprint import pprint
|
||||||
|
from flask_limiter.util import get_remote_address
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.secret_key = b'\x98>3nW[D\xa4\xd4\xd0K\xab?oM.`\x98'
|
||||||
|
limiter = Limiter(
|
||||||
|
app,
|
||||||
|
key_func=get_remote_address,
|
||||||
|
default_limits=["200 per day", "50 per hour"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# session['logged_in'] = False
|
||||||
|
|
||||||
|
def error_handler_limiter():
|
||||||
|
flash("Trop de requêtes !!! CANNOT PRINT !!! HAAAAAAAAAA",'dark')
|
||||||
|
return redirect(url_for('display_index_page'))
|
||||||
|
|
||||||
|
@app.errorhandler(418)
|
||||||
|
def i_m_a_tea_pot(error):
|
||||||
|
return make_response('☕\n', 418)
|
||||||
|
|
||||||
|
@app.route('/tea')
|
||||||
|
def tea():
|
||||||
|
abort(418)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
@limiter.exempt
|
||||||
|
def display_index_page():
|
||||||
|
if session.get('logged_in'):
|
||||||
|
return render_template('index.html')
|
||||||
|
else:
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
@app.route('/login', methods=['POST','GET'])
|
||||||
|
@limiter.limit("100 per minute", error_message=error_handler_limiter)
|
||||||
|
def login():
|
||||||
|
if request.method == 'POST':
|
||||||
|
if not session.get('logged_in'):
|
||||||
|
if request.form['username'] and request.form['password']:
|
||||||
|
# Get the json
|
||||||
|
with open('users.json') as f:
|
||||||
|
users_file = json.load(f)
|
||||||
|
for user in users_file["users"]:
|
||||||
|
if users_file["users"][user] == request.form['password']:
|
||||||
|
session['logged_in'] = True
|
||||||
|
session['user'] = request.form['username']
|
||||||
|
|
||||||
|
if not session.get('logged_in'):
|
||||||
|
flash('Mot de passe ou pseudo invalide.','danger')
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
else:
|
||||||
|
return redirect(url_for('display_index_page'))
|
||||||
|
else:
|
||||||
|
flash('Incorrect logins')
|
||||||
|
return render_template('password.html')
|
||||||
|
else:
|
||||||
|
return render_template('password.html')
|
||||||
|
else:
|
||||||
|
return render_template('password.html')
|
||||||
|
|
||||||
|
@app.route("/logout")
|
||||||
|
def logout():
|
||||||
|
session['logged_in'] = False
|
||||||
|
flash('Tu est déconnecté', 'info')
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
@app.route('/print/image')
|
||||||
|
@limiter.limit("5 per minute", error_message=error_handler_limiter)
|
||||||
|
def print_image():
|
||||||
|
if session.get('logged_in'):
|
||||||
|
img = random.choice(os.listdir("static/images/")) #change dir name to whatever
|
||||||
|
call(["lp", "-o fit-to-page", "static/images/" + img])
|
||||||
|
# printer = Adafruit_Thermal('/dev/serial0', 19200, timeout=5)
|
||||||
|
# printer.begin()
|
||||||
|
# printer.feed(1)
|
||||||
|
# # printer.printImage(dickbutt, True)
|
||||||
|
# # printer.feed(2)
|
||||||
|
return redirect(url_for('display_index_page'))
|
||||||
|
else:
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
@app.route('/print/text', methods=['POST'])
|
||||||
|
@limiter.limit("3: per minute", error_message=error_handler_limiter)
|
||||||
|
def print_text():
|
||||||
|
if session.get('logged_in'):
|
||||||
|
if len(request.form['message']) < 200:
|
||||||
|
printer = Adafruit_Thermal('/dev/serial0', 19200, timeout=5)
|
||||||
|
printer.begin()
|
||||||
|
printer.justify('L')
|
||||||
|
printer.println(request.form['message'])
|
||||||
|
printer.setSize('S')
|
||||||
|
printer.println(">> " + datetime.now().strftime("%Y-%m-%d %H:%M:%S") + " " + session.get('user'))
|
||||||
|
printer.justify('C')
|
||||||
|
printer.println("------------------------------")
|
||||||
|
printer.feed(2)
|
||||||
|
printer.sleep() # Tell printer to sleep
|
||||||
|
printer.wake() # Call wake() before printing again, even if reset
|
||||||
|
printer.setDefault() # Restore printer to defaults
|
||||||
|
return redirect(url_for('display_index_page'))
|
||||||
|
else:
|
||||||
|
flash('Le text est trop long, 200 caractères au maximum stp !')
|
||||||
|
return redirect(url_for('display_index_page'))
|
||||||
|
else:
|
||||||
|
return redirect(url_for('login'))
|
0
mastodon_writing_prompt.py
Normal file
1
pip-selfcheck.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"last_check":"2018-10-10T18:33:22Z","pypi_version":"18.1"}
|
13
requirements.txt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
Adafruit-Thermal==1.1.0
|
||||||
|
Click==7.0
|
||||||
|
Flask==1.0.2
|
||||||
|
Flask-Limiter==1.0.1
|
||||||
|
itsdangerous==0.24
|
||||||
|
Jinja2==2.10
|
||||||
|
limits==1.3
|
||||||
|
MarkupSafe==1.0
|
||||||
|
Pillow==5.3.0
|
||||||
|
pyserial==3.4
|
||||||
|
six==1.11.0
|
||||||
|
Unidecode==1.0.22
|
||||||
|
Werkzeug==0.14.1
|
27
static/css/style.css
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
body {
|
||||||
|
margin-left: 20%;
|
||||||
|
margin-right: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-top: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*// Small devices (landscape phones, 576px and up)*/
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*// Medium devices (tablets, 768px and up)*/
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
margin-left: 0%;
|
||||||
|
margin-right: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*// Large devices (desktops, 992px and up)*/
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Extra large devices (large desktops, 1200px and up)*/
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
}
|
BIN
static/images/1.jpg
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
static/images/10.jpg
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
static/images/11.jpg
Normal file
After Width: | Height: | Size: 280 KiB |
BIN
static/images/12.jpg
Normal file
After Width: | Height: | Size: 82 KiB |
BIN
static/images/2.jpg
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
static/images/3.png
Normal file
After Width: | Height: | Size: 236 KiB |
BIN
static/images/4.jpg
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
static/images/5.jpg
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
static/images/6.jpg
Normal file
After Width: | Height: | Size: 149 KiB |
BIN
static/images/7.gif
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
static/images/8.jpg
Normal file
After Width: | Height: | Size: 207 KiB |
BIN
static/images/9.jpg
Normal file
After Width: | Height: | Size: 83 KiB |
BIN
static/load.gif
Normal file
After Width: | Height: | Size: 8.7 KiB |
75
templates/index.html
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>LittlePrynter</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static',filename='css/style.css') }}">
|
||||||
|
</head>
|
||||||
|
<body class="container">
|
||||||
|
<div class="col-md-6 offset-md-3">
|
||||||
|
<h1 class="font-weight-bold">Little Prynter</h1>
|
||||||
|
<hr>
|
||||||
|
<br>
|
||||||
|
{% with messages = get_flashed_messages() %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-danger" role="alert">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
<br>
|
||||||
|
<h3>Imprimer une image aléatoire</h3>
|
||||||
|
<a class="btn btn-primary btn-lg btn-block" href="/print/image"> Imprimer</a>
|
||||||
|
<hr>
|
||||||
|
<br>
|
||||||
|
<h3>Imprimer ton propre texte</h3>
|
||||||
|
<form class="form-group" action="/print/text" method="post">
|
||||||
|
<input class="form-control" type="text" name="message" placeholder="200 caractères ou moins " maxlength="200"><br>
|
||||||
|
<input class="btn btn-primary float-right" type="submit" value="Imprimer" name="imprimer">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div id="pbar" style="display:none">
|
||||||
|
<img src="{{ url_for('static',filename='load.gif') }}" alt="loading wheel">
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<footer><a href="/logout">Se déconnecter</a></footer>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
function printImage() {
|
||||||
|
// Make an ajax call to the flask server on the /print/image route to trigger an image to be printer.
|
||||||
|
var xhttp = new XMLHttpRequest();
|
||||||
|
xhttp.onreadystatechange = function() {
|
||||||
|
var x = document.getElementById("pbar");
|
||||||
|
if (x.style.display === "none") {
|
||||||
|
x.style.display = "inline";
|
||||||
|
}
|
||||||
|
if (this.readyState == 4 && this.status == 429) {
|
||||||
|
x.style.display = "none";
|
||||||
|
alert("Hé, attends un peu avant d'imprimer une autre image, je me repose...");
|
||||||
|
} else if (this.readyState == 4 && this.status == 200) {
|
||||||
|
x.style.display = "none";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhttp.open("GET", "/print/image", true);
|
||||||
|
xhttp.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function printText(){
|
||||||
|
var xhttp = new XMLHttpRequest();
|
||||||
|
xhttp.onreadystatechange = function() {
|
||||||
|
if (this.readyState == 4 && this.status == 400) {
|
||||||
|
alert("Le text est trop long !");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhttp.open("GET", "/print/text", true);
|
||||||
|
xhttp.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</html>
|
30
templates/password.html
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Log in</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static',filename='css/style.css') }}">
|
||||||
|
</head>
|
||||||
|
<body class="container">
|
||||||
|
<div class="col-md-6 offset-md-3">
|
||||||
|
<h1>Login</h1>
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<div class="alert alert-{{ category }}" role="alert">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
<form action="/login" method="post">
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" name="username" class="form-control" placeholder="Pseudo">
|
||||||
|
<input type="password" name="password" class="form-control" placeholder="Password">
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
6
users.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"users": {
|
||||||
|
"test" : "test",
|
||||||
|
"Karl" : "Marx"
|
||||||
|
}
|
||||||
|
}
|