# Major Upgrade!

* Server implementation complete.
* Cross-compatibility with discover
* switch to fetch api
* Easy to make into a hugo shortcode
main
Gabriel 1 year ago
parent bee3e314d1
commit ca4b7635a4

1
.gitignore vendored

@ -0,0 +1 @@
*__pycache__

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

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

@ -1,169 +1,250 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/theme.css">
<title>Interverse</title>
<script src="https://unpkg.com/alpinejs" defer></script>
<script src="/helper.js"></script>
</head>
<body>
<div class="interverse" x-data x-if="Alpine.store('data')">
<div 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>
<style>
.interverse{
display: flex;
flex-direction: column;
align-items: center;
margin-left:5vw;
margin-right:5vw;
}
#interverse-details{
font-size:1.5rem;
display: flex;
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;
}
#interverse-groups h3{
text-align: center;
font-size:2rem;
}
#interverse-resource-groups img{
max-height:5rem;
max-width: 5rem;
}
#interverse-resources{
display: flex;
flex-direction: row;
max-width: 100%;
}
#interverse-resources>*{
margin:1rem;
}
#interverse-resources img{
max-height:5rem;
max-width: 5rem;
}
#interverse-groups{
display: flex;
flex-direction: column;
align-items: center;
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>
<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>
</div>
</template>
<section id="interverse-resource-groups">
<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>
<div id="interverse-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">
<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>
<h2 x-text="resource['label']"></h2>
<p x-text="resource['description']"></p>
</div>
</a>
</template>
</div>
<div id="interverse-groups">
<template x-for="(group,name) in Alpine.store('data')['connection_groups']">
<div>
<h3 x-text="name"></h3>
<div class="interverse-group">
<template x-for="link in group">
<div class="interverse-connection" x-data="{connected:false">
<template x-if="Alpine.store(link)['name'] !=''">
<div class='interverse-connection-preview' x-on:click="initialize(link+'/.well-known/interverse')">
<h3 x-text="Alpine.store(link)['name']"></h3>
<img x-bind:src="Alpine.store(link)['image']">
<a x-bind:href="link" x-text="link"></a>
</div>
</template>
<template x-if="Alpine.store(link) == undefined">
<div>
<a x-bind:href="link" x-text="link">
</a>
</div>
</template>
</div>
</template>
</div>
<div>
<h3 x-text="name"></h3>
<div class="interverse-connection-group">
<template x-for="link in group">
<div class="interverse-connection" x-data="{connected:false">
<template x-if="Alpine.store(link)['name'] !=''">
<div class='interverse-connection-preview'
x-on:click="initialize(link)">
<h3 x-text="Alpine.store(link)['name']"></h3>
<img x-bind:src="Alpine.store(link)['image']">
<a x-bind:href="link" x-text="link"></a>
</div>
</template>
<template x-if="Alpine.store(link) == undefined">
<div>
<a x-bind:href="link" x-text="link">
</a>
</div>
</template>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
<h2>Links</h2>
<div id="interverse-connections">
<template x-for="connection in Alpine.store('data')['connections']">
<div class="interverse-connection" >
<template x-if="Alpine.store(connection)['version'] >=0">
<div x-on:click="initialize(connection+'/.well-known/interverse')">
<h3 x-text="Alpine.store(connection)['name']"></h3>
<template x-if="Alpine.store(connection)['image']!=''">
<img x-bind:src="Alpine.store(connection)['image']">
</template>
</div>
</template>
<template x-if="name == ''">
<a x-bind:href="connection" x-text="connection"></a>
</template>
<div class="interverse-connection">
<template x-if="Alpine.store(connection)['name']">
<div x-on:click="initialize(connection)">
<h3 x-text="Alpine.store(connection)['name']"></h3>
<template x-if="Alpine.store(connection)['image']!=''">
<img x-bind:src="Alpine.store(connection)['image']">
</template>
</div>
</template>
<template x-if="Alpine.store(link) == undefined">
<a x-bind:href="connection" x-text="connection"></a>
</template>
</div>>
</template>
</div>>
</template>
</div>
<div id="interverse-contact">
<template x-for="contact in Alpine.store('data')['contact']">
<div class="interverse-contact">
</div>
<div class="interverse-contact">
</template>
</div>
</div>
</template>
</div>
<script>
var t = new Date();
var timestamp = t.getTime();
var main_url = "/.well-known/interverse?t=" + timestamp;
if (getUrlParameter('s') && getUrlParameter("url")) {
var uri = "";
main_url = getUrlParameter('s') + "://" + getUrlParameter("url") + '/.well-known/interverse?t=' + timestamp;
} else {
if (getUrlParameter('url')) {
main_url = "https://" + getUrlParameter('url') + '/.well-known/interverse?t=' + timestamp;
}
}
document.addEventListener('alpine:init', function() {
initialize(main_url);
});
</div>
function initialize(url) {
Alpine.store("data", {});
fetchdata(url, function(e) {
data = JSON.parse(e.target.response);
raw_data = JSON.parse(e.target.response);
Alpine.store("data", data);
var connections = raw_data['connections'];
for (group in data['connection_groups']) {
for (link in data['connection_groups'][group]) {
connections.push(data['connection_groups'][group][link]);
}
}
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");
}
<script>
var t = new Date();
var timestamp = t.getTime();
var main_url = window.location.host;
var interverse_proxy="http://localhost:5000"
if (getUrlParameter("url")) {
main_url = getUrlParameter("url")
}
document.addEventListener('alpine:init', function () {
initialize(main_url);
});
function initialize(url) {
Alpine.store("data", {});
interverse_data(url.replace("https://",'').replace('http://','').replace('/',''), function (data) {
console.log("Initializing interverse...")
Alpine.store('data',data);
for (group in data['connection_groups']) {
for (link in data['connection_groups'][group]) {
url = data['connection_groups'][group][link];
console.log("Connection: "+url)
interverse_data(url.replace("https://",'').replace('http://','').replace('/',''),function(data){
Alpine.store(data['location'],data)
});
//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>

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

@ -4,9 +4,8 @@
[Demo video](https://storage.gabe.rocks/LibreSolutionsNetwork/Interverse%20Demo.mp4)
## 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 Cryptocurrency/NFT
* Scalable
@ -37,8 +36,10 @@ https://libresolutions.network/Interverse?url=your.website
### Can I create my own client?
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)

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

@ -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
Loading…
Cancel
Save