# 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:
parent
bee3e314d1
commit
ca4b7635a4
8 changed files with 320 additions and 171 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -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>
|
||||
<style>
|
||||
.interverse{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-left:5vw;
|
||||
margin-right:5vw;
|
||||
}
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="/theme.css">
|
||||
<title>Interverse</title>
|
||||
<script src="https://unpkg.com/alpinejs" defer></script>
|
||||
#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;
|
||||
}
|
||||
|
||||
<script src="/helper.js"></script>
|
||||
</head>
|
||||
#interverse-groups h3{
|
||||
text-align: center;
|
||||
font-size:2rem;
|
||||
}
|
||||
#interverse-resource-groups img{
|
||||
|
||||
<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>
|
||||
max-height:5rem;
|
||||
max-width: 5rem;
|
||||
}
|
||||
#interverse-resources{
|
||||
display: flex;
|
||||
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>
|
||||
<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>
|
||||
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">
|
||||
<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>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</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>
|
||||
<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>
|
||||
|
||||
</template>
|
||||
<template x-if="name == ''">
|
||||
<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 class="interverse-contact">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</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);
|
||||
});
|
||||
<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", {});
|
||||
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");
|
||||
}
|
||||
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>
|
11
readme.md
11
readme.md
|
@ -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)
|
||||
|
||||
|
||||
|
|
22
server/interverse-proxy.py
Normal file
22
server/interverse-proxy.py
Normal 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
62
server/simple_cache.py
Normal 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
|
Loading…
Reference in a new issue