Compare commits

...

4 Commits

Author SHA1 Message Date
5fdbcb7468 Better error handling 2026-03-07 14:39:19 +00:00
6e47e10a44 add gitignore 2026-03-07 14:39:09 +00:00
573d8574a2 add more subcommands 2026-03-07 14:38:53 +00:00
8e69681fc3 Update to include subcommands 2026-03-07 14:38:38 +00:00
4 changed files with 137 additions and 34 deletions

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
img/
img_fullwidth/
junk/
__pycache__/
*.pyc
*.pyo
.env

40
README.md Normal file
View File

@@ -0,0 +1,40 @@
# Receipt Maker
Prints random images from 4chan's /bant/ board to an Epson TM-T20II thermal receipt printer over Ethernet.
## How it works
1. `connect_recipt.sh` — configures the network interface and connects to the printer at `192.168.192.168:9100`
2. `receipt.py` — CLI with subcommands to print random /bant/ images, text notes, or local images (resized to full printer width, 576px)
## Requirements
- Python 3
- `python-escpos`
- `Pillow`
- `requests`
- `nmap` (for connection script)
- Epson TM-T20II connected via USB-to-Ethernet adapter
## Usage
```bash
# Print a random image from /bant/
python receipt.py image
# Print a text note
python receipt.py note "your message here"
# Print a local image (with optional caption)
python receipt.py print path/to/image.jpg
python receipt.py print path/to/image.jpg "optional caption"
```
The script will run `connect_recipt.sh` automatically to bring up the network interface before printing.
## Files
- `receipt.py` — main script
- `connect_recipt.sh` — network setup for USB Ethernet adapter
- `img/` — downloaded images cache
- `img_fullwidth/` — resized images ready for printing

View File

@@ -1,4 +1,5 @@
#!/bin/bash
set -e
# Try to find the USB Ethernet interface first
iface=$(ip -o link | awk -F': ' '/enx|enp.*u/{print $2}' | head -n 1)
@@ -16,14 +17,17 @@ else
echo "Using USB Ethernet interface: $iface"
fi
# Assign IP address (only for USB interface, skip eth0)
# if [[ $iface != "eth0" ]]; then
sudo ip addr add 192.168.192.10/24 dev "$iface" 2>/dev/null
# fi
# Bring interface up
# Bring interface up before assigning IP
sudo ip link set "$iface" up
# Scan port 9100 on the printer IP
nmap -p 9100 192.168.192.168
# Assign IP address if not already set
if ! ip addr show dev "$iface" | grep -q "192.168.192.10"; then
sudo ip addr add 192.168.192.10/24 dev "$iface"
fi
# Scan port 9100 on the printer IP and verify it is open
if ! nmap -p 9100 --open 192.168.192.168 | grep -q "9100/tcp open"; then
echo "Printer port 9100 is not open."
exit 1
fi

View File

