Merge branch 'master' of https://github.com/sloumdrone/digger into develop

This commit is contained in:
sloumdrone 2018-10-29 19:36:36 -07:00
commit 11fccdbef6
14 changed files with 518 additions and 427 deletions

View File

@ -1,23 +1,23 @@
# Digger
# Burrow
A client/browser that accesses _gopherspace_. It is under current early stage development.
![Digger browser](http://brianmevans.com/files/digger.png "Digger v0.1.5 main window")
![Burrow browser](http://brianmevans.com/files/digger.png "Burrow v0.1.5 main window")
## Gopher
[_Gopher_](https://en.wikipedia.org/wiki/Gopher_(protocol)) is a communications protocol that, in the early 90s, competed (briefly) with what became the world wide web. _Gopher_ serves up files and text based menus. As such, it is much lighter weight than HTML documents and the like served over http. Due to its text based nature it also has the benefit of being reliable in its visual output and style, and for being relatively accessible.
## Digger
The following is a list of current and future Digger features:
## Browser Features
The following is a list of current and future Burrow features:
- Tk based GUI
- Back button, move backwards in session history
- Forward button, move forward in session history
- Refresh button, will be replaced by a favorite button __(non-functional)__
- Home, shows favorites and is a start page __(non-functinoal)__
- Home, shows favorites and is a start page
- An address bar, on _ENTER_ submits a request for a _gopher_ page
- Settings button __(non-functional)__
- A display area for the requested information
- Scroll bar, scrolling works, but no bar is present __(non-functional)__
- Scroll bar
- A status bar to display various information
- Links to menus and files
- On hover, link destination shows in status bar
@ -30,7 +30,7 @@ The following is a list of current and future Digger features:
- Menus Pages and files
- Menus display correctly and quickly
- Text files display correctly and quickly
- Images files __(non-functional)__
- Images files
- Sound files __(non-functional)__
- Binary files __(non-functional)__
- HTML files, will open in default browser __(non-functional)__
@ -56,19 +56,22 @@ The following is a list of current and future Digger features:
## Installation
Digger requires python3 to be installed on the system prior to running.
# can be prefixed with python3
# but not required if you have python3 installed
/path/to/digger.py
Burrow requires python3 to be installed on the system prior to running.
Until some bundling/setup file creation occurs, you will also need to add:
pip3 install pillow
On some linux distributions an additional package for tkinter may be required.
If you get a console error complaining about tkinter try the following (or equivalent for your package manager):
sudo apt-get python3-tk
Once up an running you should be good to go.
sudo apt-get install python3-tk
sudo apt-get install python3-pil.imagetk
## Distribution
Digger's primary system target is linux. Once a version 1.0 is reached the plan is to distribute primarily through [Snapcraft](https://snapcraft.io/) packages.
Burrow's primary system target is linux. Once a version 1.0 is reached the plan is to distribute primarily through [Snapcraft](https://snapcraft.io/) packages.
Some version of windows executable may come along as well, depending on configurability of build tools (py2exe, freeze, etc) for windows executables and time.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

13
burrow.py Normal file
View File

@ -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()

106
conn.py
View File

@ -1,106 +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': '(TLNET)',
'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(30.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', errors='ignore')
try:
raw_data = r.read()
except UnicodeDecodeError:
raw_data = 'iError decoding server response :(\tfalse\tnull.host\t1'
try:
data = raw_data.decode('utf-8','ignore')
except:
data = raw_data
self.raw_request = data
if itemtype[1] == '1':
#handle menus
self.text_output = self.gopher_to_text(self.raw_request)
elif itemtype[1] == '0':
#handle text files
self.text_output = [self.raw_request]
self.text_output.insert(0,itemtype[1])
s.close()
return self.text_output
def gopher_to_text(self, message):
message = message.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, execute=True):
regex = r'^(?P<protocol>(?:gopher:\/\/)?)?(?P<host>[\w\.\d]+)(?P<port>(?::\d+)?)?(?P<type>(?:\/\d)?)?(?P<resource>(?:\/[\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:]
if execute:
self.make_connection(resource, host, itemtype, port)
else:
return {'host': host, 'resource': resource, 'type': itemtype}
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')

29
connect.py Normal file
View File

@ -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}

300
digger.py
View File

