Free Competitors integration

This commit is contained in:
BlenderDumbass 2024-12-07 19:56:31 +02:00
parent f8ed6e56c8
commit 49cea62407
6 changed files with 6518 additions and 18 deletions

6139
fcdata.json Normal file

File diff suppressed because it is too large Load diff

BIN
icons/fc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -34,6 +34,7 @@ def Set():
print(clr["tdyl"]+"--domain"+clr["norm"]+" - Let the server know the clearnet domain.")
print(clr["tdyl"]+"--tor"+clr["norm"]+" - Let the server know the tor domain.")
print(clr["tdyl"]+"--email"+clr["norm"]+" - Set Email SMTP account for internal automatic messages.")
print(clr["tdyl"]+"--account"+clr["norm"]+" - Set the Main Account, for the footer and stuff.")
print(clr["tdyl"]+"--port"+clr["norm"]+" - Set port where to run the website.")
print(clr["tdyl"]+"--css"+clr["norm"]+" - Set a CSS file.")
print(clr["tdyl"]+"--css_edit"+clr["norm"]+" - edit a CSS file.")
@ -44,6 +45,7 @@ def Set():
print()
print(clr["tdyl"]+"--editor"+clr["norm"]+" - Set editor. Default nano.")
print()
print(clr["tdyl"]+"--fc_api"+clr["norm"]+" - API for software Free Competitors search.")
def Accounts():

View file

