first commit

This commit is contained in:
Fierelier 2021-03-18 16:22:56 +01:00
commit c99e3d002c
13 changed files with 393 additions and 0 deletions

2
BirdyNet.ini Normal file
View File

@ -0,0 +1,2 @@
[default]
home = https://fier.me

151
BirdyNet.py Normal file
View File

@ -0,0 +1,151 @@
#!/usr/bin/env python3
import sys
oldexcepthook = sys.excepthook
def newexcepthook(type,value,traceback):
oldexcepthook(type,value,traceback)
#input("Press ENTER to quit.") # - this causes issues with Qt's input hook
sys.excepthook = newexcepthook
import os
p = os.path.join
pUp = os.path.dirname
s = False
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
s = os.path.realpath(sys.executable)
else:
s = os.path.realpath(__file__)
sp = pUp(s)
# script start
import qtpy
from qtpy.QtGui import *
from qtpy.QtWidgets import *
from qtpy.QtCore import *
from qtpy.QtMultimedia import QSound
import configparser
import json
os.chdir(sp)
distro = os.path.splitext(s.rsplit(os.path.sep)[-1])[0]
def runCode(str, lcs = False, description = "loose-code"):
if lcs == False: lcs = {}
code = compile(str,description,"exec")
exec(code,globals(),lcs)
return lcs
def runScript(sf, lcs = False):
if lcs == False: lcs = {}
with open(sf) as script:
runCode(script.read(),lcs,sf)
return lcs
def getDataDir(appName):
basePath = False
if sys.platform.startswith("win"):
basePath = os.environ["appdata"]
elif sys.platform.startswith("linux"):
basePath = os.environ["XDG_DATA_HOME"]
elif sys.platform.startswith("darwin"):
basePath = os.path.join(os.environ["home"],"Library","Application Support")
if not basePath: raise
return p(basePath,appName)
def prettyJson(ls):
return json.dumps(ls,indent=2)
class browserWindow(QMainWindow):
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
self.cTitle = "Hello World - " +distro
self.cWidth = 640
self.cHeight = 480
self.setWindowTitle(self.cTitle)
self.resize(self.cWidth,self.cHeight)
self.cDocumentInfo = {
"url": "",
"body": "",
"headers": {},
# additional info:
"baseUrl": ""
}
self.cCreateElements()
def cCreateElements(self):
self.cMenuBar = self.menuBar()
self.cFileMenu = self.cMenuBar.addMenu("File")
self.cViewMenu = self.cMenuBar.addMenu("View")
self.cUrlBar = QLineEdit(config["default"]["home"],self)
self.cButtonGo = QPushButton("Go",self)
self.cButtonGo.clicked.connect(self.cNavigate)
self.cDoc = browserDoc(self)
self.cResizeElements()
self.show()
self.cNavigate()
def cResizeElements(self):
barSize = 22
mb = self.cMenuBar.height()
self.cUrlBar.move(0,mb)
self.cUrlBar.resize(self.cWidth - barSize,barSize)
self.cButtonGo.move(self.cWidth - barSize,mb)
self.cButtonGo.resize(barSize,barSize)
self.cDoc.move(0,mb + barSize)
self.cDoc.resize(self.cWidth,self.cHeight - barSize - mb)
def resizeEvent(self,event):
self.cWidth = self.width()
self.cHeight = self.height()
self.cResizeElements()
def cNavigate(self,event = None):
try:
print(prettyJson(parseUrl(self.cUrlBar.text())))
response = downloadPage(self.cUrlBar.text())
infoFetcher(response)
self.cDocumentInfo = response
self.cDoc.cRenderHtml(response["body"].decode("utf-8",errors="ignore"))
print(prettyJson(response["headers"]))
except Exception as e:
self.cDoc.cRenderHtml(str(e))
raise
def main():
# load program default addons
addonDir = p(sp,"addons")
for root,dirs,files in os.walk(addonDir):
for file in files:
ffile = p(root,file)
lfile = ffile.replace(addonDir + os.path.sep,"",1)
if lfile[0] == "-": continue
print("Running addon: " +lfile)
runScript(ffile)
break
global dataDir
dataDir = getDataDir(distro)
global config
config = configparser.ConfigParser()
# read program default config
config.read(distro + ".ini")
# read user config
if os.path.isfile(p(dataDir,"config.ini")):
config.read(p(dataDir,"config.ini"))
app = QApplication(sys.argv)
window = browserWindow()
windowt = browserWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

