Re-add spam filtering, improve length marquee limiting
This commit is contained in:
parent
dfca7a95a3
commit
cae13443bc
4 changed files with 87 additions and 65 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
||||||
firmware/gcc/*
|
firmware/gcc/*
|
||||||
|
host/secret_sauce.py
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
143
host/server.py
143
host/server.py
|
|
@ -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()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue