Re-add spam filtering, improve length marquee limiting

This commit is contained in:
jaseg 2016-01-04 21:14:55 +01:00
parent dfca7a95a3
commit cae13443bc
4 changed files with 87 additions and 65 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
firmware/gcc/* firmware/gcc/*
host/secret_sauce.py

View file

@ -40,7 +40,7 @@ class Font:
textw, texth = c_size_t(0), c_size_t(0) textw, texth = c_size_t(0), c_size_t(0)
res = lib.framebuffer_get_text_bounds(textbytes, self.font, byref(textw), byref(texth)) res = lib.framebuffer_get_text_bounds(textbytes, self.font, byref(textw), byref(texth))
if res: if res:
raise ValueError('Invalid text') raise RuntimeError('Invalid text')
return textw.value, texth.value return textw.value, texth.value
def render_text(self, text, offset): def render_text(self, text, offset):
@ -49,7 +49,7 @@ class Font:
res = lib.framebuffer_render_text(textbytes, self.font, self.cbuf, res = lib.framebuffer_render_text(textbytes, self.font, self.cbuf,
config.display_width, config.display_height, offset) config.display_width, config.display_height, offset)
if res: if res:
raise ValueError('Invalid text') raise RuntimeError('Invalid text')
return self.cbuf return self.cbuf
unifont = Font() unifont = Font()

View file

@ -44,3 +44,7 @@ crap_fw_addr, crap_fw_port = '127.0.0.1', 1338
# USB Serial number of matelight to control as byte string (None for first matelight connected) # USB Serial number of matelight to control as byte string (None for first matelight connected)
ml_usb_serial_match = None ml_usb_serial_match = None
# Maximum width allowed for marquee texts in px. For reference: Using GNU unifont, a normal (half-width) char such as ∀
# is 8px wide, a full-width char such as 水 is 16px wide.
max_marquee_width = 140*8

View file

@ -4,9 +4,12 @@ import socket
import time import time
import itertools import itertools
import sys import sys
import re
from contextlib import suppress from contextlib import suppress
import asyncio import asyncio
import threading import threading
import functools
import operator
import config import config
@ -14,86 +17,100 @@ import matelight
import bdf import bdf
import crap import crap
import secret_sauce
def log(*args): def log(*args):
print(time.strftime('\x1B[93m[%m-%d %H:%M:%S]\x1B[0m'), ' '.join(str(arg) for arg in args), '\x1B[0m') print(time.strftime('\x1B[93m[%m-%d %H:%M:%S]\x1B[0m'), ' '.join(str(arg) for arg in args), '\x1B[0m')
sys.stdout.flush() sys.stdout.flush()
def addrcolor(addr):
col = 16 + (functools.reduce(operator.xor, (int(e or '0') for e in re.split('[.:]', addr))) % 216)
return '\x1B[38;5;{}m{}\x1B[0m'.format(col, addr)
class TextRenderer: class TextRenderer:
def __init__(self, text, title='default', font=bdf.unifont): def __init__(self, text, title='default', checkwidth=False, font=bdf.unifont):
self.text = text self.text = text
self.font = font self.font = font
(self.width, _), _testrender = font.compute_text_bounds(text), font.render_text(text, 0) (self.width, _), _testrender = font.compute_text_bounds(text), font.render_text(text, 0)
self.title = title self.title = title
if self.width > config.max_marquee_width:
raise ValueError()
def __iter__(self): def __iter__(self):
for i in range(-config.display_width, self.width): for i in range(-config.display_width, self.width):
yield self.title, self.font.render_text(self.text, i) yield self.title, self.font.render_text(self.text, i)
time.sleep(0.05) time.sleep(0.05)
class MatelightTCPServer: class MatelightTCPServer:
def __init__(self, ip, port, loop): def __init__(self, ip, port, loop):
coro = asyncio.start_server(self.handle_conn, ip, port, loop=loop) coro = asyncio.start_server(self.handle_conn, ip, port, loop=loop)
server = loop.run_until_complete(coro) server = loop.run_until_complete(coro)
self.renderqueue = [] self.renderqueue = []
self.close = server.close self.close = server.close
def __iter__(self): def __iter__(self):
q, self.renderqueue = self.renderqueue, [] q, self.renderqueue = self.renderqueue, []
for title, frame in itertools.chain(*q): for title, frame in itertools.chain(*q):
yield title, frame yield title, frame
async def handle_conn(self, reader, writer): async def handle_conn(self, reader, writer):
line = (await reader.read(1024)).decode('UTF-8').strip() line = (await reader.read(1024)).decode('UTF-8').strip()
if len(line) > 140: # Unicode string length, *not* byte length of encoded UTF-8 addr,*rest = writer.get_extra_info('peername')
writer.write(b'TOO MUCH INFORMATION!\n')
else: log('\x1B[95mText from\x1B[0m {}: {}\x1B[0m'.format(addrcolor(addr), line))
addr,*rest = writer.get_extra_info('peername') try:
log('\x1B[95mText from\x1B[0m {}: {}\x1B[0m'.format(addr, line)) secret_sauce.check_spam(str(addr), line)
try: renderer = TextRenderer(line, title='tcp:'+addr, checkwidth=True)
self.renderqueue.append(TextRenderer(line, title='tcp:'+addr)) except secret_sauce.SpamError as err:
except: log('\x1B[91mMessage rejected from {}: {}'.format(addrcolor(addr), err))
writer.write(b'STAHPTROLLINK?\n') writer.write(b'BLERGH!\n')
else: except ValueError: # Text too long
writer.write(b'KTHXBYE!\n') writer.write(b'TOO MUCH INFORMATION!\n')
await writer.drain() except RuntimeError: # Invalid escape etc.
writer.close() writer.write(b'STAHPTROLLINK?\n')
else:
self.renderqueue.append(renderer)
writer.write(b'KTHXBYE!\n')
await writer.drain()
writer.close()
def _fallbackiter(it, fallback): def _fallbackiter(it, fallback):
for fel in fallback: for fel in fallback:
for el in it: for el in it:
yield el yield el
yield fel yield fel
def defaulttexts(filename='default.lines'): def defaulttexts(filename='default.lines'):
with open(filename) as f: with open(filename) as f:
return itertools.chain.from_iterable(( TextRenderer(l[:-1].replace('\\x1B', '\x1B')) for l in f.readlines() )) return itertools.chain.from_iterable(( TextRenderer(l[:-1].replace('\\x1B', '\x1B')) for l in f.readlines() ))
if __name__ == '__main__': if __name__ == '__main__':
try: try:
ml = matelight.Matelight(config.ml_usb_serial_match) ml = matelight.Matelight(config.ml_usb_serial_match)
except ValueError as e: except ValueError as e:
print(e, 'Starting in headless mode.', file=sys.stderr) print(e, 'Starting in headless mode.', file=sys.stderr)
ml = None ml = None
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
tcp_server = MatelightTCPServer(config.tcp_addr, config.tcp_port, loop) tcp_server = MatelightTCPServer(config.tcp_addr, config.tcp_port, loop)
udp_server = crap.CRAPServer(config.udp_addr, config.udp_port) udp_server = crap.CRAPServer(config.udp_addr, config.udp_port)
forwarder = crap.CRAPClient(config.crap_fw_addr, config.crap_fw_port) if config.crap_fw_addr is not None else None forwarder = crap.CRAPClient(config.crap_fw_addr, config.crap_fw_port) if config.crap_fw_addr is not None else None
async_thr = threading.Thread(target=loop.run_forever) async_thr = threading.Thread(target=loop.run_forever)
async_thr.daemon = True async_thr.daemon = True
async_thr.start() async_thr.start()
with suppress(KeyboardInterrupt): with suppress(KeyboardInterrupt):
while True: while True:
for title, frame in _fallbackiter(udp_server, _fallbackiter(tcp_server, defaulttexts())): for title, frame in _fallbackiter(udp_server, _fallbackiter(tcp_server, defaulttexts())):
if ml: if ml:
ml.sendframe(frame) ml.sendframe(frame)
if forwarder: if forwarder:
forwarder.sendframe(frame) forwarder.sendframe(frame)
tcp_server.close() tcp_server.close()
udp_server.close() udp_server.close()
forwarder.close() forwarder.close()