# Major Upgrade!

* Server implementation complete.
* Cross-compatibility with discover
* switch to fetch api
* Easy to make into a hugo shortcode
This commit is contained in:
Gabriel 2022-10-09 11:48:31 -04:00
parent bee3e314d1
commit ca4b7635a4
8 changed files with 320 additions and 171 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*__pycache__

View file

@ -45,7 +45,8 @@
], ],
"Known Interverse": [ "Known Interverse": [
"https://jamespearson.xyz", "https://jamespearson.xyz",
"https://tomfasano.net" "https://tomfasano.net",
"https://retroedge.tech"
] ]
}, },

View file

@ -1,37 +1,19 @@
function fetchdata(url,cback){ function interverse_data(url,cback){
var req = new XMLHttpRequest(); url = interverse_proxy+"?url="+url
req.open("GET",url,true); fetch(url).then((response)=> {
req.addEventListener("load",cback); if (!response.ok){
req.send(); return {}
} }
return response.json();
function logreq(event){ }).then((data)=>cback(data))
console.log("Event:")
console.log(event.target.response)
}
function parse_news_item(dat){
var item = {};
item.title = dat.slice(dat.search("<title>")+7,dat.search("</title>"));
item.description = dat.slice(dat.search("<description>")+13,dat.search("</description>"));
item.url = dat.slice(dat.search("<link>")+6,dat.search("</link>"));;
item.img = dat.slice(dat.search("<img>")+5,dat.search("</img>"));;
return item;
} }
function get_rss_list(data){ // https://davidwalsh.name/query-string-javascript
var items = data.split("<item>").splice(1);
return items.map(parse_news_item).reverse();
}
//https://davidwalsh.name/query-string-javascript
function getUrlParameter(name) { function getUrlParameter(name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]'); name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)'); var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
var results = regex.exec(location.search); var results = regex.exec(location.search);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' ')); return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
}; };

View file

