Compare commits

...

5 Commits

3 changed files with 299 additions and 18 deletions

92
forum_label.py Normal file
View File

@@ -0,0 +1,92 @@
from PIL import Image, ImageEnhance, ImageFont, ImageDraw
import os
import textwrap
import qrcode
import urllib.parse
location = os.path.dirname(os.path.realpath(__file__))
def print_forum_label(thread):
im = Image.open(location + '/label.png')
width, height = im.size
draw = ImageDraw.Draw(im)
#logging.info('Printing forum thread: %s', thread['title'])
url = 'https://forum.protospace.ca/t/{}/'.format(thread['id'])
qr = qrcode.make(url, version=6, box_size=9)
im.paste(qr, (840, 150))
item_size = 150
w = 9999
while w > 1200:
item_size -= 5
font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', item_size)
w, h = draw.textsize(url, font=font)
x, y = (width - w) / 2, ((height - h) / 2) + 300
draw.text((x, y), url, font=font, fill='black')
font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 80)
draw.text((120, 150), 'Forum Thread', font=font, fill='black')
text = thread['title']
MARGIN = 50
MAX_W, MAX_H, PAD = 900 - (MARGIN*2), 450 - (MARGIN*2), 5
def fit_text(text, font_size):
font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', font_size)
for cols in range(100, 1, -4):
print('trying size', font_size, 'cols', cols)
paragraph = textwrap.wrap(text, width=cols, break_long_words=False)
total_h = -PAD
total_w = 0
for line in paragraph:
w, h = draw.textsize(line, font=font)
if w > total_w:
total_w = w
total_h += h + PAD
if total_w <= MAX_W and total_h < MAX_H:
return True, paragraph, total_h
return False, [], 0
font_size_range = [1, 500]
# Thanks to Alex (UDIA) for the binary search algorithm
while abs(font_size_range[0] - font_size_range[1]) > 1:
font_size = sum(font_size_range) // 2
image_fit, check_para, check_h = fit_text(text, font_size)
if image_fit:
print('Does fit')
font_size_range = [font_size, font_size_range[1]]
good_size = font_size
paragraph = check_para
total_h = check_h
else:
print('Does not fit')
font_size_range = [font_size_range[0], font_size]
font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', good_size)
offset = height - MAX_H - MARGIN
start_h = -100 + offset
current_h = start_h
for line in paragraph:
w, h = draw.textsize(line, font=font)
x, y = (MAX_W - w) / 2, current_h
draw.text((x+MARGIN, y), line, font=font, fill='black')
current_h += h + PAD
im.save('tmp.png')
print_forum_label(dict(id=10197, title='Pitch: A wild split-flap display appeared!'))

210
main.py
View File

