def go(self, mode=None, host=None, port=None, path=None, query=None):
self.history.append((
self.mode, self.host, self.port, self.path
))
if mode is not None:
self.mode = mode
if host is not None:
self.host = host
if port is not None:
self.port = int(port)
if path is not None:
self.path = path
if mode == MenuType.QUERY:
self.path += '\t{}'.format(query)
def fetch(self):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((self.host, self.port))
s.sendall('{}\r\n'.format(self.path).encode('utf-8'))
resp = b''
while True:
chunk = s.recv(1024)
if not chunk:
break
resp += chunk
return resp
except:
return None
class Navigator(Fetcher):
def __init__(self):
super().__init__()
self.links = []
self.buffer = []
def goto(self, num):
if num <= 0 or num > len(self.links):
print('\tWrong link number')
else:
self.go(*self.links[num - 1])
def download(self, filename):
data = self.fetch()
with open(filename, 'wb') as f:
f.write(data)
def render(self):
self.buffer = []
if self.mode == MenuType.HTML:
if self.path.lower().startswith('url:'):
subprocess.call(['/usr/bin/xdg-open', self.path[4:]])
else:
subprocess.call(['/usr/bin/lynx', 'gopher://{}:{}/0{}'.format(self.host, self.port, self.path)])
self.back()
return
data = self.fetch()
if data is None:
self.buffer = 'Failed to display gopher://{}:{}/{}{}\n{}'.format(
self.host, self.port, self.mode, self.path, traceback.format_exc()
).split('\n')
self.mode = MenuType.DOC
return
if self.mode == MenuType.DOC:
self.buffer = data.decode('utf-8').split('\n')
elif self.mode == MenuType.MENU or self.mode == MenuType.QUERY:
self.links = []
items = [item.split('\t') for item in data.decode('utf-8').split('\n')]
for item in items:
if len(item) < 4:
continue
itype = item[0][0]
itext = item[0][1:]
if itype == MenuType.INFO:
self.buffer.append((None, itext))
elif itype == MenuType.DOC:
self.links.append((itype, item[2], item[3], item[1]))
self.buffer.append((
len(self.links),
itext, itype
))
elif itype == MenuType.MENU:
self.links.append((itype, item[2], item[3], item[1]))
self.buffer.append((
len(self.links),
itext, itype
))
elif itype == MenuType.BIN or itype == MenuType.SOUND or itype == MenuType.IMAGE or itype == MenuType.GIF:
self.links.append((itype, item[2], item[3], item[1]))
self.buffer.append((
len(self.links),
itext, itype
))
elif itype == MenuType.QUERY:
self.links.append((itype, item[2], item[3], item[1]))
self.buffer.append((
len(self.links),
itext, itype
))
elif itype == MenuType.HTML:
self.links.append((itype, item[2], item[3], item[1]))
self.buffer.append((
len(self.links),
itext, itype
))
else:
self.buffer.append((None, 'Unknown type: {}'.format(itype)))
else:
self.buffer.append('\tNot yet implemented')
def main(stdscr):
navi.render()
start = 0
link = 0
key = None
history = []
pagesize = curses.LINES - 2
curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK)
curses.init_pair(2, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
curses.init_pair(3, curses.COLOR_BLUE, curses.COLOR_BLACK)
curses.init_pair(4, curses.COLOR_CYAN, curses.COLOR_BLACK)
while True:
stdscr.clear()
stdscr.addstr(0, 0, '[ gopher://{}:{}/{}{}'.format(navi.host, navi.port, navi.mode, navi.path), curses.A_BOLD)
stdscr.addstr(0, curses.COLS - 1, ']')
stdscr.refresh()
first = None
last = None
for i in range(min(pagesize, len(navi.buffer) - start)):
color = curses.A_NORMAL
if navi.mode == MenuType.MENU or navi.mode == MenuType.QUERY:
text = navi.buffer[i + start][1]
color = curses.A_BOLD
if navi.buffer[i + start][0] is not None:
if first is None:
first = navi.buffer[i + start][0] - 1
last = navi.buffer[i + start][0] - 1
linktype = navi.buffer[i + start][2]
if linktype == MenuType.DOC:
color |= curses.color_pair(1)
elif linktype == MenuType.BIN:
color |= curses.color_pair(2)
elif linktype == MenuType.MENU:
color |= curses.color_pair(3)
elif linktype == MenuType.QUERY:
color |= curses.color_pair(4)
if navi.buffer[i + start][0] == link + 1:
color |= curses.A_REVERSE
text = navi.buffer[i + start][1]
# stdscr.addstr(i + 1, 0, '> ')
else:
text = navi.buffer[i + start]
try:
stdscr.addstr(i + 1, 2, text, color)
except:
pass
stdscr.move(curses.LINES - 1, 0)
stdscr.refresh()
key = stdscr.getkey()
if key == 'KEY_PPAGE':
start = max(0, start - pagesize)
elif key == 'KEY_NPAGE':
start = start + pagesize
elif key == 'KEY_RIGHT' or key == '\n':
if navi.links[link][0] == MenuType.QUERY:
curses.echo()
stdscr.addstr(curses.LINES - 1, 0, 'Query: ')
s = stdscr.getstr(curses.LINES - 1, 7).decode('utf-8')
curses.noecho()
navi.go(*navi.links[link], query=s)
elif navi.links[link][0] == MenuType.BIN:
curses.echo()
stdscr.addstr(curses.LINES - 1, 0, 'Filename: ')
filename = stdscr.getstr(curses.LINES - 1, 10).decode('utf-8')
curses.noecho()
if filename != '':
navi.go(*navi.links[link])
navi.download(filename)
navi.back()
else:
navi.goto(link + 1)
history.append((start, link))
link = 0
start = 0
navi.render()
elif key == 'KEY_LEFT':
navi.back()
if len(history) > 0:
start, link = history.pop()
navi.render()
elif key == 'KEY_UP':
if navi.mode == MenuType.DOC or last is None or link == 0:
start = max(0, start - pagesize)
elif link > 0:
link -= 1
if link < first:
start = max(0, start - pagesize)
elif key == 'KEY_DOWN':
if navi.mode == MenuType.DOC or last is None:
start += pagesize
elif link < len(navi.links):
link += 1
if link > last:
start += pagesize
elif key == 'g':
curses.echo()
stdscr.addstr(curses.LINES - 1, 0, 'Host: ')
s = stdscr.getstr(curses.LINES - 1, 6).decode('utf-8')
curses.noecho()
parts = s.split(':')
navi.go(host=parts[0], port=70 if len(parts) < 2 else int(parts[1]), path='', mode=MenuType.MENU)
history.append((start, link))
link = 0
start = 0
navi.render()
elif key == 'q':
break
# elif str(key).isdigit():
# stdscr.addstr(curses.LINES - 1, 0, key)
# curses.echo()
# s = str(key) + stdscr.getstr(curses.LINES - 1, 1)
# navi.goto()
# curses.noecho()
if __name__ == '__main__':
navi = Navigator()
curses.wrapper(main)