Feat: Allows instances to pull a badge (#13)

pull/17/head
Divided by Zer0 2023-06-27 16:11:51 +02:00 committed by GitHub
parent cf488eb583
commit ceb1c3cf3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 146 additions and 44 deletions

View File

@ -7,3 +7,17 @@ It's reliant on the [Lemmy Fediverse Observer](https://lemmy.fediverse.observer/
The currently running instance is on https://fediseer.com The currently running instance is on https://fediseer.com
See devlog: https://dbzer0.com/blog/overseer-a-fediverse-chain-of-trust/ See devlog: https://dbzer0.com/blog/overseer-a-fediverse-chain-of-trust/
# Badges
You can retrieve and display a badge for your fediverse domain by requesting a .svg for it on a special endpoint
`/v1/badges/guarantees/{domain}.svg` will give you an badge of guarantee, mentioning the domains which guaranteed for your domain
Example:
[![](http://fediseer.com/api/v1/badges/guarantees/lemmy.dbzer0.com.svg)](https://fediseer.com/api/v1/whitelist/lemmy.dbzer0.com)
`/v1/badges/endorsements/{domain}.svg` will give you an badge of endorsements, providing a count of how many other the fediverse domains guaranteed for yours
Example:
[![](http://fediseer.com/api/v1/badges/endorsements/lemmy.dbzer0.com.svg)](https://fediseer.com/api/v1/endorsements/lemmy.dbzer0.com)

View File

@ -3,6 +3,7 @@ import fediseer.apis.v1.whitelist as whitelist
import fediseer.apis.v1.endorsements as endorsements import fediseer.apis.v1.endorsements as endorsements
import fediseer.apis.v1.guarantees as guarantees import fediseer.apis.v1.guarantees as guarantees
import fediseer.apis.v1.activitypub as activitypub import fediseer.apis.v1.activitypub as activitypub
import fediseer.apis.v1.badges as badges
import fediseer.apis.v1.find as find import fediseer.apis.v1.find as find
from fediseer.apis.v1.base import api from fediseer.apis.v1.base import api
@ -16,3 +17,5 @@ api.add_resource(endorsements.Endorsements, "/endorsements/<string:domain>")
api.add_resource(endorsements.Approvals, "/approvals/<string:domain>") api.add_resource(endorsements.Approvals, "/approvals/<string:domain>")
api.add_resource(guarantees.Guarantors, "/guarantors/<string:domain>") api.add_resource(guarantees.Guarantors, "/guarantors/<string:domain>")
api.add_resource(guarantees.Guarantees, "/guarantees/<string:domain>") api.add_resource(guarantees.Guarantees, "/guarantees/<string:domain>")
api.add_resource(badges.GuaranteeBadge, "/badges/guarantees/<string:domain>.svg")
api.add_resource(badges.EndorsementBadge, "/badges/endorsements/<string:domain>.svg")

View File

@ -45,14 +45,14 @@ class Inbox(Resource):
self.args = self.post_parser.parse_args() self.args = self.post_parser.parse_args()
json_payload = request.get_json() json_payload = request.get_json()
actor = json_payload["actor"] actor = json_payload["actor"]
if json_payload.get("Type") == "Follow": if json_payload.get("type") == "Follow":
logger.info(f"Fediseer is now followed from: {actor}") logger.info(f"Fediseer is now followed from: {actor}")
elif json_payload.get("Type") == "Delete": elif json_payload.get("type") == "Delete":
pass pass
else: else:
try: try:
message = json_payload["object"]["content"] message = json_payload["object"]["content"]
logger.info(f"Fediseer Inbox Received: From: {actor} | {message}") logger.info(f"Fediseer Inbox Received: From: {actor} | {message}")
except: except:
logger.info(f"Received unexpected invox payload: {json_payload}") logger.info(f"Received unexpected inbox payload: {json_payload}")
return {"message": "delivered"}, 200 return {"message": "delivered"}, 200

View File

@ -0,0 +1,55 @@
from fediseer.apis.v1.base import *
from fediseer.badges import generate_endorsements_badge, generate_guarantee_badge
from sqlalchemy.orm import aliased
from fediseer.classes.instance import Guarantee, Endorsement
from flask import make_response
from sqlalchemy import func
class GuaranteeBadge(Resource):
def get(self, domain):
'''Retrieve Guarantee Badge SVG
'''
guaranteed_instance_alias = aliased(Instance)
query = db.session.query(
Instance.domain,
guaranteed_instance_alias.domain,
).join(
Guarantee, Instance.id == Guarantee.guaranteed_id
).join(
guaranteed_instance_alias, guaranteed_instance_alias.id == Guarantee.guarantor_id
).filter(
Instance.domain == domain
)
guarantor = query.first()
if guarantor is None:
svg = generate_guarantee_badge(domain, None)
else:
svg = generate_guarantee_badge(domain, guarantor[1])
response = make_response(svg)
response.headers['Content-Type'] = 'image/svg+xml'
return response
class EndorsementBadge(Resource):
def get(self, domain):
'''Retrieve Endorsement Badge SVG
'''
query = db.session.query(
Instance.domain,
func.count(Endorsement.id) # Count the number of endorsements
).join(
Endorsement, Instance.id == Endorsement.endorsed_id
).filter(
Instance.domain == domain
).group_by(
Instance.domain
)
endorsements = query.first()
if endorsements is None:
svg = generate_endorsements_badge(domain, 0)
else:
svg = generate_endorsements_badge(domain, endorsements[1])
response = make_response(svg)
response.headers['Content-Type'] = 'image/svg+xml'
return response

View File

@ -0,0 +1,5 @@
# Assets
* [crossed-chains](https://game-icons.net/1x1/lorc/crossed-chains.html)
* [thumb-up](https://game-icons.net/1x1/delapouite/thumb-up.html)
*

View File

@ -0,0 +1 @@
<svg style="height: 512px; width: 512px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><g class="" style="" transform="translate(0,0)"><path d="M19.023 19.676v27.2L75.48 104.32l24.842-24.843-58.804-59.8H19.023zm65.41 3.498c-4.468.09-8.392.9-11.83 2.244L189.15 141.963c2.893-7.395 3.085-17.245-.146-29.094-4.61-16.908-16.108-36.597-33.408-53.895-17.3-17.3-36.99-28.8-53.897-33.41-6.34-1.73-12.125-2.495-17.265-2.39zm-63.09 53.51c-2.894 7.394-3.088 17.243.143 29.09 4.61 16.908 16.11 36.598 33.41 53.898 17.3 17.3 36.988 28.797 53.895 33.408 11.85 3.232 21.7 3.04 29.095.147L21.342 76.684zm107.07 30.96l-24.716 24.717 119.545 119.546 24.717-24.717-119.546-119.545zm366 56.57c-7.766 2.724-15.67 6.186-23.36 10.626-20.332 11.738-35.756 27.138-44.106 41.637-5.734 9.956-7.942 18.928-7.213 26.365l74.68-42.953v-35.675zm-257.07 11.87c-4.466.09-8.39.9-11.827 2.246l73.62 73.62c10.648-5.168 21.366-8.972 31.78-11.358-5.744-9.727-13.255-19.55-22.41-28.705-17.3-17.3-36.99-28.8-53.897-33.41-6.34-1.73-12.126-2.497-17.266-2.393zm-62.677 53.096c-2.894 7.394-3.085 17.244.147 29.093 4.61 16.907 16.11 36.597 33.41 53.897 6.408 6.408 13.144 12.014 19.945 16.775 1.55-7.685 4.687-15.44 9.016-22.86 1.705-2.925 3.62-5.822 5.7-8.69l-68.218-68.215zm304.063.668L338.36 311.03l16.425 28.75 137.88-79.628v-6.12l-13.936-24.184zm-122.783 26.406c-16.79.075-37.91 5.84-58.258 17.588-20.348 11.747-35.902 27.155-44.362 41.658-5.79 9.93-8.063 18.86-7.39 26.26l136.445-78.776c-6.074-4.282-14.942-6.78-26.436-6.73zm138.467 23.637l-39.684 22.936c3.228 2.31 7.23 4.13 12.22 5.32 7.53 1.793 17.022 1.925 27.464.307V279.89zm-75.32 43.643l-136.448 78.78c6.073 4.283 14.943 6.78 26.438 6.728 16.79-.074 37.912-5.84 58.26-17.587 20.347-11.747 35.9-27.154 44.36-41.658 5.793-9.93 8.063-18.86 7.39-26.262zM296.53 334.19l-10.686 6.167-.037-.037-12.73 7.35.09.008-116.956 67.525 16.472 28.53 140.32-81.012-16.473-28.53zm138.993 7.558c-1.74 5.896-4.36 11.778-7.675 17.463-2.508 4.3-5.44 8.55-8.743 12.71l68.754 68.754c2.892-7.394 3.084-17.244-.147-29.092-4.61-16.906-16.108-36.595-33.408-53.894-6.042-6.042-12.377-11.36-18.782-15.94zM177.44 360.326c-16.79.075-37.91 5.84-58.258 17.588C98.834 389.66 83.28 405.07 74.82 419.572c-5.79 9.928-8.062 18.86-7.39 26.26l136.445-78.775c-6.073-4.283-14.942-6.782-26.436-6.73zM427.2 406.61l-24.67 24.29 62.49 62.996h27.644V472.07L427.2 406.61zm-66.124 8.982c-10.614 4.73-21.244 8.11-31.514 10.076 5.956 10.888 14.173 22.015 24.458 32.3 17.298 17.3 36.987 28.8 53.894 33.41 11.85 3.233 21.7 3.04 29.096.147l-75.934-75.933zM239.09 427.674l-114.528 66.123h65.815c18.962-11.586 33.505-26.31 41.502-40.193 5.648-9.81 7.863-18.622 7.21-25.93zm-114.854 7.144L21.73 493.896h65.452l53.392-30.86-16.338-28.218z" fill="#f8cc1c" fill-opacity="1"></path></g></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1 @@
<svg style="height: 512px; width: 512px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><g class="" style="" transform="translate(0,0)"><path d="M302.344 28.514c-2.386 1.354-4.334 3.497-6.273 7.182-2.335 4.438-4.28 10.836-5.94 18.29-3.316 14.91-5.51 33.828-12.098 51.26l-.54 1.43-.982 1.17c-53.308 63.59-92.07 98.36-119.014 148.817l-2.46 4.605-5.22.15s-10.63.322-22.607 2.46c-11.978 2.137-43.225 3.316-46.132 7.017-13.33 16.97-23.48 45.76-25.072 70.46-1.592 24.697 3.872 48.986 16.676 65.568.858 1.11 29.53 4.57 38.22 5.302 8.693.73 16.52.162 16.52.162l6.08-.457 2.678 5.48c14.83 30.325 61.456 50.595 110.937 66.056 4.384.248 11.962-1.82 20.852-6.295-19.818-7.57-35.37-14.03-52.5-23.087l-4.423-2.338-.347-4.99c-.896-12.814 5.672-23.273 13.61-30.256 2.54-2.237 5.24-4.216 7.985-6.038-1.54-.685-3.074-1.38-4.62-2.053l-4.698-2.05-.635-5.086c-1.752-14.04 2.566-27.897 11.664-37.728 1.852-2 3.932-3.81 6.19-5.405l-.23-.1-.438-5.31c-1.313-15.828 8.72-29.44 20.295-39.02 2.13-1.764 4.347-3.44 6.623-4.985l-.91-2.148c-4.49-10.608-2.336-21.785 3.01-29.815 5.35-8.03 13.357-13.657 21.92-16.79l.232-.086.236-.073c16.656-5.078 32.616-1.007 47.756 3.03 15.14 4.036 29.842 8.22 45.14 6.923 9.286 15.502 8.095 12.523 10.49 17.176l-8.966.76c-19.417 1.647-36.48-3.515-51.3-7.467-14.71-3.92-26.832-6.456-37.616-3.264-5.132 1.933-10.21 5.715-12.908 9.77-2.15 3.226-3.06 6.18-2.338 9.75 21.768 9.268 38.374 19.347 57.04 24.798 19.682 5.746 42.49 7.06 79.325-3.035 5.858-2.842 11.615-7.678 15.014-12.946 3.503-5.427 4.637-10.646 2.87-16-6.415-19.43-15.202-36.684-33.028-48.515-40.155-16.703-65.958-16.832-101.275-13.336-.627-5.215-.437-10.644.022-18.09 2.066-15.153 8.845-33.844 16.745-55.096 9.792-26.342 21.692-55.157 30.416-78.892-2.042-19.79-7.618-30.87-15.044-37.3-7.295-6.315-17.56-9.054-30.932-9.564zm-14.53 290.698c-4.52 1.1-10.952 3.76-16.513 8.363-6.763 5.6-11.67 12.988-13.268 18.99 24.578 11.48 43.09 22.944 74.63 33.086l66.92-3.794c11.996-4.2 19.78-8.523 24.07-12.197 4.42-3.79 5.292-6.294 5.45-8.77.265-4.15-3.83-11.11-9.88-18.43-37.514 9.835-63.92 8.562-86.034 2.105-16.627-4.855-30.524-12.225-45.375-19.353zm-25.398 49.41c-6.38.195-11.287 2.923-15.2 7.15-4.27 4.616-6.853 11.327-7.09 18.49 23.313 10.374 45.08 21.135 74.69 30.407l56.54-1.095c21.862-.903 27.79-9.79 28.918-17.854.6-4.28-.493-8.504-2.71-11.72l-67.27 3.815-1.57-.496c-28.61-9.035-47.056-19.095-66.265-28.698h-.044zm-9.96 50.937c-6.14 3.332-12.053 6.753-16.258 10.452-4.005 3.523-6.404 6.67-7.237 10.69 17.396 8.825 33.933 15.363 57.386 24.162l46.51 1.596c8.015-3.427 12.8-6.69 15.08-9.11 2.44-2.59 2.637-3.797 2.603-5.873-.035-2.12-1.344-5.413-3-9.437l-35.403.685-1.385-.427c-22.878-7.07-41.16-14.99-58.295-22.74z" fill="#f8cc1c" fill-opacity="1"></path></g></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

49
fediseer/badges.py 100644
View File

@ -0,0 +1,49 @@
from pybadges import badge
from loguru import logger
import base64
with open('fediseer/assets/crossed-chains.svg', 'rb') as file:
svg_data = file.read()
base64_data = base64.b64encode(svg_data).decode('utf-8')
embed_guarantee = f"data:image/svg+xml;base64,{base64_data}"
with open('fediseer/assets/thumb-up.svg', 'rb') as file:
svg_data = file.read()
base64_data = base64.b64encode(svg_data).decode('utf-8')
embed_endorsement = f"data:image/svg+xml;base64,{base64_data}"
def generate_guarantee_badge(domain: str, guarantor: str):
left_color = "SeaGreen"
right_text=guarantor
if guarantor is None:
left_color = "red"
right_text="None"
guarantee_badge = badge(
left_text="Guarantee",
right_text=right_text,
left_color=left_color,
logo=embed_guarantee,
whole_title="Fediseer Guarantee",
left_title=domain,
right_title="Guarantor",
left_link="https://fediseer.com",
right_link=f"https://fediseer.com/api/v1/endorsements/{domain}",
)
return guarantee_badge
def generate_endorsements_badge(domain: str, count: int):
left_color = "DarkOliveGreen"
right_text=str(count)
if count == 0:
left_color = "red"
endorsements_badge = badge(
left_text="Endorsements",
right_text=right_text,
left_color=left_color,
logo=embed_endorsement,
whole_title="Fediseer Endorsements",
left_title=domain,
right_title="Endorsements Count",
left_link="https://fediseer.com",
right_link=f"https://fediseer.com/api/v1/whitelist/{domain}",
)
return endorsements_badge

View File

@ -15,3 +15,5 @@ psycopg2-binary
regex regex
pythorhead>=0.8.2 pythorhead>=0.8.2
bleach bleach
boto3
pybadges

50
test.py
View File

@ -1,41 +1,13 @@
import requests from dotenv import load_dotenv
import json import os
from datetime import datetime import logging
import OpenSSL.crypto
import base64
import hashlib
import sys
import uuid
with open('lemmy-hello-world.json', 'r') as file: load_dotenv()
document = json.loads(file.read())
document["id"] = f"https://fediseer.com/{uuid.uuid4()}"
document["object"]["id"] = f"https://fediseer.com/{uuid.uuid4()}"
document["object"]["published"] = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
document = json.dumps(document, indent=4)
print(document)
digest = hashlib.sha256(document.encode('utf-8')).digest()
encoded_digest = base64.b64encode(digest).decode('utf-8')
digest_header = "SHA-256=" + encoded_digest
date = datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
with open('private.pem', 'rb') as file: from fediseer.argparser import args
private_key_data = file.read() from fediseer.flask import OVERSEER
private_key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, private_key_data) from loguru import logger
from fediseer.badges import generate_guarantee_badge
signed_string = f"(request-target): post /inbox\nhost: overctrl.dbzer0.com\ndate: {date}\ndigest: {digest_header}" if __name__ == "__main__":
signature = OpenSSL.crypto.sign(private_key, signed_string.encode('utf-8'), 'sha256') # Only setting this for the WSGI logs
encoded_signature = base64.b64encode(signature).decode('utf-8') print(generate_guarantee_badge("lemmy.dbzer0.com", "fediseer.com"))
header = f'keyId="https://fediseer.com/api/v1/user/fediseer",headers="(request-target) host date digest",signature="{encoded_signature}"'
headers = {
'Host': 'overctrl.dbzer0.com',
'Date': date,
'Signature': header,
'Digest': digest_header,
'Content-Type': 'application/ld+json; profile="http://www.w3.org/ns/activitystreams"'
}
url = 'https://overctrl.dbzer0.com/inbox'
response = requests.post(url, data=document, headers=headers)
print('Response Status:', response.status_code)
print('Response Body:', response.text)