#!/usr/bin/python3 import enum, socket, traceback, subprocess 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): 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: query = input('Search 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 = [] def goto(self, num): if num <= 0 or num > len(self.links): print('\tWrong link number') else: self.go(*self.links[num - 1]) def display(self): 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: print(data.decode('utf-8')) 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: print('\t{}'.format(itext)) elif itype == MenuType.DOC: self.links.append((itype, item[2], item[3], item[1])) print('{}[{}]\t{}{}{}'.format( ESC_WHITE, len(self.links), ESC_BLUE, itext, ESC_RESET )) elif itype == MenuType.MENU: self.links.append((itype, item[2], item[3], item[1])) print('{}[{}]\t{}{}{}'.format( ESC_WHITE, len(self.links), ESC_WHITE, itext, ESC_RESET )) 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])) print('{}[{}]\t{}{}{}'.format( ESC_WHITE, len(self.links), ESC_GREEN, itext, ESC_RESET )) elif itype == MenuType.QUERY: self.links.append((itype, item[2], item[3], item[1])) print('{}[{}]\t{}{}{}'.format( ESC_WHITE, len(self.links), ESC_CYAN, itext, ESC_RESET )) elif itype == MenuType.HTML: self.links.append((itype, item[2], item[3], item[1])) print('{}[{}]\t{}{}{}'.format( ESC_WHITE, len(self.links), ESC_RED, itext, ESC_RESET )) else: print('\tUnknown type: {}'.format(itype)) else: print('Not 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 ''') if __name__ == '__main__': navi = Navigator() omit_display = False while True: try: if not omit_display: navi.display() else: omit_display = False except: traceback.print_exc() print('\tFailed to render') command = input('{}{} {}{}> {}'.format(ESC_WHITE, navi.host, ESC_CYAN, navi.path, ESC_RESET)) if command == '': continue elif command == 'help' or command == '?': display_help() omit_display = True elif command == 'exit': break elif command == 'back': navi.back() elif command == 'index': navi.go(path='') elif command[0] == '@': navi.go(host=command[1:], path='', mode=MenuType.MENU) elif command.isdigit(): navi.goto(int(command)) else: navi.go(path=command, mode=MenuType.MENU)