@ -1,169 +1,250 @@
<!DOCTYPE html> <style>
<html> .interverse{
display: flex;
flex-direction: column;
align-items: center;
margin-left:5vw;
margin-right:5vw;
}
<head> #interverse-details{
<link rel="stylesheet" href="/theme.css"> font-size:1.5rem;
<title>Interverse</title> display: flex;
<script src="https://unpkg.com/alpinejs" defer></script> flex-direction: row;
align-items: center;
max-width: 100%;
}
#interverse-details>*{
margin:1rem;
}
#interverse-resource-groups{
display: flex;
flex-direction: row;
flex-wrap: wrap;
max-width: 100%;
}
#interverse-resource-groups>*{
margin:1rem;
}
<script src="/helper.js"></script> #interverse-groups h3{
</head> text-align: center;
font-size:2rem;
}
#interverse-resource-groups img{
<body> max-height:5rem;
<div class="interverse" x-data x-if="Alpine.store('data')"> max-width: 5rem;
<div id="interverse-details"> }
<img x-bind:src="Alpine.store('data')['image']"> #interverse-resources{
<div id="interverse-contact"> display: flex;
<h1 x-text="Alpine.store('data')['name']"></h1> flex-direction: row;
max-width: 100%;
}
#interverse-resources>*{
margin:1rem;
}
#interverse-resources img{
<a x-bind:href="Alpine.store('data')['location']" x-text="Alpine.store('data')['location']"></a> max-height:5rem;
<template x-for="key in Object.keys(Alpine.store('data')['contact'])"> max-width: 5rem;
<p> }
<strong x-text="key"></strong> #interverse-groups{
<span x-text="Alpine.store('data')['contact'][key]"></span> display: flex;
</p> flex-direction: column;
</template> align-items: center;
</div> max-width: 100%;
}
.interverse-connection-group{
display:flex;
flex-direction: row;
flex-wrap: wrap;
}
#interverse-connections{
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
#interverse-connections>*{
margin:5px;
}
@keyframes glow{
from {box-shadow: 0rem 0rem 0.25rem transparent;}
to {box-shadow: 0rem 0rem 1.5rem rgb(7, 129, 229);}
}
.interverse-connection-preview{
animation: glow 1s alternate;
animation-iteration-count: infinite;
}
.interverse-connection-preview,.interverse-connection{
display:flex;
flex-direction: column;
align-items: center;
margin:1rem;
}
.interverse-connection-preview>*{
margin:1rem;
}
a{
font-weight: bolder;
}
.interverse-connection-preview img{
max-height:15rem;
max-width: 15rem;
}
</style>
<script src="https://unpkg.com/alpinejs" defer></script>
<script src="/helper.js"></script>
<div class="interverse" x-data x-if="Alpine.store('data')">
<section id="interverse-details">
<img x-bind:src="Alpine.store('data')['image']">
<div id="interverse-contact">
<h1 x-text="Alpine.store('data')['name']"></h1>
<a x-bind:href="Alpine.store('data')['location']" x-text="Alpine.store('data')['location']"></a>
<template x-for="key in Object.keys(Alpine.store('data')['contact'])">
<p>
<strong x-text="key"></strong>
<span x-text="Alpine.store('data')['contact'][key]"></span>
</p>
</template>
</div> </div>
<section id="interverse-resource-groups">
<template x-for="(group,name) in Alpine.store('data')['resource_groups']">
<div>
<h1 x-text="name"></h1>
<section class="interverse-group">
<template x-for="resource in group">
<a x-bind:href="resource['location']">
<div class="interverse-resource">
<template x-if="resource['icon'] !=''">
<img class="interverse-resource-icon" x-bind:src="resource['icon']">
</template>
<h2 x-text="resource['label']"></h2>
<p x-text="resource['description']"></p>
</div>
</a>
</template>
</section> </section>
</div> <section id="interverse-resource-groups">
</template> <template x-for="(group,name) in Alpine.store('data')['resource_groups']">
<div class="interverse-resource">
<h1 x-text="name"></h1>
<section class="interverse-group">
<template x-for="resource in group">
<a x-bind:href="resource['location']">
<div class="interverse-resource">
<template x-if="resource['icon'] !=''">
<img class="interverse-resource-icon" x-bind:src="resource['icon']">
</template>
<h2 x-text="resource['label']"></h2>
<p x-text="resource['description']"></p>
</div>
</a>
</template>
</section>
</div>
</template>
</section> </section>
<div id="interverse-resources"> <div id="interverse-resources">
<template x-for="resource in Alpine.store('data')['resources']"> <template x-for="resource in Alpine.store('data')['resources']">
<a x-bind:href="resource['location']"> <a x-bind:href="resource['location']">
<div class="interverse-resource"> <div class="interverse-resource">
<template x-if="resource['icon'] !=''"> <template x-if="resource['icon'] !=''">
<img class="interverse-resource-icon" x-bind:src="resource['icon']"> <img class="interverse-resource-icon" x-bind:src="resource['icon']">
</template> </template>
<h2 x-text="resource['label']"></h2> <h2 x-text="resource['label']"></h2>
<p x-text="resource['description']"></p> <p x-text="resource['description']"></p>
</div> </div>
</a> </a>
</template> </template>
</div> </div>
<div id="interverse-groups"> <div id="interverse-groups">
<template x-for="(group,name) in Alpine.store('data')['connection_groups']"> <template x-for="(group,name) in Alpine.store('data')['connection_groups']">
<div> <div>
<h3 x-text="name"></h3> <h3 x-text="name"></h3>
<div class="interverse-group"> <div class="interverse-connection-group">
<template x-for="link in group"> <template x-for="link in group">
<div class="interverse-connection" x-data="{connected:false"> <div class="interverse-connection" x-data="{connected:false">
<template x-if="Alpine.store(link)['name'] !=''"> <template x-if="Alpine.store(link)['name'] !=''">
<div class='interverse-connection-preview' x-on:click="initialize(link+'/.well-known/interverse')"> <div class='interverse-connection-preview'
<h3 x-text="Alpine.store(link)['name']"></h3> x-on:click="initialize(link)">
<img x-bind:src="Alpine.store(link)['image']"> <h3 x-text="Alpine.store(link)['name']"></h3>
<a x-bind:href="link" x-text="link"></a> <img x-bind:src="Alpine.store(link)['image']">
</div> <a x-bind:href="link" x-text="link"></a>
</template> </div>
<template x-if="Alpine.store(link) == undefined"> </template>
<div> <template x-if="Alpine.store(link) == undefined">
<a x-bind:href="link" x-text="link"> <div>
<a x-bind:href="link" x-text="link">
</a> </a>
</div>
</template>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</template>
</div> </div>
</template>
</div>
<h2>Links</h2>
<div id="interverse-connections"> <div id="interverse-connections">
<template x-for="connection in Alpine.store('data')['connections']"> <template x-for="connection in Alpine.store('data')['connections']">
<div class="interverse-connection" > <div class="interverse-connection">
<template x-if="Alpine.store(connection)['version'] >=0"> <template x-if="Alpine.store(connection)['name']">
<div x-on:click="initialize(connection+'/.well-known/interverse')"> <div x-on:click="initialize(connection)">
<h3 x-text="Alpine.store(connection)['name']"></h3> <h3 x-text="Alpine.store(connection)['name']"></h3>
<template x-if="Alpine.store(connection)['image']!=''"> <template x-if="Alpine.store(connection)['image']!=''">
<img x-bind:src="Alpine.store(connection)['image']"> <img x-bind:src="Alpine.store(connection)['image']">
</template> </template>
</div> </div>
</template>
<template x-if="Alpine.store(link) == undefined">
<a x-bind:href="connection" x-text="connection"></a>
</template>
</template> </div>>
<template x-if="name == ''"> </template>
<a x-bind:href="connection" x-text="connection"></a>
</template>
</div>>
</template>
</div> </div>
<div id="interverse-contact"> <div id="interverse-contact">
<template x-for="contact in Alpine.store('data')['contact']"> <template x-for="contact in Alpine.store('data')['contact']">
<div class="interverse-contact"> <div class="interverse-contact">
</div>
</template> </div>
</template>
</div> </div>
</div> </div>
<script> <script>
var t = new Date(); var t = new Date();
var timestamp = t.getTime(); var timestamp = t.getTime();
var main_url = "/.well-known/interverse?t=" + timestamp; var main_url = window.location.host;
if (getUrlParameter('s') && getUrlParameter("url")) { var interverse_proxy="http://localhost:5000"
var uri = ""; if (getUrlParameter("url")) {
main_url = getUrlParameter('s') + "://" + getUrlParameter("url") + '/.well-known/interverse?t=' + timestamp; main_url = getUrlParameter("url")
} else { }
if (getUrlParameter('url')) { document.addEventListener('alpine:init', function () {
main_url = "https://" + getUrlParameter('url') + '/.well-known/interverse?t=' + timestamp; initialize(main_url);
} });
}
document.addEventListener('alpine:init', function() {
initialize(main_url);
});
function initialize(url) { function initialize(url) {
Alpine.store("data", {}); Alpine.store("data", {});
fetchdata(url, function(e) { interverse_data(url.replace("https://",'').replace('http://','').replace('/',''), function (data) {
data = JSON.parse(e.target.response); console.log("Initializing interverse...")
raw_data = JSON.parse(e.target.response); Alpine.store('data',data);
Alpine.store("data", data); for (group in data['connection_groups']) {
var connections = raw_data['connections']; for (link in data['connection_groups'][group]) {
for (group in data['connection_groups']) { url = data['connection_groups'][group][link];
for (link in data['connection_groups'][group]) { console.log("Connection: "+url)
connections.push(data['connection_groups'][group][link]); interverse_data(url.replace("https://",'').replace('http://','').replace('/',''),function(data){
} Alpine.store(data['location'],data)
}
var t = new Date();
var timestamp = t.getTime();
for (var i = 0; i < connections.length; i++) {
fetchdata(connections[i] + '/.well-known/interverse?t=' + timestamp, function(e) {
try {
dat = JSON.parse(e.target.response);
console.log(dat);
Alpine.store(dat['location'], dat);
} catch {
console.log("Failed to parse connection JSON");
}
}); });
//connections.push(data['connection_groups'][group][link]);
} }
}
var connections = data['connections'];
for (var i = 0; i < connections.length; i++) {
url = connections[i].replace("https://",'').replace('http://','').replace('/','')
interverse_data(url,function(data){
if (data['location']){
Alpine.store(data['location'],data);
}); }
} });
</script> }
</body>
</html> });
}
</script>

