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"
|
||||
}
|
||||
}
|