0
about/blank.html Normal file
View File

View File

@ -0,0 +1,39 @@
global browserDoc
class browserDoc(QTextBrowser):
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
self.setOpenLinks(False)
self.anchorClicked.connect(self.cAnchorClicked)
def cRenderHtml(self,html):
self.clear()
self.insertHtml(html.replace("<img","<dummy")) # half-assed slowdown fix
self.update()
def cAnchorClicked(self,url):
url = str(url.toEncoded(),"utf-8")
browserWindow = self.parent()
curUrl = browserWindow.cDocumentInfo["url"]
curUrlParsed = parseUrl(curUrl)
urlParsed = parseUrl(url)
#print("navigating from: " +curUrl)
#print("to: " +url)
if urlParsed["protocol"] == "": # is relative
if url[0] == "#":
curUrlParsed["anchor"] = url[1:]
url = unparseUrl(curUrlParsed)
elif url[:2] == "//":
url = curUrlParsed["protocol"] + ":" + url
elif url[0] == "/":
urlParsed["protocol"] = curUrlParsed["protocol"]
urlParsed["domain"] = curUrlParsed["domain"]
urlParsed["path"] = urlParsed["path"][1:]
url = unparseUrl(urlParsed)
else:
urlParsed["protocol"] = curUrlParsed["protocol"]
urlParsed["domain"] = curUrlParsed["domain"]
while len(curUrlParsed["path"]) > 0 and curUrlParsed["path"][-1] == "/": curUrlParsed["path"] = curUrlParsed["path"][:-1]
urlParsed["path"] = curUrlParsed["path"] + "/" + url
url = unparseUrl(urlParsed)
browserWindow.cUrlBar.setText(url)
browserWindow.cNavigate()

View File

@ -0,0 +1,27 @@
global urllib
import urllib.request
global downloadPage
def downloadPage(url,headers = False):
if not headers: headers = {}
request = urllib.request.Request(url,headers=headers)
response = None
try:
response = urllib.request.urlopen(request)
except urllib.error.HTTPError as e:
response = e
# process headers
headers = response.getheaders()
headersOut = {}
for hl in headers:
headersOut[hl[0]] = hl[1]
out = {
"url": url,
"headers": headersOut,
"body": response.read()
}
return out

173
addons/0.utils.py Normal file
View File