@ -173,6 +173,10 @@ def validate(cookie):
return Accounts[account]
return {}
def isHuman(cookie):
return ( cookie in ProbablyHumanCookies and cookie in KnownCookies ) or validate(cookie)
def moderates(moderator, user):
Accounts = accounts()
@ -327,7 +331,36 @@ def previewsToSize(text):
# A thousand character article is about 4 articles.
return len(text)/2200
def mastohead(mastodon):
try:
if "/" in mastodon:
mastodon = mastodon.replace("https://", "").replace("http://", "")
mastodon = mastodon.split("/")[1]+"@"+mastodon.split("/")[0]
except: pass
if not mastodon.startswith("@"): mastodon = "@"+mastodon
return mastodon
def mastolink(mastodon):
return "https://"+mastodon[1:].split("@")[1]+"/@"+mastodon[1:].split("@")[0]
def isFreeSoftware(app):
with open("fcdata.json") as json_file:
licenses = json.load(json_file).get("licenses",[])
if "licenses" in app and app["licenses"]:
all_licenses = licenses
for al in all_licenses: # Making longer loop once
for l in app["licenses"]:
if l in [al.get("licenseId",""),al.get("name","")]\
and al.get("isFsfLibre", False):
return True
###
def MainPage(server):
@ -402,7 +435,7 @@ def MainPage(server):
html = html + '</div>'
html = html + Footer()
html = html + Footer(server)
html = html + LoginButton(server)
@ -483,6 +516,7 @@ def ListPage(server, tab):
if To < len(Articles)-1:
html = html + Button(str(page+1), tab+"?page="+str(page+1), "right")
html = html + Footer(server)
html = html + LoginButton(server)
send(server, html, 200)
@ -728,7 +762,7 @@ def ArticlePage(server, url):
if str(n) == comment_edit and moderates(user.get("username"), comment.get("username")):
html = html + CommentEditInput(server, comment, url, n, user)
else:
html = html + Comment(comment, url, n, user)
html = html + Comment(comment, url, n, user, comments=comments)
# Needed to extend the suggestion for pages with many comments
commentsTextLength += previewsToSize(comment.get("text", ""))
@ -775,6 +809,7 @@ def ArticlePage(server, url):
html = html + ""
html = html + Footer(server)
html = html + LoginButton(server)
send(server, html, 200)
@ -814,7 +849,7 @@ def AccountPage(server, account):
html = html + '</center>'
# Protecting emails and stuff from scrubbers
if server.cookie in ProbablyHumanCookies and server.cookie in KnownCookies:
if isHuman(server.cookie):
# Website
@ -849,12 +884,9 @@ def AccountPage(server, account):
# It could be mastodon url and not handle.
try:
if "/" in mastodon:
mastodon = mastodon.replace("https://", "").replace("http://", "")
mastodon = mastodon.split("/")[1]+"@"+mastodon.split("/")[0]
if not mastodon.startswith("@"): mastodon = "@"+mastodon
mastolink = "https://"+mastodon[1:].split("@")[1]+"/@"+mastodon[1:].split("@")[0]
mastodon = mastohead(mastodon)
mastolink = mastolink(mastodon)
html = html + '<center>'
html = html + '<img style="vertical-align: middle" src="/icon/mastodon">'
@ -1530,10 +1562,14 @@ def EditorPage(server):
###
def Button(text, link, icon="", image=""):
def Button(text, link, icon="", image="", rel=""):
# rel tag is useful for mastodon website verifications
reltext = ""
if rel: reltext = 'rel="'+rel+'" '
html = """
<a class="button" href=\""""+link+"""">"""
<a """+reltext+"""class="button" href=\""""+link+"""">"""
if icon:
html = html + """
<img alt="[icon """+icon+"""]" src="/icon/"""+icon+"""" style="vertical-align: middle">"""
@ -1590,7 +1626,7 @@ def ArticlePreview(article, Tabs, cookie=""):
return html
def Footer():
def Footer(server):
html = """
@ -1599,6 +1635,21 @@ def Footer():
"""
html = html + Button("Powered with BDServer", "https://codeberg.org/blenderdumbass/BDServer/", "codeberg")
config = Set.Load()
account = config.get("main_account", "")
Accounts = accounts()
if account in Accounts:
if isHuman(server.cookie):
email = Accounts[account].get("email")
if email:
html = html + Button("Contact Admin", "mailto:"+email, "frase")
mastodon = Accounts[account].get("mastodon")
if mastodon:
html = html + Button("Mastodon", mastolink(mastohead(mastodon)), "mastodon", rel="me")
html = html + """
@ -1862,7 +1913,7 @@ def CommentEditInput(server, comment, url, n=0, user=None, request=None):
return html
def Comment(comment, url, n=0, user=None, request=False):
def Comment(comment, url, n=0, user={}, request=False, comments=[]):
if not request:
html = '<div class="dark_box" id="comment_'+str(n)+'">'
@ -1870,7 +1921,12 @@ def Comment(comment, url, n=0, user=None, request=False):
html = '<div class="dark_box" id="request_'+str(n)+'">'
account = comment.get("username", "Anonymous User")
html = html + User(account) + '<br>\n'
html = html + User(account)
if not request:
html = html + ' <a href="#comment_'+str(n)+'">c:'+str(n)+'</a>'
html = html + '<br>\n'
if request:
html = html + '<center><sup><b> &nbsp; Pending Approval &nbsp; </b></sup></center><br>'
@ -1883,7 +1939,7 @@ def Comment(comment, url, n=0, user=None, request=False):
html = html + warning
html = html + '</summary>'
html = html + markdown.convert(Safe(comment.get("text", "")), False)+'<br>'
html = html + markdown.convert(Safe(comment.get("text", "")), False, comments={"url":url,"comments":comments})+'<br>'
if warning:
html = html + '</details>'
@ -2016,6 +2072,189 @@ def Error(server, text="Some Error Happened."):
send(server, html, 501)
def FreeCompetitor(free, nonfree):
html = """
<div class="article_box">
"""
html = html + '<h1><img alt="[icon fc]" src="/icon/fc" style="vertical-align: middle">'
html = html + free.get("names", ["Software"])[0]+'</h1>'
icon = free.get("links", {}).get("icon", "")
if icon:
html = html + '<center><img alt="[thumbnail]" style="min-width:80%;" src="'+icon+'"></center>'
text = '<br><br>To replace <b>'+nonfree.get("names", ["The Software"])[0]+'</b>'
text = text +' you can use <b>'+free.get("names", ["Software"])[0]+'</b>'
text = text +' since it <a href="https://www.gnu.org/philosophy/free-sw.html">respects the user\'s freedom</a> and '
features = []
for feature in free.get("generic_name", []):
if feature.startswith("*"):
features.append(feature[1:])
oformats = []
for f in free.get("formats_read", []):
if f.startswith("*"):
oformats.append(f[1:])
sformats = []
for f in free.get("formats_write", []):
if f.startswith("*"):
sformats.append(f[1:])
onetworks = []
for f in free.get("networks_read", []):
if f.startswith("*"):
onetworks.append(f[1:])
snetworks = []
for f in free.get("networks_write", []):
if f.startswith("*"):
snetworks.append(f[1:])
if features:
text = text + 'is also a'
for n, feature in enumerate(features):
AND = " "
if n != 0: AND = ', '
if n and n == len(features)-1: AND = ' and '
text = text + AND + feature + " software"
text = text +"."
if any((oformats, sformats, onetworks, snetworks)):
text = text + "<br><br>Also it "
if oformats:
text = text + 'reads '+str(len(oformats))+' of the same formats as '
text = text + '<b>'+nonfree.get("names", ["The Software"])[0]+'</b>'
text = text + ' such as: '
for n, f in enumerate(oformats):
if n == 4: break
AND = " "
if n: AND = ', '
if n and ( n == len(oformats)-1 or n == 3 ): AND = ' and '
text = text + AND + f.upper()
if sformats:
text = text + ' and '
else:
text = text + '.'
if sformats:
text = text + 'saves to '+str(len(sformats))+' of the same formats as '
text = text + '<b>'+nonfree.get("names", ["The Software"])[0]+'</b>'
text = text + ' such as: '
for n, f in enumerate(sformats):
if n == 4: break
AND = " "
if n: AND = ', '
if n and ( n == len(sformats)-1 or n == 3 ): AND = ' and '
text = text + AND + f.upper()
text = text + '.'
if any((oformats, sformats)) and any((onetworks, snetworks)):
text = text + "<br><br>Also it "
if onetworks:
text = text + 'can access data from '+str(len(onetworks))+' of the same network protocols as '
text = text + '<b>'+nonfree.get("names", ["The Software"])[0]+'</b>'
text = text + ' such as: '
for n, f in enumerate(onetworks):
if n == 4: break
AND = " "
if n: AND = ', '
if n and ( n == len(onetworks)-1 or n == 3 ): AND = ' and '
text = text + AND + f.upper()
if snetworks:
text = text + ' and '
else:
text = text + '.'
if snetworks:
text = text + 'can publish data to '+str(len(snetworks))+' of the same network protocols as '
text = text + '<b>'+nonfree.get("names", ["The Software"])[0]+'</b>'
text = text + ' such as: '
for n, f in enumerate(snetworks):
if n == 4: break
AND = " "
if n: AND = ', '
if n and ( n == len(snetworks)-1 or n == 3 ): AND = ' and '
text = text + AND + f.upper()
text = text + '.'
text = text + '<br><br>'
if free.get("issues", []):
text = text + 'Unfortunately though '
text = text +'<b>'+free.get("names", ["Software"])[0]+'</b>'
text = text +' is not without issues: <br><center>'
for issue in free.get("issues", []):
text = text + '<div class="toot">'
text = text + '<img alt="[icon bug]" src="/icon/bug" style="vertical-align: middle"> '+issue+"<br>"
text = text + '</div><br>'
text = text + '</center>'
text = text + '<center>'
for link in free.get("links", {}):
if link == "icon":
continue
name = link.replace("git", "source code").replace("fsd", "FSD")
if name.lower() == name:
name = name[0].upper()+name[1:]
text = text + Button(name, free["links"][link], "internet")
text = text + '</center>'
html = html + text
html = html + '</div>'
html = html + '<br><br>'
return html
###
@ -2345,7 +2584,7 @@ def UpdatePublicationRights(server):
def DoComment(server):
# Limiting bots from commenting
if not server.cookie:
if not isHuman(server.cookie):
AccessDenied(server)
return
@ -2880,6 +3119,9 @@ def Search(server):
Articles = allArticles()
Tabs = tabs()
config = Set.Load()
# Free Competitors support
FreeCompetitors = config.get("free_competitors", "")
# Generating <head>
html = head(title = "Search",
@ -2904,11 +3146,13 @@ def Search(server):
searchpost = server.parsed.get("post",[""])[0]
searchdescription = server.parsed.get("description",[""])[0]
searchcomments = server.parsed.get("comments",[""])[0]
searchfc = server.parsed.get("fc",[""])[0]
# Supporting legacy search links
if not any([searchtitle,
searchauthor,
searchpost,
searchfc,
searchdescription,
searchcomments
]):
@ -2916,11 +3160,15 @@ def Search(server):
searchpost = True
searchdescription = True
searchcomments = True
searchfc = True
checkedtitle = ""
if searchtitle: checkedtitle = " checked "
checkedfc = ""
if searchfc: checkedfc = " checked "
checkedauthor = ""
if searchauthor: checkedauthor = " checked "
@ -2932,6 +3180,8 @@ def Search(server):
checkedcomments = ""
if searchcomments: checkedcomments = " checked "
html = html + """
@ -2948,6 +3198,15 @@ def Search(server):
</button>
<br>
"""
if FreeCompetitors:
html = html + """
<div class="button">
<input type="checkbox" """+checkedfc+""" name="fc"> Free Software
</div>"""
html = html + """
<div class="button">
<input type="checkbox" """+checkedtitle+""" name="title"> Title
@ -3045,7 +3304,16 @@ def Search(server):
if To < len(list(counted))-1:
html = html + Button(str(page+1), urlNoPage+"&page="+str(page+1), "right")
if searchfc:
html = html + '<center><div class="toot">'
html = html + ' The Free Software Search is Powered by '
html = html + Button("Free Competitors", "https://notabug.org/jyamihud/FreeCompetitors", "fc")
html = html + '</div></center>'
html = html + """
<br>
<br>
@ -3055,7 +3323,33 @@ def Search(server):
<div class="flexity">
"""
# Free Competitors Search
if searchfc:
try:
fcdata = API.Get(FreeCompetitors+"/json/"+text.replace(" ", "+"))
except:
fcdata = {}
fcMatch = 0.5
# If we found a match in software
if fcdata.get("found", {}).get("match", 0) >= fcMatch:
fcSearchMatch = max( x[0] for x in fcdata.get("suggestions",[])) * 0.2
for soft in fcdata.get("suggestions",[]):
if fcSearchMatch > soft[0] or not isFreeSoftware(soft[1]):
continue
if soft[1].get("names", [""])[0] in fcdata.get("found", {}).get("data", {}).get("names", []):
continue
html = html + FreeCompetitor(soft[1], fcdata.get("found", {}).get("data",{}))
rendered = 0
for n, article in enumerate(counted):

