#!/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 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:
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
@<host> -- request server's index
<selector> -- 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] == '@':
try:
host, port = command[1:].split(':')
navi.go(host=host, port=port, path='', mode=MenuType.MENU)
except ValueError:
navi.go(host=command[1:], path='', mode=MenuType.MENU)
elif command.isdigit():
navi.goto(int(command))
else:
navi.go(path=command, mode=MenuType.MENU)