View file

@ -1 +0,0 @@
<! not sure how to slim it down yet>

View file

@ -4,9 +4,8 @@
[Demo video](https://storage.gabe.rocks/LibreSolutionsNetwork/Interverse%20Demo.mp4) [Demo video](https://storage.gabe.rocks/LibreSolutionsNetwork/Interverse%20Demo.mp4)
## A decentralized discovery service that allows you to easily discover like-minded sites. ## A decentralized discovery service that allows you to easily discover like-minded sites.
Interverse aims to enable [small-tech](https://small-tech.org#small-tech-is).
That means
* No complicated software to run (beyond an existing website) * No complicated software to run (beyond an existing website)
* No Cryptocurrency/NFT * No Cryptocurrency/NFT
* Scalable * Scalable
@ -37,8 +36,10 @@ https://libresolutions.network/Interverse?url=your.website
### Can I create my own client? ### Can I create my own client?
Please do! Please do!
## If you need any help getting started feel free to get in touch with me by e-mailing gabriel@libresolutions.network If you need any help getting started feel free to get in touch with me by e-mailing gabriel@libresolutions.network
---
Compatible with [Discover](https://codeberg.org/onasaft/Discover)

View file

@ -0,0 +1,22 @@
from flask import Flask,request,redirect
import json,requests,time
import simple_cache
app = Flask('interverse-proxy')
cache = simple_cache.Cache()
@app.route("/", methods=['GET'])
def interverse_proxy():
url = request.args.get('url')
if url == None:
return redirect("https://codeberg.org/gabe/Interverse",307)
return "See <a href='https://codeberg.org/gabe/Interverse'>Interverse</a>"
data = cache.load_data(url)
return json.dumps(data)
if __name__ == '__main__':
app.run()

62
server/simple_cache.py Normal file
View file

@ -0,0 +1,62 @@
import requests,time,json
ideal_delta = 60*5 #5 minutes
crawler_header = {'User-agent': 'interverse-crawler','info':'https://libresolutions.network/videos/interverse-demo-1/'}
schemes = ['http://','https://']
locations = [
'/.well-known/discover.json',
'/.well-known/interverse',
'/interverse.json',
'/discover.json'
]
class Cache:
def __init__(self,delta=None):
if delta==None:
self.delta = ideal_delta
else:
self.delta = delta
self.links={}
# link = key:{data,time}
def load_data(self,url):
data = None
t = time.time()
if url in self.links:
if t - self.links[url]['time'] <= self.delta:
print(f"Using cached result for {url}")
return self.links[url]['data']
for s in schemes:
for l in locations:
try:
data = requests.get(s+url+l,headers=crawler_header,timeout=3).json()
if l.find('discover'):
#translate discover to interverse
data = json.loads(json.dumps(data).replace("preview_connections","connection_groups"))
print(f"Interverse connection found at {l}")
t = time.time()
self.links[url] = {
'time':t,
'data':data,
}
return data
except:
pass
if data != None:
t = time.time()
self.links[url] = {
'time':t,
'data':data,
}
if data == None:
#If no data is returned, wait longer before attempting again
self.links[url] = {
'data':None,
'time':t+ideal_delta
}
return data