View file

@ -66,6 +66,16 @@ def Set():
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Title Wasn't Specified!")
print('Use: $ python3 run.py --set --title "My Website"')
if "--account" in sys.argv:
try:
account = sys.argv[ sys.argv.index("--account") + 1]
if "--" in account: 1/0 # Failing this for the error message.
MainAccount(account)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Account Wasn't Specified!")
print('Use: $ python3 run.py --set --account blenderdumbass')
if "--tagline" in sys.argv:
try:
tagline = sys.argv[ sys.argv.index("--tagline") + 1]
@ -172,6 +182,17 @@ def Set():
if "--css_edit" in sys.argv:
config = Load()
os.system("nano "+config.get("css", "default.css"))
if "--fc_api" in sys.argv:
try:
api = sys.argv[ sys.argv.index("--fc_api") + 1]
if "--" in api: 1/0 # Failing this for the error message.
FreeCompetitors(api)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" API Link Wasn't Specified!")
print('Use: $ python3 run.py --set --fc_api https://sudo.madiator.com')
def Title(title):
@ -332,3 +353,20 @@ def TabRows(tab_rows):
print(clr["bold"]+clr["tbyl"]+str(tab_rows)+clr["norm"]+" is set as amount of rows to render tabs.")
def MainAccount(account):
data = Load()
data["main_account"] = account
Save(data)
print(clr["bold"]+clr["tbyl"]+account+clr["norm"]+" is set as main account.")
def FreeCompetitors(api):
data = Load()
data["free_competitors"] = api
Save(data)
print(clr["bold"]+clr["tbyl"]+api+clr["norm"]+" is set as Free Competitors API.")