@@ -26,6 +26,7 @@ import textwrap
import random
import qrcode
import urllib.parse
import unicodedata
from PIL import Image, ImageEnhance, ImageFont, ImageDraw
from datetime import datetime, timezone, timedelta
import paho.mqtt.publish as publish
@@ -86,6 +87,12 @@ def format_date(datestr):
d = d.astimezone(TIMEZONE_CALGARY)
return d.strftime('%a %b %-d, %Y %-I:%M %p')
def normalize_to_ascii(s):
return unicodedata.normalize('NFKD', s).encode('ascii', 'ignore').decode('ascii')
def truncate_string(s, max_length):
return s[:max_length-3] + '...' if len(s) > max_length else s
def sign_send(to_send):
try:
logging.info('Sending to sign: %s', to_send)
@@ -458,6 +465,103 @@ def print_consumable_label(item):
os.system('lp -d dymo tmp.png > /dev/null 2>&1')
def print_forum_label(thread):
im = Image.open(location + '/label.png')
width, height = im.size
draw = ImageDraw.Draw(im)
logging.info('Printing forum thread ID: %s, title: %s', thread['id'], thread['title'])
url = 'https://forum.protospace.ca/t/{}/'.format(thread['id'])
qr = qrcode.make(url, version=6, box_size=9)
im.paste(qr, (840, 150))
item_size = 150
w = 9999
while w > 1200:
item_size -= 5
font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', item_size)
w, h = draw.textsize(url, font=font)
x, y = (width - w) / 2, ((height - h) / 2) + 300
draw.text((x, y), url, font=font, fill='black')
font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 80)
draw.text((120, 150), 'Forum Thread', font=font, fill='black')
text = thread['title']
MARGIN = 50
MAX_W, MAX_H, PAD = 900 - (MARGIN*2), 450 - (MARGIN*2), 5
def fit_text(text, font_size):
font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', font_size)
for cols in range(100, 1, -4):
paragraph = textwrap.wrap(text, width=cols, break_long_words=False)
total_h = -PAD
total_w = 0
for line in paragraph:
w, h = draw.textsize(line, font=font)
if w > total_w:
total_w = w
total_h += h + PAD
if total_w <= MAX_W and total_h < MAX_H:
return True, paragraph, total_h
return False, [], 0
font_size_range = [1, 500]
# Thanks to Alex (UDIA) for the binary search algorithm
while abs(font_size_range[0] - font_size_range[1]) > 1:
font_size = sum(font_size_range) // 2
image_fit, check_para, check_h = fit_text(text, font_size)
if image_fit:
font_size_range = [font_size, font_size_range[1]]
good_size = font_size
paragraph = check_para
total_h = check_h
else:
font_size_range = [font_size_range[0], font_size]
font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', good_size)
offset = height - MAX_H - MARGIN
start_h = -100 + offset
current_h = start_h
for line in paragraph:
w, h = draw.textsize(line, font=font)
x, y = (MAX_W - w) / 2, current_h
draw.text((x+MARGIN, y), line, font=font, fill='black')
current_h += h + PAD
im.save('tmp.png')
os.system('lp -d dymo tmp.png > /dev/null 2>&1')
def search_forum_thread(search):
params = dict(q=search + ' in:title')
headers = {'Api-Key': secrets.FORUM_SEARCH_API_KEY, 'Api-Username': 'system'}
results = []
r = requests.get('https://forum.protospace.ca/search.json', params=params, headers=headers, timeout=5)
r.raise_for_status()
r = r.json()
for topic in r.get('topics', []):
results.append(dict(
id=topic['id'],
title=normalize_to_ascii(topic['title']),
))
return sorted(results, key=lambda x: x['id'], reverse=True)[:7]
def message_protovac(thread):
try:
logging.info('Message to Protovac: %s', thread[-1]['content'])
@@ -599,6 +703,8 @@ label_material_name = ''
label_material_contact = ''
label_generic = ''
label_consumable = ''
label_forum_search = ''
search_results = None
logging.info('Starting main loop...')
@@ -607,7 +713,7 @@ last_key = time.time()
def ratelimit_key():
global last_key
if think_to_send or sign_to_send or message_to_send or nametag_member or nametag_guest or label_tool or label_material_name or label_material_contact or label_generic or label_consumable or time.time() > last_key + 1:
if think_to_send or sign_to_send or message_to_send or nametag_member or nametag_guest or label_tool or label_material_name or label_material_contact or label_generic or label_consumable or label_forum_search or time.time() > last_key + 1:
last_key = time.time()
return False
else:
@@ -925,7 +1031,11 @@ while True:
stdscr.addstr(0, 1, 'PROTOVAC UNIVERSAL COMPUTER')
stdscr.addstr(2, 1, 'Print a Label')
stdscr.addstr(3, 1, '===============')
stdscr.addstr(5, 1, 'Choose the type of label.')
if search_results is None:
stdscr.addstr(5, 1, 'Choose the type of label.')
else:
stdscr.addstr(5, 1, 'Choose the thread to print:')
stdscr.clrtoeol()
if label_tool:
stdscr.addstr(8, 4, 'Enter Wiki-ID tool number: ' + label_tool)
@@ -934,6 +1044,8 @@ while True:
stdscr.clrtoeol()
stdscr.addstr(12, 4, '')
stdscr.clrtoeol()
stdscr.addstr(14, 4, '')
stdscr.clrtoeol()
stdscr.addstr(23, 1, '[RETURN] Print [ESC] Cancel')
elif label_material_contact:
stdscr.addstr(8, 4, '')
@@ -942,6 +1054,8 @@ while True:
stdscr.clrtoeol()
stdscr.addstr(12, 4, '')
stdscr.clrtoeol()
stdscr.addstr(14, 4, '')
stdscr.clrtoeol()
stdscr.addstr(23, 1, '[RETURN] Print [ESC] Cancel')
elif label_material_name:
stdscr.addstr(8, 4, '')
@@ -950,6 +1064,8 @@ while True:
stdscr.clrtoeol()
stdscr.addstr(12, 4, '')
stdscr.clrtoeol()
stdscr.addstr(14, 4, '')
stdscr.clrtoeol()
stdscr.addstr(23, 1, '[RETURN] Next [ESC] Cancel')
elif label_generic:
stdscr.addstr(8, 4, '')
@@ -958,6 +1074,8 @@ while True:
stdscr.clrtoeol()
stdscr.addstr(12, 4, 'Enter your message: ' + label_generic)
stdscr.clrtoeol()
stdscr.addstr(14, 4, '')
stdscr.clrtoeol()
stdscr.addstr(23, 1, '[RETURN] Print [ESC] Cancel')
elif label_consumable:
stdscr.addstr(8, 4, '')
@@ -966,11 +1084,36 @@ while True:
stdscr.clrtoeol()
stdscr.addstr(12, 4, 'Enter the item: ' + label_consumable)
stdscr.clrtoeol()
stdscr.addstr(14, 4, '')
stdscr.clrtoeol()
stdscr.addstr(23, 1, '[RETURN] Print [ESC] Cancel')
elif label_forum_search:
stdscr.addstr(8, 4, '')
stdscr.clrtoeol()
stdscr.addstr(10, 4, '')
stdscr.clrtoeol()
stdscr.addstr(12, 4, '')
stdscr.clrtoeol()
stdscr.addstr(14, 4, 'Search for a thread: ' + label_forum_search)
stdscr.clrtoeol()
stdscr.addstr(23, 1, '[RETURN] Search [ESC] Cancel')
elif search_results is not None:
if len(search_results):
for i, result in enumerate(search_results):
result_title = truncate_string(result['title'], 74)
stdscr.addstr(7 + i*2, 1, '[{}]'.format(i+1), curses.A_REVERSE if highlight_keys else 0)
stdscr.addstr(7 + i*2, 5, ' {}'.format(result_title))
stdscr.clrtoeol()
stdscr.addstr(23, 1, '[ESC] Cancel')
else:
stdscr.addstr(8, 4, 'No results, try again.')
stdscr.addstr(23, 1, '[ESC] Cancel', curses.A_REVERSE if highlight_keys else 0)
else:
stdscr.addstr(8, 4, '[T] Tool label', curses.A_REVERSE if highlight_keys else 0)
stdscr.addstr(10, 4, '[S] Sheet material', curses.A_REVERSE if highlight_keys else 0)
stdscr.addstr(12, 4, '[G] Generic label', curses.A_REVERSE if highlight_keys else 0)
stdscr.addstr(14, 4, '[F] Forum thread', curses.A_REVERSE if highlight_keys else 0)
stdscr.addstr(23, 1, '[B] Back', curses.A_REVERSE if highlight_keys else 0)
stdscr.clrtoeol()
@@ -1110,7 +1253,7 @@ while True:
button = None
def try_highlight():
global c, highlight_debounce, highlight_keys, highlight_count, current_screen
global c, highlight_debounce, highlight_keys, highlight_count, current_screen, search_results
if c and time.time() - highlight_debounce > 0.6:
highlight_debounce = time.time()
@@ -1122,6 +1265,7 @@ while True:
if highlight_count >= 3:
highlight_count = 0
current_screen = 'help'
search_results = None
if current_screen == 'home':
if button == 's':
@@ -1428,6 +1572,52 @@ I will be terse in my responses.
else:
if c < 127 and c > 31:
label_consumable = label_consumable[:-1] + chr(c) + '_'
elif label_forum_search:
if c == curses.KEY_BACKSPACE:
label_forum_search = label_forum_search[:-2] + '_'
elif c == KEY_ESCAPE:
label_forum_search = ''
stdscr.erase()
elif c == KEY_ENTER:
if len(label_forum_search) > 2:
stdscr.addstr(16, 4, 'Searching...')
stdscr.refresh()
try:
search_results = search_forum_thread(label_forum_search[:-1])
except BaseException as e:
logging.exception(e)
stdscr.addstr(16, 4, 'Error.')
stdscr.clrtoeol()
stdscr.refresh()
time.sleep(2)
stdscr.erase()
label_forum_search = ''
else:
if c < 127 and c > 31:
label_forum_search = label_forum_search[:-1] + chr(c) + '_'
elif search_results is not None:
if c == KEY_ESCAPE or button == 'b':
search_results = None
stdscr.erase()
elif c >= 49 and c <= 57:
num = int(chr(c))
if num <= len(search_results):
stdscr.addstr(21, 1, 'Printing...')
stdscr.refresh()
try:
print_forum_label(search_results[num-1])
except BaseException as e:
logging.exception(e)
stdscr.addstr(15, 4, 'Error.')
stdscr.clrtoeol()
stdscr.refresh()
time.sleep(2)
stdscr.erase()
label_consumable = ''
else:
try_highlight()
else:
try_highlight()
elif button == 'b' or c == KEY_ESCAPE:
current_screen = 'home'
elif button == 't':
@@ -1438,6 +1628,8 @@ I will be terse in my responses.
label_generic = '_'
elif button == 'c':
label_consumable = '_'
elif button == 'f':
label_forum_search = '_'
else:
try_highlight()
@@ -1551,14 +1743,14 @@ I will be terse in my responses.
res = ''
if button == 'r':
res = mqtt_publish('train/control/speed', 50)
logging.info('Setting train speed to: 50')
res = mqtt_publish('train/control/speed', -300)
logging.info('Setting train speed to: -300')
elif button == 't' or c == KEY_SPACE:
res = mqtt_publish('train/control/speed', 90)
logging.info('Setting train speed to: 90')
res = mqtt_publish('train/control/speed', 0)
logging.info('Setting train speed to: 0')
elif button == 'f':
res = mqtt_publish('train/control/speed', 140)
logging.info('Setting train speed to: 140')
res = mqtt_publish('train/control/speed', 300)
logging.info('Setting train speed to: 300')
elif button == 'b' or c == KEY_ESCAPE:
current_screen = 'home'

View File

@@ -1,11 +1,8 @@
wa_api_key = ''
# Get these from your network inspector after sending a message to character.ai
character_ai_cookies = {
'_legacy_auth0.whatever.is.authenticated': 'true',
'auth0.whatever.is.authenticated': 'true',
'messages': '',
'csrftoken': '',
'sessionid': '',
}
character_ai_token = ''
openai_key = ''
MQTT_WRITER_PASSWORD = ''
FORUM_SEARCH_API_KEY = ''