Avatar

@vladimyr

1 like18 public vals
Joined August 5, 2023
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// SPDX-License-Identifier: AGPL
// Ported from https://codeberg.org/kitten/app/src/branch/main/src/lib/KittenMoji.js
// dprint-ignore
const alphabet = [
"🐵", "🐒", "🦍", "🦧", "🐶", "🐕", "🦮", "🐩", "🐺", "🦊", "🦝",
"🐱", "🐈", "🦁", "🐯", "🐅", "🐆", "🐴", "🧮", "🦄", "🦓", "🦌",
"🦬", "🐮", "🐂", "🐃", "🐄", "🐷", "🐖", "🐗", "🐽", "🐏", "🐑",
"🐐", "🐪", "🐫", "🦙", "🦒", "🐘", "🦣", "🦏", "🦛", "🐭", "🐁",
"🐀", "🐹", "🐰", "🐇", "🎈", "🦫", "🦔", "🦇", "🐻", "🐨", "🐼",
"🦥", "🦦", "🦨", "🦘", "🦡", "🐾", "🦃", "🎹", "🐓", "🐣", "🐤",
"🐥", "🐦", "🐧", "💕", "🦅", "🦆", "🦢", "🦉", "🦤", "🪶", "🦩",
"🦚", "🦜", "🚲", "🐊", "🐢", "🦎", "📚", "🐉", "🦕", "🦖", "🐳",
"🐋", "🐬", "🦭", "🐟", "🐠", "🐡", "🦈", "🐙", "🐚", "🐌", "🦋",
"🐛", "🐜", "🐝", "🪲", "🐞", "🦗", "🎭", "🎁", "🧬", "🪱", "🦠",
"💐", "🌸", "🎠", "🌈", "🌹", "🧣", "🌺", "🌻", "🌼", "🌷", "🌱",
"🪴", "🌲", "🌳", "🌴", "🌵", "🌾", "🌿", "🎤", "🍀", "🍁", "🪺",
"👽", "🍇", "🍈", "🍉", "🍊", "🍋", "🍌", "🍍", "🥭", "🍎", "🍏",
"🍐", "🍑", "🍒", "🍓", "🫐", "🥝", "🍅", "🫒", "🥥", "🥑", "🍆",
"🥔", "🥕", "🌽", "🧸", "🫑", "🥒", "🥬", "🥦", "🧄", "🧅", "🍄",
"🥜", "🌰", "🍞", "🥐", "🥖", "💩", "🥨", "🥯", "🥞", "🧇", "🧀",
"🎶", "🏸", "🎾", "🎨", "🍔", "🔭", "🍕", "🌭", "🥪", "🌮", "🌯",
"😸", "📷", "🌜", "🥚", "🚂", "🛼", "🚁", "👾", "👻", "🥗", "🍿",
"🧩", "🖖", "🥫", "🎸", "🍘", "🍙", "🍚", "🃏", "🍜", "🍝", "🍠",
"🍢", "🍣", "🍤", "🍥", "🥮", "🍡", "🥟", "🥠", "🩰", "🦀", "🦞",
"🦐", "🦑", "🎡", "🍦", "🍧", "🍨", "🍩", "🍪", "🎂", "🍰", "🧁",
"🥧", "🍫", "🍬", "🍭", "🍮", "🎓", "🍼", "🎮", "🛹", "🫖", "🌍",
"🌎", "🌏", "🧭", "🌠", "🪐", "🪀", "🧵", "🧶", "🧋", "🎉", "🪁",
"🙈", "🙉", "🙊"
];
const alphabetBytesToChars: string[] = alphabet.reduce<string[]>((p, c, i) => {
p[i] = c;
return p;
}, []);
const alphabetCharsToBytes: number[] = alphabet.reduce<number[]>((p, c, i) => {
p[c.codePointAt(0) as number] = i;
return p;
}, []);
export function encode(data: Uint8Array): string {
return data.reduce((p, c) => {
p += alphabetBytesToChars[c];
return p;
}, "");
}
export function decode(str: string): Uint8Array {
const byts = [];
for (const char of str) {
Fork
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import { DOMParser } from "https://deno.land/x/deno_dom/deno-dom-wasm.ts";
import { Feed } from "npm:feed";
import ky from "npm:ky";
const BASE_URL = "https://midnight.pub";
// @see: https://git.sr.ht/~m15o/midnight-pub/tree/master/item/model/user.go#L28
const reUserFeed = /^\/(?<username>~[a-z0-9-_]+)\.(?<format>atom|rss)$/;
export default async function(req: Request): Promise<Response> {
const { pathname } = new URL(req.url);
const match = pathname.match(reUserFeed);
if (!match) {
return new Response(null, { status: 400 });
}
const { format, username } = match.groups;
const profileURL = new URL(`/${username}`, BASE_URL);
const posts = await grabPosts(profileURL);
const feed = new Feed({
id: profileURL.href,
link: profileURL.href,
title: username,
description: `${username}'s posts`,
author: {
name: username,
link: profileURL.href,
},
feedLinks: {
atom: req.url,
rss: req.url,
},
copyright: undefined, // I have no idea what to put there ¯\_(ツ)_/¯
});
posts.forEach(post =>
feed.addItem({
id: post.href,
link: post.href,
date: post.createdAt,
title: post.title,
description: post.title,
})
);
if (format === "rss") {
const headers = { "content-type": "application/xml" };
return new Response(feed.rss2(), { headers });
}
const headers = { "content-type": "application/atom+xml" };
return new Response(feed.atom1(), { headers });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// SPDX-License-Identifier: 0-BSD
export function createPacketReader({
packetStartMarker,
packetEndMarker,
}: {
packetStartMarker: string;
packetEndMarker: string;
}) {
packetStartMarker += "\n";
return function readPacket(input: string) {
input = input.trim();
let offset = input.indexOf(packetStartMarker);
if (offset === -1) throw TypeError("error: invalid packet");
offset += packetStartMarker.length;
const contentOffset = offset;
offset = input.indexOf(packetEndMarker, offset);
if (offset === -1) throw TypeError("error: invalid packet");
const contentLength = offset - contentOffset;
return input.substring(contentOffset, contentOffset + contentLength);
};
}
export function createSignedPacketReader({
packetStartMarker,
signatureStartMarker,
packetEndMarker,
}: {
packetStartMarker: string;
signatureStartMarker: string;
packetEndMarker: string;
}) {
packetStartMarker += "\n";
signatureStartMarker += "\n";
return function readSignedPacket(input: string) {
input = input.trim();
let offset = input.indexOf(packetStartMarker);
if (offset === -1) throw TypeError("error: invalid packet");
offset += packetStartMarker.length;
const contentOffset = offset;
offset = input.indexOf(signatureStartMarker, offset);
if (offset === -1) throw TypeError("error: invalid packet");
const contentLength = offset - contentOffset;
offset += signatureStartMarker.length;
const signatureOffset = offset;
offset = input.indexOf(packetEndMarker, offset);
if (offset === -1) throw TypeError("error: invalid packet");
const signatureLength = offset - signatureOffset;

This is small SVG badge generator made for Ariadne.id Signature Profiles. Route format is:

https://vladimyr-aspbadge.web.val.run/<aspe_uri>

with optional format and debug query params. Use ?format=json to get back JSON response. JSON format can be used as an input for Shields.io's dynamic endpoint badge.

Badge with legacy ASPE URI:

[![](https://vladimyr-aspbadge.web.val.run/aspe:keyoxide.org:TOICV3SYXNJP7E4P5AOK5DHW44)](https://keyoxide.org/aspe:keyoxide.org:TOICV3SYXNJP7E4P5AOK5DHW44)

🔬️ inspect ASP JWT token

Badge with new ASPE URI (not functional yet ⚠️):

[![](https://vladimyr-aspbadge.web.val.run/aspe:fe7b75c54b95ac019dd48fbefe8d654af383cbfe)](https://keyoxide.org/aspe:fe7b75c54b95ac019dd48fbefe8d654af383cbfe)
Readme
Fork
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// SPDX-License-Identifier: 0-BSD
import {
AspeURI,
calculateFingerprint,
fetchASP,
formatFingerprint,
getKeyId,
HTTPError,
LegacyAspeURI,
parseAspeURI,
parseLegacyAspeURI,
} from "https://esm.town/v/vladimyr/libasp";
import { badgen as createBadge, BadgenOptions } from "npm:badgen";
import svg2dataURL from "npm:mini-svg-data-uri";
type BadgeConfig = BadgenOptions & { _debug?: object };
// @see: https://design.keyoxide.org/
const BADGE_ICON =
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"feather feather-key\"><path d=\"M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778
.replace("stroke=\"currentColor\"", "stroke=\"#ffffff\"");
const BADGE_LABEL = "ASP";
const BADGE_COLOR = "6855c3";
export default async function(req: Request): Promise<Response> {
const reqURL = new URL(req.url);
const { format, debug } = Object.fromEntries(reqURL.searchParams.entries());
const isDebugEnabled = debug && debug !== "false";
const aspeUri = reqURL.pathname.slice(1);
if (!aspeUri) return new Response(null, { status: 404 });
const badgeConfig = await getBadgeConfig(aspeUri, isDebugEnabled);
if (format === "json") {
const data = {
schemaVersion: 1,
label: badgeConfig.label,
message: badgeConfig.status,
color: badgeConfig.color,
logoSvg: BADGE_ICON,
_debug: undefined,
};
if (isDebugEnabled) data._debug = badgeConfig._debug;
return Response.json(data);
}
badgeConfig.icon = svg2dataURL(BADGE_ICON);
return new Response(createBadge(badgeConfig), {
headers: {
"content-type": "image/svg+xml",
},
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// SPDX-License-Identifier: 0-BSD
import { computeThumbprint } from "https://esm.town/v/vladimyr/libjwk";
import { shake128, shake256 } from "npm:@noble/hashes/sha3";
import * as jose from "npm:jose";
import ky, { HTTPError } from "npm:ky";
import { base16 } from "npm:multiformats/bases/base16";
export { HTTPError as HTTPError };
export type HashAlgorithm = "shake128" | "shake256";
const reLegacyAspeURI = /^aspe:(?:\/\/)?(?:(?<hostname>[^\s\/:]+)(?:\/|:))?(?<keyId>[A-Za-z0-9]{26})$/;
const reAspeURI = /^aspe:(?:\/\/(?<hostname>[^\s\/]+)\/)?(?<fingerprint>[A-Fa-f0-9]{40})$/;
export type AspeURI = ReturnType<typeof parseAspeURI>;
export function parseAspeURI(input: string) {
const match = input.match(reAspeURI);
if (!match) return null;
const { hostname, fingerprint } = match.groups;
return { hostname, fingerprint: fingerprint.toUpperCase() };
}
export type LegacyAspeURI = ReturnType<typeof parseLegacyAspeURI>;
export function parseLegacyAspeURI(input: string) {
const match = input.match(reLegacyAspeURI);
if (!match) return null;
const { hostname, keyId } = match.groups;
return { hostname, keyId: keyId.toUpperCase() };
}
export function getKeyId(fingerprint: string) {
return formatFingerprint(fingerprint)
.split(" ")
.slice(0, 4)
.join(" ");
}
export function formatFingerprint(fingerprint: string) {
return fingerprint
.toUpperCase()
.split(/([0-9A-F]{4})/g)
.filter(Boolean)
.join(" ");
}
export function calculateFingerprint(asp: string, alg: HashAlgorithm = "shake128", d: number = 160) {
const { jwk } = jose.decodeProtectedHeader(asp);
if (!["shake128", "shake256"].includes(alg)) {
throw new Error("error: unsupported algorithm");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// SPDX-License-Identifier: 0-BSD
export function bufferEquals(buffer1: ArrayBuffer, buffer2: ArrayBuffer) {
if (buffer1.byteLength !== buffer2.byteLength) return false;
let i = 0;
while (i < buffer1.byteLength) {
if (buffer1[i] !== buffer2[i]) return false;
i += 1;
}
return true;
}
export function splitBuffer(buffer: ArrayBuffer, chunkSize: number) {
const chunks = [];
let offset = 0;
while (offset < buffer.byteLength) {
const size = Math.min(chunkSize, buffer.byteLength - offset);
chunks.push(new Uint8Array(buffer, offset, size));
offset += size;
}
return chunks;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// SPDX-License-Identifier: 0-BSD
import { createPacketReader, createSignedPacketReader } from "https://esm.town/v/vladimyr/libarmor";
import { bufferEquals } from "https://esm.town/v/vladimyr/libbuffer";
import { verifyAsync } from "npm:@noble/ed25519";
import { base64 } from "npm:multiformats/bases/base64";
const KEY_ALG_LENGTH = 2;
const KEY_ID_LENGTH = 8;
const REOP_PUBLIC_KEY_START = "-----BEGIN REOP PUBLIC KEY-----";
const REOP_PUBLIC_KEY_END = "-----END REOP PUBLIC KEY-----";
const parseArmoredKey = createPacketReader({
packetStartMarker: REOP_PUBLIC_KEY_START,
packetEndMarker: REOP_PUBLIC_KEY_END,
});
const REOP_MESSAGE_START = "-----BEGIN REOP SIGNED MESSAGE-----";
const REOP_SIGNATURE_START = "-----BEGIN REOP SIGNATURE-----";
const REOP_MESSAGE_END = "-----END REOP SIGNED MESSAGE-----";
const parseAmoredMessage = createSignedPacketReader({
packetStartMarker: REOP_MESSAGE_START,
packetEndMarker: REOP_MESSAGE_END,
signatureStartMarker: REOP_SIGNATURE_START,
});
type PublicKey = ReturnType<typeof parsePublicKey>;
export type SignedMessage = ReturnType<typeof parseSignedMessage>;
export type SigningKey = PublicKey["signingKey"];
export function parsePublicKey(input: string) {
const publicKey = parseArmoredKey(input);
const publicKeyEncoded = publicKey
.split(/\r?\n/g)
.filter(line => !line.startsWith("ident:"))
.join("")
.trim();
return decodePublicKey(publicKeyEncoded);
}
export function decodePublicKey(input: string) {
input = input.replace(/^reop/, "");
return readPublicKeyPacket(base64.baseDecode(input));
}
export function parseSignedMessage(input: string) {
const { content, signature } = parseAmoredMessage(input);
const signatureEncoded = signature
.split(/\r?\n/g)
1
2
3
4
5
6
7
8
9
10
11
12
13
import ky from "npm:ky@1.2.0";
function resolveDidKey(did) {
return ky.get(`https://dev.uniresolver.io/1.0/identifiers/${did}`, {
headers: {
accept: "application/ld+json; profile=\"https://w3id.org/did-resolution\"",
},
}).json();
}
export function getResolver() {
return { key: resolveDidKey };
}
Fork
1
2
3
4
5
6
7
8
9
import { Hono } from "npm:hono@3.12.7";
const app = new Hono();
app.get("/:fepId{[A-za-z0-9]{4}}", (c) => {
const fepId = c.req.param("fepId").toLowerCase();
return c.redirect(`https://codeberg.org/fediverse/fep/src/branch/main/fep/${fepId}/fep-${fepId}.md`);
});
export default app.fetch;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import {
array,
boolean,
object,
parse,
string,
toCustom,
transform,
url,
} from "https://deno.land/x/valibot@v0.22.0/mod.ts";
import ky, { HTTPError } from "npm:ky@1.1.3";
import { joinURL } from "npm:ufo@1.3.2";
const API_URL = "https://crates.io/api/v1/";
const VersionSchema = transform(
object({
num: string(),
dl_path: string([toCustom(input => (new URL(input, API_URL)).href)]),
yanked: boolean(),
}),
({ num: version, dl_path: downloadUrl, ...other }) => ({ version, downloadUrl, ...other }),
);
const CrateInfoSchema = object({
versions: array(VersionSchema),
});
export async function getCrateInfo(crateName: string, version?: string) {
const createURL = joinURL(API_URL, `/crates/${crateName}`);
const crateInfo = parse(CrateInfoSchema, await ky.get(createURL).json());
const versions = crateInfo.versions.filter(it => !it.yanked);
if (!version) return versions.at(0);
return versions.find(it => it.version === version);
}
export { HTTPError };