mirror of https://github.com/9001/r0c.git
2017-1216-3, channel concept
This commit is contained in:
parent
a570823e00
commit
e1b673beff
270
r0c.py
270
r0c.py
|
@ -15,6 +15,8 @@ import asyncore
|
|||
import socket
|
||||
import signal
|
||||
import struct
|
||||
import base64
|
||||
import hashlib
|
||||
import datetime
|
||||
import time
|
||||
import sys
|
||||
|
@ -30,6 +32,9 @@ else:
|
|||
|
||||
|
||||
DBG = True
|
||||
HEXDUMP_IN = True
|
||||
HEXDUMP_OUT = False
|
||||
|
||||
MSG_LEN = 8192
|
||||
HEX_WIDTH = 16
|
||||
|
||||
|
@ -199,7 +204,7 @@ def trunc(txt, maxlen):
|
|||
|
||||
|
||||
|
||||
class Printer:
|
||||
class Printer(object):
|
||||
|
||||
def __init__(self):
|
||||
self.mutex = threading.Lock()
|
||||
|
@ -216,11 +221,78 @@ class Printer:
|
|||
|
||||
|
||||
|
||||
class World(object):
|
||||
def __init__(self):
|
||||
self.users = [] # User instances
|
||||
self.chans = [] # NChannel instances
|
||||
|
||||
def add_user(self, user):
|
||||
self.users.append(user)
|
||||
# announce it
|
||||
|
||||
|
||||
|
||||
class NChannel(object):
|
||||
def __init__(self):
|
||||
self.name = u''
|
||||
self.users = [] # UChannel instances
|
||||
|
||||
|
||||
|
||||
class UChannel(object):
|
||||
def __init__(self):
|
||||
self.user = None # the user which this object belongs to
|
||||
self.chan = None # the NChannel object
|
||||
self.last_ts = None # last time the user viewed this channel
|
||||
|
||||
|
||||
|
||||
class User(object):
|
||||
def __init__(self, world, address):
|
||||
self.world = world
|
||||
self.client = None # the client which this object belongs to
|
||||
self.chans = [] # UChannel instances
|
||||
self.nick = None
|
||||
|
||||
plain_base = u'lammo/{0}'.format(address[0])
|
||||
|
||||
for sep in u'/!@#$%^&*()_+-=[]{};:<>,.':
|
||||
|
||||
plain = plain_base
|
||||
while True:
|
||||
#print(plain)
|
||||
hashed = hashlib.sha256(plain).digest()
|
||||
hashed = base64.b64encode(hashed).replace('+','').replace('/','')[:6]
|
||||
#print(hashed)
|
||||
|
||||
ok = True
|
||||
for user in self.world.users:
|
||||
if user.nick == hashed:
|
||||
ok = False
|
||||
break
|
||||
|
||||
if ok:
|
||||
self.nick = hashed
|
||||
break
|
||||
else:
|
||||
if len(plain) > 100:
|
||||
break
|
||||
plain += '/{0}'.format(address[1])
|
||||
|
||||
if self.nick:
|
||||
break
|
||||
|
||||
if not self.nick:
|
||||
raise RuntimeException("out of legit nicknames")
|
||||
|
||||
|
||||
|
||||
class TelnetHost(asyncore.dispatcher):
|
||||
|
||||
def __init__(self, p, host, port):
|
||||
def __init__(self, p, host, port, world):
|
||||
asyncore.dispatcher.__init__(self)
|
||||
self.p = p
|
||||
self.world = world
|
||||
self.clients = []
|
||||
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
if PY2:
|
||||
|
@ -240,7 +312,9 @@ class TelnetHost(asyncore.dispatcher):
|
|||
def handle_accept(self):
|
||||
socket, addr = self.accept()
|
||||
self.con(' ++', addr, len(self.clients) + 1)
|
||||
remote = TelnetClient(self, socket, addr)
|
||||
user = User(self.world, addr)
|
||||
remote = TelnetClient(self, socket, addr, world, user)
|
||||
self.world.add_user(user)
|
||||
self.clients.append(remote)
|
||||
|
||||
def broadcast(self, message):
|
||||
|
@ -256,10 +330,12 @@ class TelnetHost(asyncore.dispatcher):
|
|||
|
||||
class Client(asyncore.dispatcher):
|
||||
|
||||
def __init__(self, host, socket, address):
|
||||
def __init__(self, host, socket, address, world, user):
|
||||
asyncore.dispatcher.__init__(self, socket)
|
||||
self.host = host
|
||||
self.socket = socket
|
||||
self.world = world
|
||||
self.user = user
|
||||
self.mutex = threading.Lock()
|
||||
self.outbox = Queue()
|
||||
self.replies = Queue()
|
||||
|
@ -302,7 +378,6 @@ class Client(asyncore.dispatcher):
|
|||
for x in range(self.h):
|
||||
self.screen.append(u'*' * self.w)
|
||||
|
||||
self.nick = 'dude'
|
||||
self.msg_hist = []
|
||||
self.msg_hist_n = None
|
||||
|
||||
|
@ -344,20 +419,36 @@ class Client(asyncore.dispatcher):
|
|||
else:
|
||||
msg = src.get()
|
||||
|
||||
if len(msg) < 100:
|
||||
hexdump(msg, '<<--')
|
||||
else:
|
||||
print('<<-- : [{0} byte]'.format(len(msg)))
|
||||
if HEXDUMP_OUT:
|
||||
if len(msg) < 100:
|
||||
hexdump(msg, '<<--')
|
||||
else:
|
||||
print('<<-- : [{0} byte]'.format(len(msg)))
|
||||
|
||||
sent = self.send(msg)
|
||||
self.backlog = msg[sent:]
|
||||
|
||||
def send_status_line_update(self):
|
||||
with self.mutex:
|
||||
if self.update_status_line():
|
||||
self.send_lines([self.h-2], False)
|
||||
|
||||
def update_status_line(self):
|
||||
def refresh(self, full_redraw, cursor_moved):
|
||||
""" compose necessary ansi text and send to client """
|
||||
to_send = u''
|
||||
to_send += self.update_top_bar(full_redraw)
|
||||
to_send += self.update_chat_view(full_redraw)
|
||||
to_send += self.update_status_bar(full_redraw)
|
||||
to_send += self.update_text_input(full_redraw)
|
||||
if to_send or cursor_moved:
|
||||
to_send += u'\033[{0};{1}H'.format(self.h, len(self.user.nick) + 2 + self.linepos + 1 - self.lineview)
|
||||
self.say(to_send.encode('utf-8'))
|
||||
|
||||
def update_top_bar(self, full_redraw):
|
||||
""" no need to optimize this tbh """
|
||||
top_bar = u'\033[1H\033[44;48;5;235;38;5;220mtopic goes here\033[K'
|
||||
if self.screen[0] != top_bar:
|
||||
self.screen[0] = top_bar
|
||||
return trunc(top_bar, self.w)
|
||||
return u''
|
||||
|
||||
def update_status_bar(self, full_redraw):
|
||||
preface = u'\033[{0}H\033[0;37;44;48;5;235m'.format(self.h-1)
|
||||
hhmmss = datetime.datetime.utcnow().strftime('%H:%M:%S')
|
||||
nChan = 1
|
||||
nChans = 3
|
||||
|
@ -365,19 +456,36 @@ class Client(asyncore.dispatcher):
|
|||
chan_name = u'general'
|
||||
hilight_chans = u'\033[1;33mh 2,5,8\033[22;39m'
|
||||
active_chans = u'\033[1;32ma 1,3,4,6,7\033[22;39m'
|
||||
line = trunc(
|
||||
u'\033[0;37;44;48;5;235m{0} {1} #{2} {3} {4}'.format(
|
||||
hhmmss, nChan, chan_name, hilight_chans, active_chans, nUsers), self.w)
|
||||
if self.screen[self.h-2] == line:
|
||||
return False
|
||||
line = trunc(u'{0}{1} {2} #{3} {4} {5}\033[K'.format(
|
||||
preface, hhmmss, nChan, chan_name, hilight_chans, active_chans, nUsers), self.w)
|
||||
if full_redraw:
|
||||
if self.screen[self.h-2] != line:
|
||||
self.screen[self.h-2] = line
|
||||
return trunc(line, self.w)
|
||||
else:
|
||||
old = self.screen[self.h-2]
|
||||
self.screen[self.h-2] = line
|
||||
return True
|
||||
|
||||
if len(old) != len(line):
|
||||
return trunc(line, self.w)
|
||||
|
||||
cutoff = len(preface) + len(hhmmss)
|
||||
changed_part1 = old[:cutoff] != line[:cutoff]
|
||||
changed_part2 = old[cutoff:] != line[cutoff:]
|
||||
|
||||
if changed_part2:
|
||||
return trunc(line, self.w)
|
||||
|
||||
if changed_part1:
|
||||
return line[:cutoff]
|
||||
#return u'\033[{0}H{1}'.format(self.h-1, hhmmss) # drops colors
|
||||
|
||||
return u''
|
||||
|
||||
def update_text_input(self):
|
||||
def update_text_input(self, full_redraw):
|
||||
msg_len = len(self.linebuf)
|
||||
vis_text = self.linebuf
|
||||
free_space = self.w - (len(self.nick) + 2 + 1) # nick chrome + final char on screen
|
||||
free_space = self.w - (len(self.user.nick) + 2 + 1) # nick chrome + final char on screen
|
||||
if msg_len <= free_space:
|
||||
self.lineview = 0
|
||||
else:
|
||||
|
@ -386,41 +494,25 @@ class Client(asyncore.dispatcher):
|
|||
elif self.linepos > self.lineview + free_space:
|
||||
self.lineview = self.linepos - free_space
|
||||
vis_text = vis_text[self.lineview:self.lineview+free_space]
|
||||
line = u'\033[0;36m{0}>\033[0m {1}'.format(self.nick, vis_text)
|
||||
if self.screen[self.h-1] == line:
|
||||
return False
|
||||
else:
|
||||
line = u'\033[0;36m{0}>\033[0m {1}'.format(self.user.nick, vis_text)
|
||||
if self.screen[self.h-1] != line:
|
||||
self.screen[self.h-1] = line
|
||||
return True
|
||||
|
||||
def send_lines(self, to_send, cursor_moved):
|
||||
if not to_send and not cursor_moved:
|
||||
return
|
||||
if DBG and to_send:
|
||||
dstr = '<<-- lines: '
|
||||
dlo = None
|
||||
dlast = None
|
||||
for v in to_send:
|
||||
if dlast != v - 1:
|
||||
if dlo is not None:
|
||||
if dlo == dlast:
|
||||
dstr += '{0}, '.format(dlo)
|
||||
else:
|
||||
dstr += '{0}-{1}, '.format(dlo, dlast)
|
||||
dlo = v
|
||||
dlast = v
|
||||
if dlo == dlast:
|
||||
dstr += str(dlo)
|
||||
else:
|
||||
dstr += '{0}-{1}'.format(dlo, dlast)
|
||||
print(dstr)
|
||||
|
||||
#print('<<-- lines: {0}'.format(to_send))
|
||||
msg = u''
|
||||
for n in to_send:
|
||||
msg += u'\033[{0}H{1}\033[K'.format(n+1, self.screen[n])
|
||||
msg += u'\033[{0};{1}H'.format(self.h, len(self.nick) + 2 + self.linepos + 1 - self.lineview)
|
||||
self.say(msg.encode('utf-8'))
|
||||
return u'\033[{0}H{1}\033[K'.format(self.h, line)
|
||||
return u''
|
||||
|
||||
def update_chat_view(self, full_redraw):
|
||||
# TODO: naive implementation;
|
||||
# should impl screen scrolling
|
||||
ret = u''
|
||||
for n in range(self.h - 3):
|
||||
line = u'{0}<{1:-4d}>{2}<>'.format(
|
||||
u'\033[0m' if n==0 else '',
|
||||
n + 1, '*' * (self.w - 8))
|
||||
|
||||
if self.screen[n+1] != line:
|
||||
self.screen[n+1] = line
|
||||
ret += u'\033[{0}H{1}'.format(n+2, self.screen[n+1])
|
||||
return ret
|
||||
|
||||
def read_cb(self, full_redraw):
|
||||
aside = u''
|
||||
|
@ -456,7 +548,7 @@ class Client(asyncore.dispatcher):
|
|||
for pch in aside:
|
||||
nch = ord(pch)
|
||||
if nch < 0x20 or (nch >= 0x80 and nch < 0x100):
|
||||
print('substituting non-printable \\x{0:2x}'.format(nch))
|
||||
print('substituting non-printable \\x{0:02x}'.format(nch))
|
||||
plain += '?'
|
||||
else:
|
||||
plain += pch
|
||||
|
@ -551,62 +643,18 @@ class Client(asyncore.dispatcher):
|
|||
self.say(msg.encode('utf-8'))
|
||||
return
|
||||
|
||||
to_send = []
|
||||
|
||||
if full_redraw:
|
||||
self.screen = ['x'] * self.h
|
||||
|
||||
# top bar
|
||||
top_bar = u'\033[44;48;5;235;38;5;220mtopic goes here'
|
||||
if self.screen[0] != top_bar:
|
||||
self.screen[0] = top_bar
|
||||
to_send.append(0)
|
||||
|
||||
# chat view
|
||||
for n in range(self.h - 3):
|
||||
line = u'{0}<{1:-4d}>{2}<>'.format(
|
||||
u'\033[0m' if n==0 else '',
|
||||
n + 1, '*' * (self.w - 8))
|
||||
|
||||
if self.screen[n+1] != line:
|
||||
self.screen[n+1] = line
|
||||
to_send.append(n+1)
|
||||
|
||||
if self.update_status_line():
|
||||
to_send.append(self.h-2)
|
||||
|
||||
if self.update_text_input():
|
||||
to_send.append(self.h-1)
|
||||
|
||||
self.send_lines(to_send, old_cursor != self.linepos)
|
||||
|
||||
## backspace
|
||||
#while True:
|
||||
# ofs = txstr.find(u'\x7f')
|
||||
# if ofs < 0:
|
||||
# break
|
||||
# if ofs == 0:
|
||||
# txstr = txstr[ofs+1:]
|
||||
# else:
|
||||
# txstr = txstr[:ofs-1] + txstr[ofs+1:]
|
||||
#
|
||||
|
||||
## newline
|
||||
##
|
||||
## putty: 0d 0a
|
||||
## winxp: 0d 0a
|
||||
## linux: 0d 00
|
||||
#if b'\r\0' in self.txbuf:
|
||||
# last_newline = self.txbuf.rfind(b'\r\0')
|
||||
# print('last_newline = {0}'.format(last_newline))
|
||||
# self.txbuf = self.txbuf[last_newline+1:]
|
||||
self.refresh(full_redraw,
|
||||
old_cursor != self.linepos)
|
||||
|
||||
|
||||
|
||||
class TelnetClient(Client):
|
||||
|
||||
def __init__(self, host, socket, address):
|
||||
Client.__init__(self, host, socket, address)
|
||||
def __init__(self, host, socket, address, world, user):
|
||||
Client.__init__(self, host, socket, address, world, user)
|
||||
self.replies.put(b'\xff\xfe\x22') # don't linemode
|
||||
self.replies.put(b'\xff\xfb\x01') # will echo
|
||||
self.replies.put(b'\xff\xfd\x1f') # do naws
|
||||
|
@ -617,7 +665,8 @@ class TelnetClient(Client):
|
|||
if not data:
|
||||
self.host.part(self)
|
||||
|
||||
hexdump(data, '-->>')
|
||||
if HEXDUMP_IN:
|
||||
hexdump(data, '-->>')
|
||||
|
||||
self.in_bytes += data
|
||||
|
||||
|
@ -754,7 +803,7 @@ def push_worker(ifaces):
|
|||
|
||||
for iface in ifaces:
|
||||
for client in iface.clients:
|
||||
client.send_status_line_update()
|
||||
client.refresh(False, False)
|
||||
|
||||
|
||||
|
||||
|
@ -784,8 +833,11 @@ p = Printer()
|
|||
p.p(' * Capturing ^C')
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
p.p(' * Creating world')
|
||||
world = World()
|
||||
|
||||
p.p(' * Starting telnet server')
|
||||
telnet_host = TelnetHost(p, '0.0.0.0', telnet_port)
|
||||
telnet_host = TelnetHost(p, '0.0.0.0', telnet_port, world)
|
||||
|
||||
p.p(' * Starting push driver')
|
||||
push_thr = threading.Thread(target=push_worker, args=([telnet_host],))
|
||||
|
|
Loading…
Reference in New Issue