@@ -1,28 +1,29 @@
#!/usr/bin/env python3
from escpos import *
from escpos import printer
from PIL import Image
import os
import subprocess
import time
import random
import requests
from urllib.parse import quote
# Return T20II printer
def get_T20II_usb():
p = printer.Usb(0x04b8, 0x0202) #, profile="TM-T20II")
p = printer.Usb(0x04B8, 0x0202) # , profile="TM-T20II")
# p.set_with_default(align='center', font='a', bold=False, width=2, height=2, custom_size=True, smooth=True)
return p
def get_T20II_ethernet():
p = printer.Network("192.168.192.168", port=9100)#, profile="TM-T20II")
p = printer.Network("192.168.192.168", port=9100) # , profile="TM-T20II")
# p.set_with_default(align='center', font='a', bold=False, width=2, height=2, custom_size=True, smooth=True)
return p
def resize_image_to_fullwidth(img_src_name, target_width=576):
# Make fullwidth dir
os.makedirs('img_fullwidth', exist_ok=True)
os.makedirs("img_fullwidth", exist_ok=True)
# Open the image
img = Image.open(img_src_name)
@@ -38,7 +39,7 @@ def resize_image_to_fullwidth(img_src_name, target_width=576):
resized_img = img.resize((target_width, new_height), Image.LANCZOS)
# Save the resized image
name, ext = os.path.splitext(img_src_name)
_, ext = os.path.splitext(img_src_name)
filename_no_ext = os.path.splitext(os.path.basename(img_src_name))[0]
# Build destination filename
@@ -49,7 +50,7 @@ def resize_image_to_fullwidth(img_src_name, target_width=576):
def get_random_bant_image():
# Create img directory if it doesn't exist
os.makedirs('img', exist_ok=True)
os.makedirs("img", exist_ok=True)
# Fetch the catalog JSON for /bant/
url = "https://a.4cdn.org/bant/catalog.json"
@@ -58,12 +59,15 @@ def get_random_bant_image():
catalog = response.json()
# Flatten the list of threads from all pages
threads = [thread for page in catalog for thread in page['threads']]
threads = [thread for page in catalog for thread in page["threads"]]
# Filter threads that have images (tim and ext fields)
image_threads = [
thread for thread in threads
if 'tim' in thread and 'ext' in thread and thread['ext'].lower() not in ['.gif', '.mp4', '.webm']
thread
for thread in threads
if "tim" in thread
and "ext" in thread
and thread["ext"].lower() not in [".gif", ".mp4", ".webm"]
]
if not image_threads:
@@ -80,31 +84,79 @@ def get_random_bant_image():
img_response = requests.get(image_url)
img_response.raise_for_status()
with open(filename, 'wb') as f:
with open(filename, "wb") as f:
f.write(img_response.content)
return filename
def print_random_bant_image(p, sh_file):
img = get_random_bant_image()
if img is None:
print("No images found on /bant/.")
return
filename_no_ext = os.path.splitext(os.path.basename(img))[0]
p.textln(filename_no_ext)
fimg = resize_image_to_fullwidth(img)
try:
p.image(fimg, impl='bitImageColumn');
except:
p.image(fimg, impl="bitImageColumn")
except Exception as e:
print(f"Print failed, reconnecting: {e}")
subprocess.run(["bash", sh_file], check=True)
p = get_T20II_ethernet()
p.cut()
def print_note(p, text):
p.textln(text)
p.cut()
def print_local_image(p, sh_file, img_path, message=None):
if not os.path.isfile(img_path):
print(f"File not found: {img_path}")
return
if message:
p.textln(message)
fimg = resize_image_to_fullwidth(img_path)
try:
p.image(fimg, impl="bitImageColumn")
except Exception as e:
print(f"Print failed, reconnecting: {e}")
subprocess.run(["bash", sh_file], check=True)
p = get_T20II_ethernet()
p.cut()
def main():
import argparse
parser = argparse.ArgumentParser(description="Receipt printer CLI")
subparsers = parser.add_subparsers(dest="command", required=True)
subparsers.add_parser("image", help="Print a random /bant/ image")
note_p = subparsers.add_parser("note", help="Print a text note")
note_p.add_argument("text", help="The note to print")
print_p = subparsers.add_parser("print", help="Print a local image")
print_p.add_argument("path", help="Path to the image file")
print_p.add_argument("message", nargs="?", default=None, help="Optional caption")
args = parser.parse_args()
script_dir = os.path.dirname(os.path.abspath(__file__))
sh_file = os.path.join(script_dir, "connect_recipt.sh")
subprocess.run(["bash", sh_file], check=True)
p = get_T20II_ethernet()
if args.command == "image":
print_random_bant_image(p, sh_file)
return
elif args.command == "note":
print_note(p, args.text)
elif args.command == "print":
print_local_image(p, sh_file, args.path, args.message)
if __name__ == "__main__":
main()