@ -1,300 +0,0 @@
#!/usr/bin/env python3
import tkinter as tk
from conn import Tunnel as go
import time
import sys
import json
import os.path
class GUI:
def __init__(self):
self.history = []
self.history_location = -1
self.message_bar_content = ''
self.config = None
self.read_config()
#colors
self.FG = 'black'
self.BG = 'white'
self.LINK = 'blue'
self.ACTIVELINK = 'red'
self.HLB = '#e37800'
self.HLF = 'white'
self.STATUS = 'silver'
self.ERROR = 'red'
#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_favorite = tk.Button(self.top_bar,image=self.img_favorite, 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.scroll_bar = tk.Scrollbar(self.body)
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, yscrollcommand=self.scroll_bar.set)
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)
self.site_display.tag_configure('type_tag', background=self.FG, foreground=self.BG, spacing2=0, spacing1=0)
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, takefocus=0)
self.pack_geometry()
self.add_status_titles()
self.add_event_listeners()
#load the home screen
self.load_home_screen()
#---------------------------------------------------
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('<Enter>', self.update_status)
x.bind('<Leave>', self.clear_status)
self.entry_url.bind('<Return>', self.execute_address)
self.btn_back.bind('<Button-1>', self.go_back)
self.btn_forward.bind('<Button-1>', self.go_forward)
self.btn_home.bind('<Button-1>', self.load_home_screen)
self.site_display.bind("<Up>", lambda event: self.site_display.yview_scroll(-1, 'units'))
self.site_display.bind("<Down>", lambda event: self.site_display.yview_scroll(1, 'units'))
self.site_display.bind("<Button-1>", lambda event: self.site_display.focus_set())
self.entry_url.bind("<Button-1>", 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=10, 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='./btn_back.png')
self.img_forward = tk.PhotoImage(file='./btn_forward.png')
self.img_favorite = 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 False
else:
url = self.entry_url.get()
if url == 'home':
self.load_home_screen()
return True
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)
self.config["last_viewed"] = url
return True
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)
self.site_display.config(cursor="arrow")
e.update_idletasks()
def load_home_screen(self,event=False):
with open('./home.gopher','r') as f:
data = f.read()
parser = go()
data = parser.gopher_to_text(data)
data.insert(0,'1')
self.entry_url.delete(0, tk.END)
self.entry_url.insert(tk.END, 'home')
self.send_to_screen(data, True)
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(header, False)
def send_to_screen(self, data, clear=True):
link_count = 0
self.site_display.config(state=tk.NORMAL)
if clear:
self.site_display.delete(1.0, tk.END)
types = {
'0': '( TEXT )',
'1': '( MENU )',
'2': None,
'3': '( ERROR)',
'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] in ['1','3']:
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']))
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, "<Button-1>", callback)
self.site_display.tag_bind(tag_name, "<Enter>", hover)
self.site_display.tag_bind(tag_name, '<Leave>', 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)
self.site_display.config(cursor='xterm')
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)
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()

View File

@ -1 +0,0 @@
{"favorites": [], "last_viewed": "gopher://gopher.club:70/1/phlogs/"}

399
gui.py Normal file
View File

@ -0,0 +1,399 @@
#!/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
import webbrowser as wb
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='Burrow')
self.root.title('Burrow')
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('<Enter>', self.update_status)
x.bind('<Leave>', self.clear_status)
x.config(activebackground=self.BG)
self.entry_url.bind('<Return>', self.execute_address)
self.btn_back.bind('<Button-1>', self.go_back)
self.btn_forward.bind('<Button-1>', self.go_forward)
self.btn_home.bind('<Button-1>', self.load_home_screen)
self.site_display.bind("<Up>", lambda event: self.site_display.yview_scroll(-1, 'units'))
self.site_display.bind("<Down>", lambda event: self.site_display.yview_scroll(1, 'units'))
self.site_display.bind("<Button-1>", lambda event: self.site_display.focus_set())
self.entry_url.bind("<Button-1>", 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):
if href[:4] == 'http':
wb.open(href, 2, True)
return True
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'])
if x['type'] == 'h':
link = '{}/{}'.format(x['host'], x['resource'])
link = 'http://{}'.format(link.replace('//','/'))
else:
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, "<Button-1>", callback)
self.site_display.tag_bind(tag_name, "<Enter>", hover)
self.site_display.tag_bind(tag_name, '<Leave>', 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()

View File

@ -1,10 +1,10 @@
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██╔══██╗██║ ██║██╔══██╗██╔══██╗██╔═══██╗██║ ██║ 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

54
parser.py Normal file
View File

@ -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<protocol>(?:gopher:\/\/)?)?(?P<host>[\w\.\d]+)(?P<port>(?::\d+)?)?(?P<type>(?:\/[\dgIp])?)?(?P<resource>(?:\/[\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