#!/usr/bin/python3 import enum, socket, traceback, subprocess, curses from curses.textpad import Textbox, rectangle class MenuType(str, enum.Enum): DOC = '0' MENU = '1' QUERY = '7' BIN = '9' HTML = 'h' INFO = 'i' SOUND = 's' IMAGE = 'I' GIF = 'g' ESC_WHITE = '\033[1;37m' ESC_CYAN = '\033[1;36m' ESC_RESET = '\033[0m' ESC_RED = '\033[1;31m' ESC_GREEN = '\033[1;32m' ESC_YELLO = '\033[1;33m' ESC_BLUE = '\033[1;34m' class Fetcher(object): def __init__(self): self.mode = MenuType.MENU self.host = 'ake.crabdance.com' self.port = 70 self.path = '' self.history = [] 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 back(self): try: mode, host, port, path = self.history.pop() self.mode = mode self.host = host self.port = port self.path = path except: print('No entries in history') 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: traceback_exc() print('Failed to fetch data') 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 render(self): self.buffer = [] if self.mode == MenuType.BIN: print('Enter filename or leave empty to ignore') filename = input('Save as: ') if filename == '': return elif 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 self.mode == MenuType.DOC: self.buffer = data.decode('utf-8').split('\n') elif self.mode == MenuType.BIN: with open(filename, 'wb') as f: f.write(data) elif self.mode == MenuType.MENU: 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 )) elif itype == MenuType.MENU: self.links.append((itype, item[2], item[3], item[1])) self.buffer.append(( len(self.links), itext )) 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 )) elif itype == MenuType.QUERY: self.links.append((itype, item[2], item[3], item[1])) self.buffer.append(( len(self.links), itext )) elif itype == MenuType.HTML: self.links.append((itype, item[2], item[3], item[1])) self.buffer.append(( len(self.links), itext )) else: self.buffer.append('\tUnknown type: {}'.format(itype)) else: self.buffer.append('\tNot yet implemented') def display_help(): print('''Omsk subway -- commandline gopher navigator ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Commands: exit -- exit program help -- view this help back -- return to previous menu index -- return to server index @ -- request server's index -- request menu by selector on current server ''') def main(stdscr): navi.render() start = 0 link = 0 key = None while True: stdscr.clear() stdscr.addstr(0, 0, 'start: {}, key: {}, url: gopher://{}:{}/{}{}'.format(start, key, navi.host, navi.port, navi.mode, navi.path)) stdscr.refresh() for i in range(min(start + curses.LINES - 1, len(navi.buffer) - start)): if navi.mode == MenuType.MENU: if navi.buffer[i + start][0] == None: text = '\t' + navi.buffer[i + start][1] elif navi.buffer[i + start][0] == link + 1: text = '[*]\t' + navi.buffer[i + start][1] else: text = '[ ]\t' + navi.buffer[i + start][1] else: text = navi.buffer[i + start] try: stdscr.addstr(i + 1, 0, text) except: pass stdscr.move(curses.LINES - 1, 0) stdscr.refresh() key = stdscr.getkey() if key == 'KEY_PPAGE': start = max(0, start - curses.LINES - 1) elif key == 'KEY_NPAGE': start = start + curses.LINES - 1 elif key == '\n': if navi.links[link][0] == MenuType.QUERY: stdscr.move(curses.LINES - 1, 0) curses.echo() s = stdscr.getstr(0, 0, curses.COLS).decode('utf-8') navi.go(*navi.links[link], query=s) else: navi.goto(link + 1) link = 0 start = 0 navi.render() elif key == 'b': navi.back() link = 0 start = 0 navi.render() elif key == 'KEY_UP': link = (link - 1) % len(navi.links) elif key == 'KEY_DOWN': link = (link + 1) % len(navi.links) elif key == 'q': break if __name__ == '__main__': navi = Navigator() curses.wrapper(main)