@ -0,0 +1,173 @@
global unparseUrl
def unparseUrl(parsedUrl):
url = ""
if parsedUrl["protocol"] != "":
url = parsedUrl["protocol"] + "://" +parsedUrl["domain"]
if parsedUrl["path"] != "":
if url != "":
url += "/" + parsedUrl["path"]
else:
url = parsedUrl["path"]
if parsedUrl["parameters"] != "":
first = True
for parameter in parsedUrl["parameters"]:
if first == False:
url += "&"
else:
first = True
url += parameter[0]
if parameter[1] != None:
url += "=" + parameter[1]
if parsedUrl["anchor"] != "":
url += "#" + parsedUrl["anchor"]
return url
global parseUrl
def parseUrl(url):
out = {
"protocol": "",
"domain": "",
"path": "",
"parameters": [],
"anchor": ""
}
# get anchor
if True:
surl = url.split("#",1)
if len(surl) > 1:
url = surl[0]
out["anchor"] = surl[1]
# get parameters
if True:
surl = url.split("?",1)
if len(surl) > 1:
url = surl[0]
argListDumb = surl[1].split("&")
for arg in argListDumb:
argSplit = arg.split("=",1)
argKey = argSplit[0]
argValue = None
if len(argSplit) > 1:
argValue = argSplit[1]
out["parameters"].append([argKey,argValue])
# get protocol
hasProtocol = False
for s in url:
if s == "/":
break
if s == ":":
hasProtocol = True
break
if hasProtocol:
surl = url.split(":",1)
out["protocol"] = surl[0]
url = surl[1]
while len(url) > 0 and url[0] == "/": url = url[1:]
# get path and domain
if hasProtocol == False:
out["path"] = url
else:
surl = url.split("/",1)
out["domain"] = surl[0]
if len(surl) > 1:
out["path"] = surl[1]
return out
global urlJoin
def urlJoin(*args):
first = True
outUrl = ""
for arg in args:
if first == True:
outUrl = arg
first = False
continue
while len(arg > 0) and arg[0] == "/": arg = arg[1:]
while len(arg > 0) and arg[-1] == "/": arg = arg[:-1]
outUrl = outUrl + "/" + arg
return outUrl
global infoFetcher
def infoFetcher(info):
''' if "Content-Base" in info["headers"]:
info["baseUrl"] = info["headers"]["Content-Base"]
return
elif "Content-Location" in info["headers"]:
info["baseUrl"] = "/"
return '''
''' https://www.w3.org/TR/WD-html40-970917/htmlweb.html
User agents should calculate the base URL for resolving relative URLs according to the [RFC1808]. The following is a summary of how [RFC1808] applies to HTML. User agents should calculate the base URL according to the following precedences (highest priority to lowest):
1. The base URL is set by the BASE element. (TO BE IMPLEMENTED)
2. The base URL is given by an HTTP header (see [RFC2068]). (TO BE IMPLEMENTED)
3. By default, the base URL is that of the current document. (TO BE IMPLEMENTED)
Additionally, the OBJECT and APPLET elements define attributes that take precedence over the value set by the BASE element. Please consult the definitions of these elements for more information about URL issues specific to them.
'''
''' https://tools.ietf.org/html/rfc2068
14.11 Content-Base
The Content-Base entity-header field may be used to specify the base
URI for resolving relative URLs within the entity. This header field
is described as Base in RFC 1808, which is expected to be revised.
Content-Base = "Content-Base" ":" absoluteURI
If no Content-Base field is present, the base URI of an entity is
defined either by its Content-Location (if that Content-Location URI
is an absolute URI) or the URI used to initiate the request, in that
order of precedence. Note, however, that the base URI of the contents
within the entity-body may be redefined within that entity-body.
14.15 Content-Location
The Content-Location entity-header field may be used to supply the
resource location for the entity enclosed in the message. In the case
where a resource has multiple entities associated with it, and those
entities actually have separate locations by which they might be
individually accessed, the server should provide a Content-Location
for the particular variant which is returned. In addition, a server
SHOULD provide a Content-Location for the resource corresponding to
the response entity.
Content-Location = "Content-Location" ":"
( absoluteURI | relativeURI )
If no Content-Base header field is present, the value of Content-
Location also defines the base URL for the entity (see section
14.11).
The Content-Location value is not a replacement for the original
requested URI; it is only a statement of the location of the resource
corresponding to this particular entity at the time of the request.
Future requests MAY use the Content-Location URI if the desire is to
identify the source of that particular entity.
A cache cannot assume that an entity with a Content-Location
different from the URI used to retrieve it can be used to respond to
later requests on that Content-Location URI. However, the Content-
Location can be used to differentiate between multiple entities
retrieved from a single requested resource, as described in section
13.6.
If the Content-Location is a relative URI, the URI is interpreted
relative to any Content-Base URI provided in the response. If no
Content-Base is provided, the relative URI is interpreted relative to
the Request-URI.
'''

BIN
assets/BirdyNet full.xcf Normal file

Binary file not shown.

Binary file not shown.

BIN
assets/BirdyNet icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
assets/BirdyNet icon.xcf Normal file

Binary file not shown.

BIN
assets/BirdyNet-XP.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
assets/BirdyNet.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

1
assets/Credits.txt Normal file
View File

@ -0,0 +1 @@
The logo is based on the bird ref sheet by Lainn: https://www.furaffinity.net/view/23640147/ - If you make your own version of the browser, make sure to include this file to credit the artist.