View file

@ -8,6 +8,7 @@ import urllib.request
import urllib.parse
from urllib import request, parse
from modules import Render
preicons = os.listdir("/home/vcs/Software/VCStudio/settings/themes/Default/icons")
icons = []
@ -399,7 +400,7 @@ def search_convert(s):
r = r + i
return r
def convert(filename, isfile=True, fullpath=False):
def convert(filename, isfile=True, fullpath=False, comments={}):
if fullpath:
@ -452,6 +453,32 @@ def convert(filename, isfile=True, fullpath=False):
if i[-1].startswith("http"):
tag = '<a href="'+i[-1][:-1]+'">'
ctag = "</a>"
if i[-1].startswith("c:"):
try: N = int(i[-1].replace("c:",""))
except:N = None
try:
comment = comments.get("comments", [])[N]
URL = comments.get("url","")
COMMENTS = comments.get("comments", [])
comment = Render.Comment(comment, URL, n=N, comments=COMMENTS)
TEXT = """
<details>
<summary class="button">
<img src="/icon/frase" style="vertical-align: middle">
c:"""+str(N)+"""
</summary>
"""+comment+"""
</details>
"""
i[-1] = TEXT.replace("\n", "")
except Exception as e: print(e)
textReturn = textReturn + tag + i[-1] + ctag
elif i[0] == "text_cm":