diff --git a/btn_back.png b/btn_back.png deleted file mode 100644 index 3b19330..0000000 Binary files a/btn_back.png and /dev/null differ diff --git a/btn_forward.png b/btn_forward.png deleted file mode 100644 index 70d1fe8..0000000 Binary files a/btn_forward.png and /dev/null differ diff --git a/btn_home.png b/btn_home.png deleted file mode 100644 index 2536137..0000000 Binary files a/btn_home.png and /dev/null differ diff --git a/btn_menu.png b/btn_menu.png deleted file mode 100644 index 7d9694f..0000000 Binary files a/btn_menu.png and /dev/null differ diff --git a/btn_refresh.png b/btn_refresh.png deleted file mode 100644 index 48a8229..0000000 Binary files a/btn_refresh.png and /dev/null differ diff --git a/burrow.py b/burrow.py new file mode 100644 index 0000000..8770d7d --- /dev/null +++ b/burrow.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +################################################ +## Burrow, a gopher client/browser +## - - - - - - - - - - - - - - - - - - - - - - - +## Version 0.2.1 +################################################ + +from gui import GUI + + +if __name__ == '__main__': + app = GUI() + app.root.mainloop() diff --git a/conn.py b/conn.py deleted file mode 100644 index 440e64e..0000000 --- a/conn.py +++ /dev/null @@ -1,97 +0,0 @@ -import socket -import sys -import re - -class Tunnel: - def __init__(self): - self.raw_request = None - self.text_output = None - self.types = { - '0': '(TXT)', - '1': '(MENU)', - '2': None, - '3': 'Error code', - '4': None, - '5': None, - '6': None, - '7': '(INTER)', - '8': 'Telnet', - '9': '(BIN)', - '+': None, - 'g': '(GIF)', - 'I': '(IMG)', - 't': None, - 'h': '(HTML)', - 'i': '(INFO)', - 's': '(SOUND)' - } - - - def make_connection(self, resource, host, itemtype, port=70): - endline = '\r\n' - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.settimeout(20.0) - try: - port = int(port) - except: - port = 70 - s.connect((host, port)) - s.sendall((resource + '\r\n').encode('utf-8')) - r = s.makefile(mode = 'r') - raw_data = r.read() - try: - data = raw_data.decode('utf-8') - except AttributeError: - data = raw_data - self.raw_request = data - if itemtype[1] == '1': - #handle menus - self.text_output = self.gopher_to_text() - elif itemtype[1] == '0': - #handle text files - self.text_output = [self.raw_request] - self.text_output.insert(0,itemtype[1]) - s.close() - - - - def gopher_to_text(self): - message = self.raw_request.split('\n') - message = [x.split('\t') for x in message] - message = [{'type': x[0][0], 'description': x[0][1:], 'resource': x[1], 'host': x[2], 'port': x[3]} for x in message if len(x) >= 4] - return message - - - def parse_url(self, url): - regex = r'^(?P(?:gopher:\/\/)?)?(?P[\w\.\d]+)(?P(?::\d+)?)?(?P(?:\/\d)?)?(?P(?:\/[\w\/\d\-?.]*)?)?$' - match = re.match(regex, url) - - protocol = match.group('protocol') - itemtype = match.group('type') - host = match.group('host') - port = match.group('port') - resource = match.group('resource') - - if protocol != 'gopher://' and protocol: - return False - if itemtype and not self.types[itemtype[1]]: - return False - elif not itemtype: - itemtype = '/1' - if not host: - return False - if not resource: - resource = '/' - if port: - port = port[1:] - - self.make_connection(resource, host, itemtype, port) - - -if __name__ == '__main__': - inp = sys.argv[1:] - if len(inp) >= 2: - test = Tunnel() - test.make_connection(inp[1],inp[0],'70') - else: - print('Incorrect request') diff --git a/connect.py b/connect.py new file mode 100644 index 0000000..d9e0adf --- /dev/null +++ b/connect.py @@ -0,0 +1,29 @@ +import socket + +class connect: + def __init__(self): + self.raw_response = None + self.filetype = None + + def request(self, resource, host, itemtype, port=70): + #connects to server and returns list with response type and body + socket_conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + socket_conn.settimeout(20.0) + socket_conn.connect((host, port)) + socket_conn.sendall((resource + '\r\n').encode('utf-8')) + + if itemtype in ['I','p','g']: + response = socket_conn.makefile(mode = 'rb', errors = 'ignore') + else: + response = socket_conn.makefile(mode = 'r', errors = 'ignore') + + try: + self.raw_response = response.read() + self.filetype = itemtype + except UnicodeDecodeError: + self.raw_response = '3Error decoding server response\tfalse\tnull.host\t1' + self.filetype = '3' + + socket_conn.close() + + return {'type': self.filetype, 'body': self.raw_response} diff --git a/digger.py b/digger.py deleted file mode 100755 index 3dfb606..0000000 --- a/digger.py +++ /dev/null @@ -1,236 +0,0 @@ -#!/usr/bin/env python3 - -import tkinter as tk -from conn import Tunnel as go -import time -import sys - -class GUI: - def __init__(self): - self.history = [] - self.history_location = -1 - self.message_bar_content = '' - - #colors - self.FG = 'black' - self.BG = 'white' - self.LINK = 'blue' - self.ACTIVELINK = 'red' - self.HLB = '#e37800' - self.HLF = 'white' - self.STATUS = 'silver' - - #create and configure root window - self.root = tk.Tk(className='Digger') - self.root.title('Digger') - self.root.geometry("1200x800") - self.add_assets() - - #main frame objects - self.top_bar = tk.Frame(self.root, padx=10, height=70, relief=tk.FLAT, bd=2) - self.body = tk.Frame(self.root, relief=tk.RIDGE, bd=2) - self.status_bar = tk.Frame(self.root, height="20", bg=self.STATUS, takefocus=0) - - #top bar objects - self.btn_back = tk.Button(self.top_bar, image=self.img_back, bd=0, highlightthickness=0, takefocus=1) - self.btn_forward = tk.Button(self.top_bar,image=self.img_forward, bd=0, highlightthickness=0) - self.btn_refresh = tk.Button(self.top_bar,image=self.img_refresh, bd=0, highlightthickness=0) - self.btn_home = tk.Button(self.top_bar, image=self.img_home, bd=0, highlightthickness=0) - self.entry_url = tk.Entry(self.top_bar, selectbackground=self.HLB, selectforeground=self.HLF, highlightcolor=self.HLB) - self.btn_menu = tk.Button(self.top_bar, image=self.img_menu, bd=0, highlightthickness=0) - - #body objects - self.site_display = tk.Text(self.body, bg=self.BG, foreground=self.FG, padx=20, pady=20, wrap=tk.WORD, state=tk.DISABLED, spacing2=5, spacing1=5) - self.site_display.tag_configure('linkcolor', foreground=self.LINK, spacing1=5) - self.site_display.tag_configure('type_tag', background=self.FG, foreground=self.BG, spacing2=0, spacing1=0) - - #status bar objects - self.status_info = tk.Label(self.status_bar, textvariable=self.message_bar_content, bg=self.STATUS, takefocus=0) - - self.pack_geometry() - self.add_status_titles() - self.add_event_listeners() - - #--------------------------------------------------- - - def add_event_listeners(self): - buttons = [ - self.btn_back, - self.btn_forward, - self.btn_refresh, - self.btn_home, - self.btn_menu - ] - - for x in buttons: - x.bind('', self.update_status) - x.bind('', self.clear_status) - self.entry_url.bind('', self.execute_address) - self.btn_back.bind('', self.go_back) - self.btn_forward.bind('', self.go_forward) - - - def pack_geometry(self): - self.top_bar.pack(expand=False,fill=tk.BOTH,side=tk.TOP,anchor=tk.NW) - self.top_bar.pack_propagate(False) - self.body.pack(expand=True,fill=tk.BOTH,side=tk.TOP) - self.status_bar.pack(expand=False,fill=tk.X,side=tk.TOP,anchor=tk.SW) - self.btn_back.pack(side=tk.LEFT, padx=(0,20)) - self.btn_forward.pack(side=tk.LEFT, padx=(0,20)) - self.btn_refresh.pack(side=tk.LEFT, padx=(0,20)) - self.btn_home.pack(side=tk.LEFT, padx=(0,20)) - self.entry_url.pack(side=tk.LEFT, fill=tk.X, expand=True, ipady=10, ipadx=10) - self.btn_menu.pack(side=tk.LEFT, padx=(10,0)) - self.site_display.pack(expand=True, side=tk.TOP, fill=tk.BOTH) - self.status_info.pack(side=tk.LEFT) - - - def add_status_titles(self): - self.btn_back.pop_title = 'Back' - self.btn_forward.pop_title = 'Forward' - self.btn_refresh.pop_title = 'Refresh' - self.btn_home.pop_title = 'Home' - self.btn_menu.pop_title = 'Menu' - - - def add_assets(self): - self.img_back = tk.PhotoImage(file='./btn_back.png') - self.img_forward = tk.PhotoImage(file='./btn_forward.png') - self.img_refresh = tk.PhotoImage(file='./btn_refresh.png') - self.img_menu = tk.PhotoImage(file='./btn_menu.png') - self.img_home = tk.PhotoImage(file='./btn_home.png') - self.img_menu = tk.PhotoImage(file='./btn_menu.png') - self.message_bar_content = tk.StringVar() - self.message_bar_content.set('Ready.') - - - def execute_address(self, event, btn_url=False): - if btn_url and btn_url != -1: - url = btn_url - elif btn_url and btn_url == -1: - return - else: - url = self.entry_url.get() - self.history = self.history[:self.history_location+1] - self.history.append(url) - self.history_location = len(self.history) - 1 - self.site_display.focus_set() - request = go() - request.parse_url(url) - self.send_to_screen(request.text_output) - - - def gotolink(self, event, href, tag_name): - res = event.widget - res.tag_config(tag_name, background=self.ACTIVELINK) # change tag text style - res.update_idletasks() # make sure change is visible - time.sleep(.5) # optional delay to show changed text - self.entry_url.delete(0,tk.END) - self.entry_url.insert(tk.END,href) - self.execute_address(0) #the zero is meaningless, but the function expects a param - res.tag_config(tag_name, background=self.BG) # restore tag text style - res.update_idletasks() - - - def hoverlink(self, event, href, tag_name): - self.update_status(event, href) - e = event.widget - e.tag_config(tag_name, underline=1) - e.update_idletasks() - - - def send_to_screen(self, data): - link_count = 0 - self.site_display.config(state=tk.NORMAL) - self.site_display.delete(1.0, tk.END) - types = { - '0': '( TEXT )', - '1': '( MENU )', - '2': None, - '3': 'Error code', - '4': None, - '5': None, - '6': None, - '7': '( INTR )', - '8': 'Telnet', - '9': '( BIN )', - '+': None, - 'g': '( GIF )', - 'I': '( IMG )', - 't': None, - 'h': '( HTML )', - 'i': '( INFO )', - 's': '( SOUND)' - } - - if data[0] == '0': - self.site_display.insert(tk.END, data[1]) - elif data[0] == '1': - for x in data[1:]: - if x['type'] == 'i': - self.site_display.insert(tk.END,' \t\t{}\n'.format(x['description'])) - else: - # adapted from: - # https://stackoverflow.com/questions/27760561/tkinter-and-hyperlinks - if x['port'] and x['port'][0] != ':': - x['port'] = ':{}'.format(x['port']) - - link = 'gopher://{}{}/{}{}'.format(x['host'], x['port'], x['type'], x['resource']) - tag_name = 'link{}'.format(link_count) - callback = (lambda event, href=link, tag_name=tag_name: self.gotolink(event, href, tag_name)) - hover = (lambda event, href=link, tag_name=tag_name: self.hoverlink(event, href, tag_name)) - clear = (lambda event, tag_name=tag_name: self.clear_status(event, tag_name)) - self.site_display.tag_bind(tag_name, "", callback) - self.site_display.tag_bind(tag_name, "", hover) - self.site_display.tag_bind(tag_name, '', clear) - self.site_display.insert(tk.END, types[x['type']], ('type_tag',)) - self.site_display.insert(tk.END,'\t\t') - self.site_display.insert(tk.END, x['description'], (tag_name,'linkcolor')) - self.site_display.insert(tk.END, '\n') - link_count += 1 - self.site_display.config(state=tk.DISABLED) - - - def update_status(self, event, href=False): - if href: - self.message_bar_content.set(href) - else: - self.message_bar_content.set(event.widget.pop_title) - - - def clear_status(self, event, tag_name=False): - if tag_name: - e = event.widget - e.tag_config(tag_name, underline=0) - e.update_idletasks() - self.message_bar_content.set('') - - - def go_back(self, event): - if len(self.history) > 1 and self.history_location > 0: - self.history_location -= 1 - href = self.history[self.history_location] - self.entry_url.delete(0, tk.END) - self.entry_url.insert(tk.END, href) - else: - href = -1 - self.execute_address(False, href) - - - def go_forward(self, event): - if len(self.history) > 1 and self.history_location < len(self.history) - 1: - self.history_location += 1 - href = self.history[self.history_location] - self.entry_url.delete(0,tk.END) - self.entry_url.insert(tk.END, href) - else: - href = -1 - self.execute_address(False, href) - - - -if __name__ == '__main__': - app = GUI() - app.root.mainloop() - - diff --git a/go.config.json b/go.config.json new file mode 100644 index 0000000..89f1b31 --- /dev/null +++ b/go.config.json @@ -0,0 +1 @@ +{"favorites": [], "last_viewed": "gopher://gopher.floodgap.com:70/1/"} \ No newline at end of file diff --git a/gui.py b/gui.py new file mode 100644 index 0000000..6b22c2c --- /dev/null +++ b/gui.py @@ -0,0 +1,389 @@ +#!/usr/bin/env python3 + +import tkinter as tk +from connect import connect as conn +from parser import parser +import time +import sys +import json +import os.path +from io import BytesIO +from PIL import Image, ImageTk + +class GUI: + def __init__(self): + self.history = [] + self.history_location = -1 + self.message_bar_content = '' + self.config = None + self.read_config() + self.conn = conn() + self.parser = parser() + + #colors + self.FG = '#E0E2E4' + self.BG = '#2F393C' + self.LINK = '#E8E2B7' + self.ACTIVELINK = '#678CB1' + self.HLB = '#804000' + self.HLF = '#E0E2E4' + self.STATUS_BG = '#293134' + self.STATUS_FG = '#FFCD22' + self.ERROR = '#E8E2B7' + self.BAR_BG = '#293134' + self.BAR_FG = '#2F393C' + self.BAR_HLB = '#804000' + self.BAR_HLF = '#E0E2E4' + self.BAR_SLOT = '#E0E2E4' + self.SCROLL = '#434A57' + self.TYPES = '#A082BD' + + + #create and configure root window + self.root = tk.Tk(className='Digger') + self.root.title('Digger') + self.root.geometry("1200x800") + self.add_assets() + + #main frame objects + self.top_bar = tk.Frame(self.root, padx=10, height=50, relief=tk.FLAT, bd=2, bg=self.BAR_BG) + self.body = tk.Frame(self.root, relief=tk.FLAT, bd=0, bg=self.BG) + self.status_bar = tk.Frame(self.root, height="20", relief=tk.FLAT, bg=self.STATUS_BG, takefocus=0) + + #top bar objects + self.btn_back = tk.Button(self.top_bar, image=self.img_back, bd=0, highlightthickness=0, takefocus=1, bg=self.BAR_BG) + self.btn_forward = tk.Button(self.top_bar,image=self.img_forward, bd=0, highlightthickness=0, bg=self.BAR_BG) + self.btn_favorite = tk.Button(self.top_bar,image=self.img_favorite, bd=0, highlightthickness=0, bg=self.BAR_BG) + self.btn_home = tk.Button(self.top_bar, image=self.img_home, bd=0, highlightthickness=0, bg=self.BAR_BG) + self.entry_url = tk.Entry(self.top_bar, selectbackground=self.HLB, selectforeground=self.HLF, highlightcolor=self.FG, highlightbackground=self.BAR_BG, fg=self.BAR_FG, bg=self.BAR_SLOT) + self.btn_menu = tk.Button(self.top_bar, image=self.img_menu, bd=0, highlightthickness=0, bg=self.BAR_BG) + + #body objects + self.scroll_bar = tk.Scrollbar(self.body, bg=self.BAR_BG, bd=0, highlightthickness=0, troughcolor=self.BG, activebackground=self.SCROLL, activerelief=tk.RAISED) + self.site_display = tk.Text(self.body, bg=self.BG, foreground=self.FG, padx=20, pady=20, wrap=tk.WORD, state=tk.DISABLED, spacing2=2, spacing1=2, spacing3=2, yscrollcommand=self.scroll_bar.set, highlightcolor=self.BG, highlightbackground=self.BAR_BG, relief=tk.FLAT) + self.scroll_bar.config(command=self.site_display.yview, width=20, relief=tk.RIDGE) + self.site_display.tag_configure('linkcolor', foreground=self.LINK, spacing1=5, spacing2=5, spacing3=5) + self.site_display.tag_configure('type_tag', background=self.BG, foreground=self.TYPES, spacing2=1, spacing1=1, spacing3=1) + self.site_display.tag_configure('error_text', foreground=self.ERROR, spacing1=5, spacing2=5, spacing3=5) + + #status bar objects + self.status_info = tk.Label(self.status_bar, textvariable=self.message_bar_content, bg=self.STATUS_BG, takefocus=0, fg=self.ACTIVELINK) + + self.pack_geometry() + self.add_status_titles() + self.add_event_listeners() + + #load the home screen + self.load_home_screen(1) + + #-----------Start GUI configuration----------------------- + + def add_event_listeners(self): + buttons = [ + self.btn_back, + self.btn_forward, + self.btn_favorite, + self.btn_home, + self.btn_menu + ] + + for x in buttons: + x.bind('', self.update_status) + x.bind('', self.clear_status) + x.config(activebackground=self.BG) + self.entry_url.bind('', self.execute_address) + self.btn_back.bind('', self.go_back) + self.btn_forward.bind('', self.go_forward) + self.btn_home.bind('', self.load_home_screen) + self.site_display.bind("", lambda event: self.site_display.yview_scroll(-1, 'units')) + self.site_display.bind("", lambda event: self.site_display.yview_scroll(1, 'units')) + self.site_display.bind("", lambda event: self.site_display.focus_set()) + self.entry_url.bind("", lambda event: self.entry_url.focus_set()) + self.root.protocol('WM_DELETE_WINDOW', self.close_window) + + + def pack_geometry(self): + self.top_bar.pack(expand=False,fill=tk.BOTH,side=tk.TOP,anchor=tk.NW) + self.top_bar.pack_propagate(False) + self.body.pack(expand=True,fill=tk.BOTH,side=tk.TOP) + self.status_bar.pack(expand=False,fill=tk.X,side=tk.TOP,anchor=tk.SW) + self.btn_back.pack(side=tk.LEFT, padx=(0,20)) + self.btn_forward.pack(side=tk.LEFT, padx=(0,20)) + self.btn_home.pack(side=tk.LEFT, padx=(0,20)) + self.entry_url.pack(side=tk.LEFT, fill=tk.X, expand=True, ipady=5, ipadx=10) + self.btn_favorite.pack(side=tk.LEFT, padx=(10,10)) + self.btn_menu.pack(side=tk.LEFT) + self.scroll_bar.pack(side=tk.RIGHT,fill=tk.Y) + self.site_display.pack(expand=True, side=tk.TOP, fill=tk.BOTH) + self.status_info.pack(side=tk.LEFT) + + + def add_status_titles(self): + self.btn_back.pop_title = 'Back' + self.btn_forward.pop_title = 'Forward' + self.btn_favorite.pop_title = 'Favorite' + self.btn_home.pop_title = 'Home' + self.btn_menu.pop_title = 'Menu' + + + def add_assets(self): + self.img_back = tk.PhotoImage(file='./images/back.png') + self.img_forward = tk.PhotoImage(file='./images/forward.png') + self.img_favorite = tk.PhotoImage(file='./images/favorite.png') + self.img_home = tk.PhotoImage(file='./images/home.png') + self.img_menu = tk.PhotoImage(file='./images/settings.png') + self.message_bar_content = tk.StringVar() + self.message_bar_content.set('Ready.') + + + # ------------Start navigation methods---------------------------- + + + def execute_address(self, event=False, btn_url=False, history=True): + url = btn_url if btn_url else self.entry_url.get() + if url == 'home': + adjust_history = None if btn_url else 1 + self.load_home_screen(adjust_history) + return True + + parsed_url = self.parser.parse_url(url) + + if not parsed_url: + # To do: build errors class to handle displaying errors + # return errors.url_error + return False + + if parsed_url['type'] == '7': + self.send_to_screen(parsed_url, parsed_url['type']) + + response = self.conn.request(self.parser.resource, self.parser.host, self.parser.filetype, self.parser.port) + + if not response: + # To do: build errors class to handle displaying errors + # return errors.connection_error_NUMBER + return False + + if history: + self.history = self.history[:self.history_location+1] + self.history.append(url) + self.history_location = len(self.history) - 1 + + # Get the data to the screen + self.site_display.focus_set() + self.config["last_viewed"] = url + + self.send_to_screen(self.conn.raw_response, self.conn.filetype) + return True + + + def add_to_history(self, url): + self.history = self.history[:self.history_location+1] + self.history.append(url) + self.history_location = len(self.history) - 1 + + + + def gotolink(self, event, href, tag_name): + element = event.widget + element.tag_config(tag_name, background=self.ACTIVELINK) + element.update_idletasks() # make sure change is visible + time.sleep(.5) # optional delay to show changed text + self.entry_url.delete(0,tk.END) + self.entry_url.insert(tk.END,href) + success = self.execute_address() + element.tag_config(tag_name, background=self.BG) # restore tag text style + element.update_idletasks() + + + def load_home_screen(self,event=None): + with open('./home.gopher','r') as f: + data = f.read() + self.entry_url.delete(0, tk.END) + self.entry_url.insert(tk.END, 'home') + if event is not None: + self.add_to_history('home') + self.send_to_screen(data, '1') + + + def go_back(self, event): + if len(self.history) <= 1 or self.history_location <= 0: + return False + + self.history_location -= 1 + href = self.history[self.history_location] + self.populate_url_bar(href) + self.execute_address(False, href, False) + + + def go_forward(self, event): + if len(self.history) <= 1 or self.history_location >= len(self.history) - 1: + return False + + self.history_location += 1 + href = self.history[self.history_location] + self.populate_url_bar(href) + self.execute_address(False, href, False) + + + #-------------Start view methods---------------- + + + def load_favorites(self): + header = 'i#############\tfalse\tnull.host\t1\r\ni manually edit in go.config.json\tfalse\tnull.host\t1\r\n or add using the favorites button\tfalse\tnull.host\t1\r\ni\tfalse\tnull.host\t1\r\n' + #soon add code to load in favorites here + self.send_to_screen(data=header, clear=False) + + def show_menu(self, data, clear=True): + if not data: + #error handling will go here + return False + + types = { + '0': '( TXT )', + '1': '( MNU )', + '3': '( ERR )', + '7': '( INT )', + '9': '( BIN )', + 'g': '( GIF )', + 'I': '( IMG )', + 'h': '( HTM )', + 'i': '( INF )', + 's': '( SND )', + 'p': '( PNG )' + } + + self.site_display.config(state=tk.NORMAL) + + if clear: + self.site_display.delete(1.0, tk.END) + + link_count = 0 + + for x in data[1:]: + if x['type'] == 'i': + self.site_display.insert(tk.END,' \t\t{}\n'.format(x['description'])) + elif x['type'] == '3': + self.site_display.insert(tk.END,' \t\t{}\n'.format(x['description'])) + elif x['type'] in types: + + # adapted from: + # https://stackoverflow.com/questions/27760561/tkinter-and-hyperlinks + if x['port'] and x['port'][0] != ':': + x['port'] = ':{}'.format(x['port']) + + link = 'gopher://{}{}/{}{}'.format(x['host'], x['port'], x['type'], x['resource']) + tag_name = 'link{}'.format(link_count) + callback = (lambda event, href=link, tag_name=tag_name: self.gotolink(event, href, tag_name)) + hover = (lambda event, href=link, tag_name=tag_name: self.hoverlink(event, href, tag_name)) + clear = (lambda event, tag_name=tag_name: self.clear_status(event, tag_name)) + self.site_display.tag_bind(tag_name, "", callback) + self.site_display.tag_bind(tag_name, "", hover) + self.site_display.tag_bind(tag_name, '', clear) + self.site_display.insert(tk.END, types[x['type']], ('type_tag',)) + self.site_display.insert(tk.END,'\t\t') + self.site_display.insert(tk.END, x['description'], (tag_name,'linkcolor')) + self.site_display.insert(tk.END, '\n') + link_count += 1 + + self.site_display.config(state=tk.DISABLED) + + return True + + + def show_text(self, data): + self.site_display.config(state=tk.NORMAL) + self.site_display.delete(1.0, tk.END) + self.site_display.insert(tk.END, data[:-2]) + self.site_display.config(state=tk.DISABLED) + + + def show_image(self, data): + self.current_image = self.build_image(data) + self.site_display.config(state=tk.NORMAL) + self.site_display.delete(1.0, tk.END) + self.site_display.image_create(tk.END, image = self.current_image) + self.site_display.config(state=tk.DISABLED) + + + def send_to_screen(self, data, itemtype='1', clear=True): + if itemtype == '0': + self.show_text(data) + elif itemtype in ['1','3']: + data = self.parser.parse_menu(data) + self.show_menu(data, clear) + elif itemtype in ['p','I','g']: + self.show_image(data) + elif itemtype == '7': + pass + + + def update_status(self, event, href=False): + if href: + self.message_bar_content.set(href) + else: + self.message_bar_content.set(event.widget.pop_title) + + + def clear_status(self, event, tag_name=False): + if tag_name: + e = event.widget + e.tag_config(tag_name, underline=0) + self.site_display.config(cursor='xterm') + e.update_idletasks() + self.message_bar_content.set('') + + + def populate_url_bar(self, url): + self.entry_url.delete(0, tk.END) + self.entry_url.insert(tk.END, url) + + + def hoverlink(self, event, href, tag_name): + self.update_status(event, href) + e = event.widget + e.tag_config(tag_name, underline=1, foreground=self.LINK) + self.site_display.config(cursor="arrow") + e.update_idletasks() + + def build_image(self, bytes_str): + stream = BytesIO(bytes_str) + pilimage = Image.open(stream) + tkimage = ImageTk.PhotoImage(pilimage) + return tkimage + + + + #--------Start file handling methods------------ + + + def read_config(self, url='./go.config.json'): + if not os.path.isfile(url): + self.create_config() + with open('./go.config.json', 'r') as f: + config = f.read() + config = json.loads(config) + self.config = config + + + def write_config(self, config, url='./go.config.json'): + with open(url, 'w') as f: + data = json.dumps(config) + f.write(data) + + + def create_config(self): + config = {"favorites": [],"last_viewed": None} + self.write_config(config) + + + def close_window(self): + self.write_config(self.config) + self.root.destroy() + + + +if __name__ == '__main__': + app = GUI() + app.root.mainloop() + + diff --git a/home.gopher b/home.gopher new file mode 100644 index 0000000..9abdffe --- /dev/null +++ b/home.gopher @@ -0,0 +1,18 @@ +i false null.host 1 +i██████╗ ██╗ ██╗██████╗ ██████╗ ██████╗ ██╗ ██╗ false null.host 1 +i██╔══██╗██║ ██║██╔══██╗██╔══██╗██╔═══██╗██║ ██║ false null.host 1 +i██████╔╝██║ ██║██████╔╝██████╔╝██║ ██║██║ █╗ ██║ false null.host 1 +i██╔══██╗██║ ██║██╔══██╗██╔══██╗██║ ██║██║███╗██║ false null.host 1 +i██████╔╝╚██████╔╝██║ ██║██║ ██║╚██████╔╝╚███╔███╔╝ false null.host 1 +i╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══╝╚══╝ false null.host 1 +i false null.host 1 +i##########CONTENT PORTALS########## false null.host 1 +i false null.host 1 +1Floodgap / gopher.floodgap.com 70 +1Super Dimensional Fortress / sdf.org 70 +i false null.host 1 +i false null.host 1 +i#############FAVORITES############# false null.host 1 +i manually edit in favorites.json false null.host 1 +i or add using the favorites button false null.host 1 +i false null.host 1 diff --git a/images/back.png b/images/back.png new file mode 100644 index 0000000..59c2546 Binary files /dev/null and b/images/back.png differ diff --git a/images/favorite.png b/images/favorite.png new file mode 100644 index 0000000..13c1a1f Binary files /dev/null and b/images/favorite.png differ diff --git a/images/favorite2.png b/images/favorite2.png new file mode 100644 index 0000000..3fdfcdb Binary files /dev/null and b/images/favorite2.png differ diff --git a/images/forward.png b/images/forward.png new file mode 100644 index 0000000..be4b284 Binary files /dev/null and b/images/forward.png differ diff --git a/images/forward2.png b/images/forward2.png new file mode 100644 index 0000000..cf9e019 Binary files /dev/null and b/images/forward2.png differ diff --git a/images/home.png b/images/home.png new file mode 100644 index 0000000..65e751d Binary files /dev/null and b/images/home.png differ diff --git a/images/home2.png b/images/home2.png new file mode 100644 index 0000000..be67fef Binary files /dev/null and b/images/home2.png differ diff --git a/images/settings.png b/images/settings.png new file mode 100644 index 0000000..dbec038 Binary files /dev/null and b/images/settings.png differ diff --git a/parser.py b/parser.py new file mode 100644 index 0000000..fc08caf --- /dev/null +++ b/parser.py @@ -0,0 +1,54 @@ +import re + +# Handles parsing gopher data: +# URLs, Menus + +class parser: + + def __init__(self): + self.host = None + self.resource = None + self.filetype = None + self.protocol = None + self.port = None + self.menu = None + + def parse_url(self, url): + # Take in a URL and output a dict of the url parts + + regex = r'^(?P(?:gopher:\/\/)?)?(?P[\w\.\d]+)(?P(?::\d+)?)?(?P(?:\/[\dgIp])?)?(?P(?:\/[\w\/\d\-?.]*)?)?$' + + match = re.match(regex, url) + protocol = match.group('protocol') + itemtype = match.group('type') + host = match.group('host') + port = match.group('port') + resource = match.group('resource') + + if not host: + return False + + if not resource: + resource = '/' + + + self.filetype = itemtype[len(itemtype) - 1] if itemtype else '1' + self.protocol = protocol if protocol else 'gopher://' + self.port = int(port[1:]) if port else 70 + self.host = host + self.resource = resource + + return {'host': self.host, 'resource': self.resource, 'type': self.filetype, 'protocol': self.protocol, 'port': self.port} + + + def parse_menu(self, text): + # Take in text from connection and output a list + # w/ objects representing each menu item + + message_list = text.split('\n') + message_list = [x.split('\t') for x in message_list] + message_list = [{'type': x[0][0], 'description': x[0][1:], 'resource': x[1], 'host': x[2], 'port': x[3]} for x in message_list if len(x) >= 4] + self.menu = message_list + + return message_list +