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 socket
|
||||||
import signal
|
import signal
|
||||||
import struct
|
import struct
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
|
@ -30,6 +32,9 @@ else:
|
||||||
|
|
||||||
|
|
||||||
DBG = True
|
DBG = True
|
||||||
|
HEXDUMP_IN = True
|
||||||
|
HEXDUMP_OUT = False
|
||||||
|
|
||||||
MSG_LEN = 8192
|
MSG_LEN = 8192
|
||||||
HEX_WIDTH = 16
|
HEX_WIDTH = 16
|
||||||
|
|
||||||
|
@ -199,7 +204,7 @@ def trunc(txt, maxlen):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Printer:
|
class Printer(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.mutex = threading.Lock()
|
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):
|
class TelnetHost(asyncore.dispatcher):
|
||||||
|
|
||||||
def __init__(self, p, host, port):
|
def __init__(self, p, host, port, world):
|
||||||
asyncore.dispatcher.__init__(self)
|
asyncore.dispatcher.__init__(self)
|
||||||
self.p = p
|
self.p = p
|
||||||
|
self.world = world
|
||||||
self.clients = []
|
self.clients = []
|
||||||
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
if PY2:
|
if PY2:
|
||||||
|
@ -240,7 +312,9 @@ class TelnetHost(asyncore.dispatcher):
|
||||||
def handle_accept(self):
|
def handle_accept(self):
|
||||||
socket, addr = self.accept()
|
socket, addr = self.accept()
|
||||||
self.con(' ++', addr, len(self.clients) + 1)
|
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)
|
self.clients.append(remote)
|
||||||
|
|
||||||
def broadcast(self, message):
|
def broadcast(self, message):
|
||||||
|
@ -256,10 +330,12 @@ class TelnetHost(asyncore.dispatcher):
|
||||||
|
|
||||||
class Client(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)
|
asyncore.dispatcher.__init__(self, socket)
|
||||||
self.host = host
|
self.host = host
|
||||||
self.socket = socket
|
self.socket = socket
|
||||||
|
self.world = world
|
||||||
|
self.user = user
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
self.outbox = Queue()
|
self.outbox = Queue()
|
||||||
self.replies = Queue()
|
self.replies = Queue()
|
||||||
|
@ -302,7 +378,6 @@ class Client(asyncore.dispatcher):
|
||||||
for x in range(self.h):
|
for x in range(self.h):
|
||||||
self.screen.append(u'*' * self.w)
|
self.screen.append(u'*' * self.w)
|
||||||
|
|
||||||
self.nick = 'dude'
|
|
||||||
self.msg_hist = []
|
self.msg_hist = []
|
||||||
self.msg_hist_n = None
|
self.msg_hist_n = None
|
||||||
|
|
||||||
|
@ -344,20 +419,36 @@ class Client(asyncore.dispatcher):
|
||||||
else:
|
else:
|
||||||
msg = src.get()
|
msg = src.get()
|
||||||
|
|
||||||
if len(msg) < 100:
|
if HEXDUMP_OUT:
|
||||||
hexdump(msg, '<<--')
|
if len(msg) < 100:
|
||||||
else:
|
hexdump(msg, '<<--')
|
||||||
print('<<-- : [{0} byte]'.format(len(msg)))
|
else:
|
||||||
|
print('<<-- : [{0} byte]'.format(len(msg)))
|
||||||
|
|
||||||
sent = self.send(msg)
|
sent = self.send(msg)
|
||||||
self.backlog = msg[sent:]
|
self.backlog = msg[sent:]
|
||||||
|
|
||||||
def send_status_line_update(self):
|
def refresh(self, full_redraw, cursor_moved):
|
||||||
with self.mutex:
|
""" compose necessary ansi text and send to client """
|
||||||
if self.update_status_line():
|
to_send = u''
|
||||||
self.send_lines([self.h-2], False)
|
to_send += self.update_top_bar(full_redraw)
|
||||||
|
to_send += self.update_chat_view(full_redraw)
|
||||||
def update_status_line(self):
|
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')
|
hhmmss = datetime.datetime.utcnow().strftime('%H:%M:%S')
|
||||||
nChan = 1
|
nChan = 1
|
||||||
nChans = 3
|
nChans = 3
|
||||||
|
@ -365,19 +456,36 @@ class Client(asyncore.dispatcher):
|
||||||
chan_name = u'general'
|
chan_name = u'general'
|
||||||
hilight_chans = u'\033[1;33mh 2,5,8\033[22;39m'
|
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'
|
active_chans = u'\033[1;32ma 1,3,4,6,7\033[22;39m'
|
||||||
line = trunc(
|
line = trunc(u'{0}{1} {2} #{3} {4} {5}\033[K'.format(
|
||||||
u'\033[0;37;44;48;5;235m{0} {1} #{2} {3} {4}'.format(
|
preface, hhmmss, nChan, chan_name, hilight_chans, active_chans, nUsers), self.w)
|
||||||
hhmmss, nChan, chan_name, hilight_chans, active_chans, nUsers), self.w)
|
if full_redraw:
|
||||||
if self.screen[self.h-2] == line:
|
if self.screen[self.h-2] != line:
|
||||||
return False
|
self.screen[self.h-2] = line
|
||||||
|
return trunc(line, self.w)
|
||||||
else:
|
else:
|
||||||
|
old = self.screen[self.h-2]
|
||||||
self.screen[self.h-2] = line
|
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)
|
msg_len = len(self.linebuf)
|
||||||
vis_text = 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:
|
if msg_len <= free_space:
|
||||||
self.lineview = 0
|
self.lineview = 0
|
||||||
else:
|
else:
|
||||||
|
@ -386,41 +494,25 @@ class Client(asyncore.dispatcher):
|
||||||
elif self.linepos > self.lineview + free_space:
|
elif self.linepos > self.lineview + free_space:
|
||||||
self.lineview = self.linepos - free_space
|
self.lineview = self.linepos - free_space
|
||||||
vis_text = vis_text[self.lineview:self.lineview+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)
|
line = u'\033[0;36m{0}>\033[0m {1}'.format(self.user.nick, vis_text)
|
||||||
if self.screen[self.h-1] == line:
|
if self.screen[self.h-1] != line:
|
||||||
return False
|
|
||||||
else:
|
|
||||||
self.screen[self.h-1] = line
|
self.screen[self.h-1] = line
|
||||||
return True
|
return u'\033[{0}H{1}\033[K'.format(self.h, line)
|
||||||
|
return u''
|
||||||
def send_lines(self, to_send, cursor_moved):
|
|
||||||
if not to_send and not cursor_moved:
|
def update_chat_view(self, full_redraw):
|
||||||
return
|
# TODO: naive implementation;
|
||||||
if DBG and to_send:
|
# should impl screen scrolling
|
||||||
dstr = '<<-- lines: '
|
ret = u''
|
||||||
dlo = None
|
for n in range(self.h - 3):
|
||||||
dlast = None
|
line = u'{0}<{1:-4d}>{2}<>'.format(
|
||||||
for v in to_send:
|
u'\033[0m' if n==0 else '',
|
||||||
if dlast != v - 1:
|
n + 1, '*' * (self.w - 8))
|
||||||
if dlo is not None:
|
|
||||||
if dlo == dlast:
|
if self.screen[n+1] != line:
|
||||||
dstr += '{0}, '.format(dlo)
|
self.screen[n+1] = line
|
||||||
else:
|
ret += u'\033[{0}H{1}'.format(n+2, self.screen[n+1])
|
||||||
dstr += '{0}-{1}, '.format(dlo, dlast)
|
return ret
|
||||||
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'))
|
|
||||||
|
|
||||||
def read_cb(self, full_redraw):
|
def read_cb(self, full_redraw):
|
||||||
aside = u''
|
aside = u''
|
||||||
|
@ -456,7 +548,7 @@ class Client(asyncore.dispatcher):
|
||||||
for pch in aside:
|
for pch in aside:
|
||||||
nch = ord(pch)
|
nch = ord(pch)
|
||||||
if nch < 0x20 or (nch >= 0x80 and nch < 0x100):
|
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 += '?'
|
plain += '?'
|
||||||
else:
|
else:
|
||||||
plain += pch
|
plain += pch
|
||||||
|
@ -551,62 +643,18 @@ class Client(asyncore.dispatcher):
|
||||||
self.say(msg.encode('utf-8'))
|
self.say(msg.encode('utf-8'))
|
||||||
return
|
return
|
||||||
|
|
||||||
to_send = []
|
|
||||||
|
|
||||||
if full_redraw:
|
if full_redraw:
|
||||||
self.screen = ['x'] * self.h
|
self.screen = ['x'] * self.h
|
||||||
|
|
||||||
# top bar
|
self.refresh(full_redraw,
|
||||||
top_bar = u'\033[44;48;5;235;38;5;220mtopic goes here'
|
old_cursor != self.linepos)
|
||||||
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:]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TelnetClient(Client):
|
class TelnetClient(Client):
|
||||||
|
|
||||||
def __init__(self, host, socket, address):
|
def __init__(self, host, socket, address, world, user):
|
||||||
Client.__init__(self, host, socket, address)
|
Client.__init__(self, host, socket, address, world, user)
|
||||||
self.replies.put(b'\xff\xfe\x22') # don't linemode
|
self.replies.put(b'\xff\xfe\x22') # don't linemode
|
||||||
self.replies.put(b'\xff\xfb\x01') # will echo
|
self.replies.put(b'\xff\xfb\x01') # will echo
|
||||||
self.replies.put(b'\xff\xfd\x1f') # do naws
|
self.replies.put(b'\xff\xfd\x1f') # do naws
|
||||||
|
@ -617,7 +665,8 @@ class TelnetClient(Client):
|
||||||
if not data:
|
if not data:
|
||||||
self.host.part(self)
|
self.host.part(self)
|
||||||
|
|
||||||
hexdump(data, '-->>')
|
if HEXDUMP_IN:
|
||||||
|
hexdump(data, '-->>')
|
||||||
|
|
||||||
self.in_bytes += data
|
self.in_bytes += data
|
||||||
|
|
||||||
|
@ -754,7 +803,7 @@ def push_worker(ifaces):
|
||||||
|
|
||||||
for iface in ifaces:
|
for iface in ifaces:
|
||||||
for client in iface.clients:
|
for client in iface.clients:
|
||||||
client.send_status_line_update()
|
client.refresh(False, False)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -784,8 +833,11 @@ p = Printer()
|
||||||
p.p(' * Capturing ^C')
|
p.p(' * Capturing ^C')
|
||||||
signal.signal(signal.SIGINT, signal_handler)
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
|
|
||||||
|
p.p(' * Creating world')
|
||||||
|
world = World()
|
||||||
|
|
||||||
p.p(' * Starting telnet server')
|
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')
|
p.p(' * Starting push driver')
|
||||||
push_thr = threading.Thread(target=push_worker, args=([telnet_host],))
|
push_thr = threading.Thread(target=push_worker, args=([telnet_host],))
|
||||||
|
|
Loading…
Reference in New Issue