first commit
This commit is contained in:
commit
c99e3d002c
2
BirdyNet.ini
Normal file
2
BirdyNet.ini
Normal file
@ -0,0 +1,2 @@
|
||||
[default]
|
||||
home = https://fier.me
|
151
BirdyNet.py
Normal file
151
BirdyNet.py
Normal 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
0
about/blank.html
Normal file
39
addons/0.documentViewer.QTextBrowser.py
Normal file
39
addons/0.documentViewer.QTextBrowser.py
Normal 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()
|
27
addons/0.pageDownloader.py
Normal file
27
addons/0.pageDownloader.py
Normal 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
173
addons/0.utils.py
Normal 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
BIN
assets/BirdyNet full.xcf
Normal file
Binary file not shown.
BIN
assets/BirdyNet icon sizes.xcf
Normal file
BIN
assets/BirdyNet icon sizes.xcf
Normal file
Binary file not shown.
BIN
assets/BirdyNet icon.png
Normal file
BIN
assets/BirdyNet icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 59 KiB |
BIN
assets/BirdyNet icon.xcf
Normal file
BIN
assets/BirdyNet icon.xcf
Normal file
Binary file not shown.
BIN
assets/BirdyNet-XP.ico
Normal file
BIN
assets/BirdyNet-XP.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
BIN
assets/BirdyNet.ico
Normal file
BIN
assets/BirdyNet.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 181 KiB |
1
assets/Credits.txt
Normal file
1
assets/Credits.txt
Normal 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.
|
Loading…
Reference in New Issue
Block a user