initial commit

This commit is contained in:
Ubuntu 2024-08-21 15:00:40 +00:00
commit eaec14f4fb
14 changed files with 1987 additions and 0 deletions

7
.env.example Normal file
View file

@ -0,0 +1,7 @@
PREFIX=!
BASE_URL=https://example.com
OWNER_ID=@root:example.com
USER_ID=@bot:example.com
ACCESS_TOKEN=1234567890
DEVICE_ID=Bot-Device
LOG_CHANNEL=!1234567890:example.com

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.env
node_modules
data/*

41
commands/catgpt.js Normal file
View file

@ -0,0 +1,41 @@
var meows = [
"mreow",
"miau",
"mewo",
"maow",
"mrow",
"mrao",
"meow",
"mew",
"nya",
];
var emoticons = [
":3",
"^w^",
"=^w^=",
"-w-",
":333"
];
async function execute(client, event) {
var reply = "";
for(let i = 0;i < Math.random() * 15; i++)
reply += " " + meows.random();
reply += "!".repeat(Math.random() * 5);
if(Math.random() > 0.5) {
reply += " " + emoticons.random();
}
await client.reply(event, reply);
}
export default {
command: "catgpt",
name: "catgpt",
usage: "[prompt]",
desc: "advanced NAI-based language meowdel",
execute
}

22
commands/eval.js Normal file
View file

@ -0,0 +1,22 @@
import { encode } from "html-entities";
import util from "util";
function execute(client, event, args) {
var c = "";
try {
var result = eval(args);
c = util.format(result).replaceAll(process.env.ACCESS_TOKEN, "*".repeat(process.env.ACCESS_TOKEN.length));
} catch(err) {
c = util.format(err);
}
client.reply(event, c, `<pre><code>${encode(c)}</code></pre>`);
}
export default {
command: "eval",
name: "eval",
usage: "<code>",
owner: true,
desc: "Run JS code and reply with the result",
execute
}

55
commands/help.js Normal file
View file

@ -0,0 +1,55 @@
import { encode } from "html-entities";
function execute(client, event, args) {
var name = args.toLowerCase();
var command = client.commands.filter(c=>(c.name.toLowerCase()==name || c.command.toLowerCase()==name))[0];
var module = client.modules.filter(m=>m.name.toLowerCase()==name)[0];
var specific = command ?? module ?? false;
if(args != "") {
if(!command && !module) {
client.reply(event, `Section "${args}" not found.\nRun "${process.env.PREFIX}help" for a list of commands and modules.`);
return;
}
if(module) {
var reply = module.name + "\n";
reply += module.desc;
var replyHTML = `<b>${encode(module.name)}</b><br>` +
`<i>${encode(module.desc)}</i>`;
client.reply(event, reply, replyHTML);
return;
}
var reply = command.name + "\n" +
command.desc + "\n\n" +
command.usage;
var replyHTML = `<b>${encode(command.name)}</b><br>` +
`<i>${encode(command.desc)}</i>` +
`<br><code>${encode(command.usage)}</code>`;
client.reply(event, reply, replyHTML);
return;
}
var reply = `commands: ${client.commands.map(m => m.name).join(", ")}\n` +
`modules: ${client.modules.map(m => m.name).join(", ")}\n` +
`run ${process.env.PREFIX}help <module/command> for more information.`;
var replyHTML = `commands: <code>${client.commands.map(m => encode(m.name)).join("</code>, <code>")}</code><br>` +
`modules: <code>${client.modules.map(m => encode(m.name)).join("</code>, <code>")}</code><br>` +
encode(`run ${process.env.PREFIX}help <module/command> for more information.`);
client.reply(event, reply, replyHTML);
}
export default {
command: "help",
name: "help",
desc: "show list of commands and modules",
execute
}

16
commands/info.js Normal file
View file

@ -0,0 +1,16 @@
function execute(client, event, args) {
var info = `${client.name}\n\n`;
info += `Commands: ${client.commands.length}\n`;
info += `Modules: ${client.commands.length}\n`;
info += `Prefix: ${process.env.PREFIX} (you can also mention the bot!)\n`;
info += `Owner: ${process.env.OWNER_ID}`;
client.reply(event, info);
}
export default {
command: "info",
name: "info",
desc: "show general info about the bot",
execute
}

13
commands/restart.js Normal file
View file

@ -0,0 +1,13 @@
import { exec } from "node:child_process";
function execute(client, event, args) {
exec("systemctl --user restart possumbot");
}
export default {
command: "restart",
name: "restart",
owner: true,
desc: "restarts the bot",
execute
}

126
index.js Normal file
View file

@ -0,0 +1,126 @@
import { encode } from "html-entities";
import * as sdk from "matrix-js-sdk";
import sdkExt from "./lib/ext.js";
import { resolve } from "node:path";
import fs from "node:fs";
import util from "util";
import fetch from "node-fetch";
global.fetch = fetch;
import Olm from "@matrix-org/olm";
global.Olm = Olm;
import env from "dotenv";
env.config();
const client = sdk.createClient({
baseUrl: process.env.BASE_URL,
accessToken: process.env.ACCESS_TOKEN,
deviceId: process.env.DEVICE_ID,
userId: process.env.USER_ID
});
sdkExt(client);
var prefixes = [process.env.PREFIX];
client.initialized = false;
client.modules = [];
client.commands = [];
for (const file of fs.readdirSync(resolve("modules"))) {
try {
if(file.startsWith(".")) continue;
var module = (await import(resolve("modules", file))).default;
client.modules.push(module);
client.log("[load:modules]", `loaded ${module.name}`);
} catch (err) {
client.error("[load:modules]", `failed to load "${file}":\n`, err);
}
}
for (const file of fs.readdirSync(resolve("commands"))) {
try {
if(file.startsWith(".")) continue;
var command = (await import(resolve("commands", file))).default;
command.usage = process.env.PREFIX + command.command + (command.usage ? " " + command.usage : '');
client.commands.push(command);
client.log("[load:commands]", `loaded ${command.name}`);
} catch (err) {
client.error("[load:commands]", `failed to load "${file}":`, err);
}
}
function doModule(client, event) {
client.modules.forEach(m => {
if(!m) return;
m.hooks?.message(client, event);
});
}
function doCommand(client, event, cmd, args) {
var command;
command = client.commands.filter(c=>c.command==cmd)[0];
if(!command)
return false;
if(command.owner && event.sender.userId != process.env.OWNER_ID) {
client.reply(event, "nuh uh", '<img src="mxc://possum.city/cu5mPRM1ITiJZQRgAyzJXDz22jN6TBo9" alt="nuh uh" />');
return true;
}
command.execute(client, event, args);
return true;
}
client.once("sync", async function (state, prevState, data) {
switch (state) {
case "PREPARED":
client.name = client._store.users[client.credentials.userId].displayName;
client.log("[sdk:sync]", "connected:", client.name);
prefixes.push(client.name + ": ");
prefixes.push(client.name + " ");
client.initialized = true;
break;
}
});
client.on(sdk.RoomEvent.Timeline, async function (event, room, toStartOfTimeline) {
if (!client.initialized || toStartOfTimeline || event.getType() !== "m.room.message" || event.sender.userId == client.credentials.userId) {
return;
}
if(event.event.content["m.relates_to"] != null)
event.event.content.body = event.event.content.body.substring(event.event.content.body.indexOf("\n\n") + 2);
var content = event.event.content.body;
var isCommand = false;
for(const prefix of prefixes) {
if(content.startsWith(prefix)) {
isCommand = true;
content = content.substring(prefix.length);
break;
}
}
if(!isCommand) {
doModule(client, event);
} else {
var args = content.split(/\s/g);
var cmd = args.shift();
args = content.substring(cmd.length + 1);
if(!doCommand(client, event, cmd, args)) {
client.reply(event, `Command "${cmd}" not found.\nRun "${process.env.PREFIX}help" for a list of commands.`);
}
}
});
client.on("Room.myMembership", function (room, membership, prevMembership) {
if (membership === "invite") {
client.joinRoom(room.roomId).then(function () {
client.log("[client:autojoin] joined %s", room.roomId);
});
}
});
//await client.initCrypto();
await client.startClient();

107
lib/ext.js Normal file
View file

@ -0,0 +1,107 @@
import { encode } from "html-entities";
import forceSync from 'sync-rpc';
import { JSDOM } from "jsdom";
import util from "util";
import fs from "fs";
Array.prototype.random = function(){return this[Math.floor(Math.random() * this.length)]}
var cache = {};
var log = null;
export default function(client) {
client.logEvent = null;
client.cache = {
get: (key) => {
return cache[key] ?? null;
},
set: (key, value) => {
cache[key] = value;
fs.writeFileSync("data/cache.json", JSON.stringify(cache));
}
}
client.reply = function(event, text, html) {
var mcontent = {
body: "> <" + event.sender.userId + "> " + event.event.content.body.split("\n")[0] + "\n\n" + text.trim(),
msgtype: "m.notice",
"m.relates_to": {
"m.in_reply_to": {
"event_id": event.event.event_id
}
}
};
if(html) {
mcontent = {
format: "org.matrix.custom.html",
formatted_body: "<mx-reply><blockquote><a href=\"https://m.posm.gay/#/" + event.event.room_id + "/" + event.event.event_id + "?via=possum.city\">In reply to</a> <a href=\"https://m.posm.gay/#/" + event.sender.userId + "\">" + event.sender.userId + "</a><br>" + encode(event.event.content.body.split("\n")[0]) + "</blockquote></mx-reply>" + html.trim(),
...mcontent
}
}
client.sendEvent(event.event.room_id, "m.room.message", mcontent, "", (err, res) => {
console.log(err);
});
}
client.logData = [];
client.startup = new Date();
client.updateLog = async function() {
if(log && (log.body == client.logData.join("\n") || log["m.new_content"]?.body == client.logData.join("\n"))) {
setTimeout(client.updateLog, 1000);
return;
}
var mcontent = {
format: "org.matrix.custom.html",
formatted_body: `<pre><code>[client:logger] started: ${client.startup}\n${encode(client.logData.join("\n"))}</code></pre>`,
body: client.logData.join("\n"),
msgtype: "m.notice"
}
if(!client.logEvent) {
log = mcontent;
} else {
log["m.new_content"] = mcontent;
log["m.relates_to"] = {
rel_type: "m.replace",
event_id: client.logEvent
}
}
var x = await client.sendEvent(process.env.LOG_CHANNEL, "m.room.message", log, "", (err, res) => {
console.log("[client:log]", res);
console.error(err);
});
client.logEvent = client.logEvent ?? x.event_id;
setTimeout(client.updateLog, 1000);
}
client.log = function() {
var data = util.format.apply(null, arguments);
client.logData.push(data);
console.log(data);
}
client.error = client.log;
if(fs.existsSync("data/cache.json")) {
var data = fs.readFileSync("data/cache.json");
try {
cache = JSON.parse(data);
client.log("[sdkext:cache]", 'loaded', Object.keys(cache).length, 'cache entries!');
} catch(err) {
client.error("[sdkext:cache]", err);
}
}
process.on('uncaughtException', function (err) {
console.error('[core:err]', err);
});
client.updateLog();
}

597
lib/fedimbed.js Normal file
View file

@ -0,0 +1,597 @@
import httpSignature from "@peertube/http-signature";
import path from "node:path";
import fs from "node:fs";
const FRIENDLY_USERAGENT = "PossumBot/1.0 (+https://bot.possum.city/)";
const URLS_REGEX = /(?:\s|^|\]\()(\|\|\s*)?(https?:\/\/[^\s<]+[^<.,:;"'\]|)\s])(\s*\)?\|\||\s*[\S]*?\))?/g;
const PATH_REGEX = {
mastodon: /^\/@(.+?)\/(\d+)\/?/,
mastodon2: /^\/(.+?)\/statuses\/\d+\/?/,
pleroma: /^\/objects\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/?/,
pleroma2: /^\/notice\/[A-Za-z0-9]+\/?/,
misskey: /^\/notes\/[a-z0-9]+\/?/,
gotosocial: /^\/@(.+?)\/statuses\/[0-9A-Z]+\/?/,
lemmy: /^\/post\/\d+\/?/,
honk: /^\/u\/(.+?)\/h\/(.+?)\/?/,
cohost: /^\/[A-Za-z0-9]+\/post\/\d+-[A-Za-z0-9-]+\/?/,
};
const PLATFORM_COLORS = {
mastodon: 0x2791da,
pleroma: 0xfba457,
akkoma: 0x593196,
misskey: 0x99c203,
calckey: 0x31748f,
firefish: 0xf07a5b, // YCbCr interpolated from the two logo colors
gotosocial: 0xff853e,
lemmy: 0x14854f,
birdsitelive: 0x1da1f2,
iceshrimp: 0x8e82f9, // YCbCr interpolated as the accent color is a gradient
cohost: 0x83254f,
};
const domainCache = new Map();
domainCache.set("cohost.org", "cohost"); // no nodeinfo
async function resolvePlatform(url) {
const urlObj = new URL(url);
if(domainCache.has(urlObj.hostname)) return domainCache.get(urlObj.hostname);
const res = await fetch(urlObj.origin + "/.well-known/nodeinfo", {
headers: {"User-Agent": FRIENDLY_USERAGENT},
}).then((res) => res.text());
if(!res.startsWith("{")) {
console.error("[fedimbed]", `No nodeinfo for "${urlObj.hostname}"???`);
domainCache.set(urlObj.hostname, null);
return null;
}
const probe = JSON.parse(res);
if(!probe?.links) {
console.error("[fedimbed]", `No nodeinfo for "${urlObj.hostname}"???`);
domainCache.set(urlObj.hostname, null);
return null;
}
const nodeinfo = await fetch(probe.links[probe.links.length - 1].href, {
headers: {"User-Agent": FRIENDLY_USERAGENT},
}).then((res) => res.json());
if(!nodeinfo?.software?.name) {
console.error("[fedimbed]", `Got nodeinfo for "${urlObj.hostname}", but missing software name.`);
domainCache.set(urlObj.hostname, null);
return null;
}
domainCache.set(urlObj.hostname, nodeinfo.software.name);
return nodeinfo.software.name;
}
const keyId = "https://bot.possum.city/actor#main-key";
const privKey = fs.readFileSync("data/private.pem");
async function signedFetch(url, options) {
const urlObj = new URL(url);
const headers = {
host: urlObj.host,
date: new Date().toUTCString(),
};
const headerNames = ["(request-target)", "host", "date"];
httpSignature.sign(
{
getHeader: (name) => headers[name.toLowerCase()],
setHeader: (name, value) => (headers[name] = value),
method: options.method ?? "GET",
path: urlObj.pathname,
},
{
keyId,
key: privKey,
headers: headerNames,
authorizationHeaderName: "signature",
}
);
options.headers = Object.assign(headers, options.headers ?? {});
return await fetch(url, options);
}
async function processUrl(url) {
let spoiler = false;
let invalidUrl = false;
let urlObj;
try {
urlObj = new URL(url);
} catch(err) {
console.error("[fedimbed]", err);
invalidUrl = true;
}
if(invalidUrl) return {};
// some lemmy instances have old reddit frontend subdomains
// but these frontends are just frontends and dont actually expose the API
if(urlObj.hostname.startsWith("old.")) {
urlObj.hostname = urlObj.hostname.replace("old.", "");
url = urlObj.href;
}
let platform = (await resolvePlatform(url)) ?? "<no nodeinfo>";
let color = PLATFORM_COLORS[platform];
let platformName = platform
.replace("gotosocial", "GoToSocial")
.replace("birdsitelive", '"Twitter" (BirdsiteLive)')
.replace(/^(.)/, (_, c) => c.toUpperCase())
.replace("Cohost", "cohost");
const files = [];
let content,
cw,
author,
timestamp,
title,
poll,
emotes = [],
sensitive = false;
// Fetch post
let rawPostData;
try {
rawPostData = await signedFetch(url, {
headers: {
"User-Agent": FRIENDLY_USERAGENT,
Accept: 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
},
}).then((res) => res.text());
} catch (err) {
console.error("[fedimbed]", `Failed to signed fetch "${url}", retrying unsigned: ${err}`);
}
if(!rawPostData) {
try {
rawPostData = await fetch(url, {
headers: {
"User-Agent": FRIENDLY_USERAGENT,
Accept: 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
},
}).then((res) => res.text());
} catch (err) {
console.error("[fedimbed]", `Failed to fetch "${url}": ${err}`);
}
}
let postData;
if(rawPostData?.startsWith("{")) {
try {
postData = JSON.parse(rawPostData);
} catch (err) {
console.error("[fedimbed]", `Failed to decode JSON for "${url}": ${err}\n "${rawPostData}"`);
}
} else {
console.warn("[fedimbed]", `Got non-JSON for "${url}": ${rawPostData}`);
}
if(postData?.error) {
console.error("[fedimbed]", `Received error for "${url}": ${postData.error}`);
console.error("[fedimbed]", postData);
return postData;
}
if(!postData) {
// We failed to get post.
// Assume it was due to AFM or forced HTTP signatures and use MastoAPI
// Follow redirect from /object since we need the ID from /notice
if(PATH_REGEX.pleroma.test(urlObj.pathname)) {
url = await signedFetch(url, {
method: "HEAD",
headers: {
"User-Agent": FRIENDLY_USERAGENT,
},
redirect: "manual",
}).then((res) => res.headers.get("location"));
if(url.startsWith("/")) {
url = urlObj.origin + url;
}
urlObj = new URL(url);
}
let redirUrl;
const options = {};
const headers = {};
if(PATH_REGEX.pleroma2.test(urlObj.pathname)) {
redirUrl = url.replace("notice", "api/v1/statuses");
} else if(PATH_REGEX.mastodon.test(urlObj.pathname)) {
const postId = urlObj.pathname.match(PATH_REGEX.mastodon)?.[2];
redirUrl = urlObj.origin + "/api/v1/statuses/" + postId;
} else if(PATH_REGEX.mastodon2.test(urlObj.pathname)) {
redirUrl = url.replace(/^\/(.+?)\/statuses/, "/api/v1/statuses");
} else if(PATH_REGEX.misskey.test(urlObj.pathname)) {
let noteId = url.split("/notes/")[1];
if(noteId.indexOf("/") > -1) {
noteId = noteId.split("/")[0];
} else if(noteId.indexOf("?") > -1) {
noteId = noteId.split("?")[0];
} else if(noteId.indexOf("#") > -1) {
noteId = noteId.split("#")[0];
}
console.log("[fedimbed]", "Misskey post ID: " + noteId);
redirUrl = urlObj.origin + "/api/notes/show/";
options.method = "POST";
options.body = JSON.stringify({noteId});
headers["Content-Type"] = "application/json";
} else {
console.error("[fedimbed]", `Missing MastoAPI replacement for "${platform}"`);
}
if(redirUrl) {
console.log(
"[fedimbed]",
`Redirecting "${url}" to "${redirUrl}": ${JSON.stringify(options)}, ${JSON.stringify(headers)}`
);
let rawPostData2;
try {
rawPostData2 = await signedFetch(
redirUrl,
Object.assign(options, {
headers: Object.assign(headers, {
"User-Agent": FRIENDLY_USERAGENT,
}),
})
).then((res) => res.text());
} catch (err) {
console.error("[fedimbed]", `Failed to signed fetch "${url}" via MastoAPI, retrying unsigned: ${err}`);
}
if(!rawPostData2) {
try {
rawPostData2 = await signedFetch(
redirUrl,
Object.assign(options, {
headers: Object.assign(headers, {
"User-Agent": FRIENDLY_USERAGENT,
}),
})
).then((res) => res.text());
} catch (err) {
console.error("[fedimbed]", `Failed to fetch "${url}" via MastoAPI: ${err}`);
}
}
let postData2;
if(rawPostData2?.startsWith("{")) {
postData2 = JSON.parse(rawPostData2);
} else {
console.warn("[fedimbed]", `Got non-JSON for "${url}" via MastoAPI: ${rawPostData2}`);
}
if(!postData2) {
console.warn("[fedimbed]", `Bailing trying to re-embed "${url}": Failed to get post from normal and MastoAPI.`);
} else if(postData2.error) {
console.error(
"[fedimbed]",
`Bailing trying to re-embed "${url}", MastoAPI gave us error: ${JSON.stringify(postData2.error)}`
);
} else {
cw = postData2.spoiler_warning ?? postData2.spoiler_text ?? postData2.cw;
content =
postData2.akkoma?.source?.content ??
postData2.pleroma?.content?.["text/plain"] ??
postData2.text ??
postData2.content;
author = {
name:
postData2.account?.display_name ??
postData2.account?.username ??
postData2.user?.name ??
postData2.user?.username,
handle:
postData2.account?.fqn ?? `${postData2.account?.username ?? postData2.user?.username}@${urlObj.hostname}`,
url: postData2.account?.url ?? `${urlObj.origin}/@${postData2.account?.username ?? postData2.user?.username}`,
avatar: postData2.account?.avatar ?? postData2.user?.avatarUrl,
};
timestamp = postData2.created_at ?? postData2.createdAt;
emotes = postData2.emojis.filter((x) => !x.name.endsWith("@.")).map((x) => ({name: `:${x.name}:`, url: x.url}));
sensitive = postData2.sensitive;
const attachments = postData2.media_attachments ?? postData2.files;
if(attachments) {
for(const attachment of attachments) {
const contentType = await fetch(attachment.url, {
method: "HEAD",
}).then((res) => res.headers.get("Content-Type"));
if(contentType) {
if(contentType.startsWith("image/") || contentType.startsWith("video/") || contentType.startsWith("audio/")) {
files.push({
url: attachment.url,
desc: attachment.description ?? attachment.comment,
type: contentType,
});
}
} else {
const type = attachment.type?.toLowerCase();
const fileType =
attachment.pleroma?.mime_type ?? type.indexOf("/") > -1
? type
: type +
"/" +
(url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? type == "image"
? "png"
: type == "video"
? "mp4"
: "mpeg");
if(type.startsWith("image") || type.startsWith("video") || type.startsWith("audio")) {
files.push({
url: attachment.url,
desc: attachment.description ?? attachment.comment,
type: fileType,
});
}
}
}
}
if(postData2.sensitive && attachments.length > 0) {
spoiler = true;
}
if(postData2.poll) {
poll = {
end: new Date(postData2.poll.expires_at),
total: postData2.poll.votes_count,
options: postData2.poll.options.map((o) => ({
name: o.title,
count: o.votes_count,
})),
};
}
}
}
} else {
if(postData.id) {
const realUrlObj = new URL(postData.id);
if(realUrlObj.origin != urlObj.origin) {
platform = await resolvePlatform(postData.id);
color = PLATFORM_COLORS[platform];
platformName = platform.replace("gotosocial", "GoToSocial").replace(/^(.)/, (_, c) => c.toUpperCase());
url = postData.id;
}
}
content = postData._misskey_content ?? postData.source?.content ?? postData.content;
cw = postData.summary;
timestamp = postData.published;
sensitive = postData.sensitive;
if(postData.tag) {
let tag = postData.tag;
// gts moment
if(!Array.isArray(tag)) tag = [tag];
emotes = tag.filter((x) => !!x.icon).map((x) => ({name: x.name, url: x.icon.url}));
}
// NB: gts doesnt send singular attachments as array
const attachments = Array.isArray(postData.attachment) ? postData.attachment : [postData.attachment];
for(const attachment of attachments) {
if(!attachment) continue;
if(attachment.mediaType) {
if(attachment.mediaType.startsWith("video/") || attachment.mediaType.startsWith("image/") || attachment.mediaType.startsWith("audio/")) {
files.push({
url: attachment.url,
desc: attachment.name ?? attachment.description ?? attachment.comment,
type: attachment.mediaType,
});
}
} else if(attachment.url) {
const contentType = await fetch(attachment.url, {
method: "HEAD",
}).then((res) => res.headers.get("Content-Type"));
if(contentType) {
if(contentType.startsWith("image/") || contentType.startsWith("video/") || contentType.startsWith("audio/")) {
files.push({
url: attachment.url,
desc: attachment.name ?? attachment.description ?? attachment.comment,
type: contentType,
});
}
} else {
const type = attachment.type?.toLowerCase();
const fileType =
type.indexOf("/") > -1
? type
: type +
"/" +
(url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? type == "image" ? "png" : type == "video" ? "mp4" : "mpeg");
if(type.startsWith("image") || type.startsWith("video") || type.startsWith("audio")) {
files.push({
url: attachment.url,
desc: attachment.name ?? attachment.description ?? attachment.comment,
type: fileType,
});
}
}
} else {
console.warn("[fedimbed]", `Unhandled attachment structure! ${JSON.stringify(attachment)}`);
}
}
if(postData.sensitive && attachments.length > 0) {
spoiler = true;
}
if(postData.image?.url) {
const imageUrl = new URL(postData.image.url);
const contentType = await fetch(postData.image.url, {
method: "HEAD",
}).then((res) => res.headers.get("Content-Type"));
files.push({
url: postData.image.url,
desc: "",
type: contentType ?? "image/" + imageUrl.pathname.substring(imageUrl.pathname.lastIndexOf(".") + 1),
});
}
if(postData.name) title = postData.name;
// Author data is not sent with the post with AS2
const authorData = await signedFetch(postData.actor ?? postData.attributedTo, {
headers: {
"User-Agent": FRIENDLY_USERAGENT,
Accept: 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
},
})
.then((res) => res.json())
.catch((err) => {
// only posts can be activity+json right now, reduce log spam
if(platform !== "cohost") console.error("[fedimbed]", `Failed to get author for "${url}": ${err}`);
});
if(authorData) {
const authorUrlObj = new URL(authorData.url ?? authorData.id);
author = {
name: authorData.name,
handle: `${authorData.preferredUsername}@${authorUrlObj.hostname}`,
url: authorData.url,
avatar: authorData.icon?.url,
};
} else {
// bootleg author, mainly for cohost
const authorUrl = postData.actor ?? postData.attributedTo;
const authorUrlObj = new URL(authorUrl);
const name = authorUrlObj.pathname.substring(authorUrlObj.pathname.lastIndexOf("/") + 1);
author = {
name,
handle: `${name}@${authorUrlObj.hostname}`,
url: authorUrl,
};
}
if(postData.endTime && postData.oneOf && postData.votersCount) {
poll = {
end: new Date(postData.endTime),
total: postData.votersCount,
options: postData.oneOf.map((o) => ({
name: o.name,
count: o.replies.totalItems,
})),
};
}
}
// We could just continue without author but it'd look ugly and be confusing.
if(!author) {
console.warn("[fedimbed]", `Bailing trying to re-embed "${url}": Failed to get author.`);
return {};
}
// Start constructing embed
content = content ?? "";
cw = cw ?? "";
//content = htmlToMarkdown(content);
for(const emote of emotes) {
//content = content.replaceAll(emote.name, `[${emote.name}](${emote.url})`);
content = content.replaceAll(emote.name, `<img src="${emote.url}" alt="${emote.name}" />`);
}
//cw = htmlToMarkdown(cw);
let desc = cw;
if((cw != "" || sensitive) && files.length) {
desc += "<span data-mx-spoiler>" + content + "</span>";
} else {
desc = content;
}
const user = author.name ? `${author.name} (${author.handle})` : author.handle;
const baseEmbed = {
color,
url,
timestamp,
description: desc,
title: title ?? user,
author: title
? {
name: user,
url: author.url,
}
: null,
footer: {
text: platformName,
},
thumbnail: {
url: author.avatar,
},
fields: [],
};
if(poll) {
baseEmbed.fields.push({
name: "Poll",
value:
poll.options
.map((o) => {
const percent = o.count / poll.total;
const bar = Math.round(percent * 30);
return `**${o.name}** (${o.count}, ${Math.round(percent * 100)}%)\n\`[${"=".repeat(bar)}${" ".repeat(
30 - bar
)}]\``;
})
.join("\n\n") + `\n\n${poll.total} votes \u2022 Ends <t:${Math.floor(poll.end.getTime() / 1000)}:R>`,
});
}
const embeds = [baseEmbed];
return {
content:
cw != "" && (files.length > 0)
? `:warning: ${cw} <span data-mx-spoiler>${url}</span>`
: spoiler
? `<span data-mx-spoiler>${url}</span>`
: "",
embeds,
files
};
}
async function fedimbed(msg) {
if(URLS_REGEX.test(msg)) {
const urls = msg.match(URLS_REGEX);
for(let url of urls) {
let urlObj;
try {
urlObj = new URL(url);
} catch {
console.error("[fedimbed]", `Invalid URL "${url}"`);
// noop
}
for(const service of Object.keys(PATH_REGEX)) {
const regex = PATH_REGEX[service];
if(urlObj && regex.test(urlObj.pathname)) {
console.log("[fedimbed]", `Hit "${service}" for "${url}", processing now.`);
try {
const response = await processUrl(url);
return response;
} catch (err) {
console.error("[fedimbed]", `Error processing "${url}":\n` + err.stack);
}
break;
}
}
}
}
}
export default fedimbed;

92
modules/fedimbed.js Normal file
View file

@ -0,0 +1,92 @@
import fedimbed from "../lib/fedimbed.js";
import { encode } from "html-entities";
import { JSDOM } from "jsdom";
import util from "util";
async function onMessage(client, event) {
const embed = await fedimbed(event.event.content.body);
if(!embed) return;
const dom = new JSDOM("<!DOCTYPE html><body></body>");
const document = dom.window.document;
var quote = document.createElement("blockquote");
if(!embed.embeds) {
var c = util.format(embed);
client.reply(event, c, `<pre><code>${encode(c)}</code></pre>`);
return;
}
for(const emb of embed.embeds) {
var link = document.createElement("a");
link.href = emb.url;
if(emb.thumbnail && emb.thumbnail.url) {
var avatar = document.createElement("img");
var matrixUrl = client.cache.get("fedimbed_" + emb.thumbnail.url);
if(!matrixUrl) {
const buffer = await fetch(emb.thumbnail.url, {
headers: {
"User-Agent": "PossumBot/1.0 (+https://bot.possum.city/)",
},
}).then((res) => res.arrayBuffer()).then((buf) => Buffer.from(buf));
const uploadResponse = await client.uploadContent(buffer, { rawResponse: false });
matrixUrl = uploadResponse.content_uri;
client.cache.set("fedimbed_" + emb.thumbnail.url, matrixUrl);
}
avatar.src = matrixUrl;
avatar.height = "16";
link.appendChild(avatar);
}
var linkText = document.createElement("span");
linkText.innerHTML = ((emb.thumbnail?.url) ? "&nbsp" : "") + emb.title;
link.appendChild(linkText);
quote.appendChild(link);
var text = document.createElement("p");
text.innerHTML = emb.description;
quote.appendChild(text);
}
for(const file of embed.files) {
var matrixUrl = client.cache.get("fedimbed_" + file.url);
if(!matrixUrl) {
const buffer = await fetch(file.url, {
headers: {
"User-Agent": "PossumBot/1.0 (+https://bot.possum.city/)",
},
}).then((res) => res.arrayBuffer()).then((buf) => Buffer.from(buf));
const uploadResponse = await client.uploadContent(buffer, { rawResponse: false });
matrixUrl = uploadResponse.content_uri;
client.cache.set("fedimbed_" + file.url, matrixUrl);
}
var media;
switch(file.type.split("/")[0]) {
case "audio":
media = document.createElement("audio");
break;
case "video":
media = document.createElement("video");
break;
case "image":
media = document.createElement("img");
break;
}
media.src = matrixUrl;
media.alt = file.desc;
quote.appendChild(media);
}
document.body.appendChild(quote);
let x = document.createElement("small");
x.innerHTML = "Powered by <a href=\"https://gitdab.com/Cynosphere/HiddenPhox/src/branch/rewrite/src/modules/fedimbed.js\">HF Fedimbed</a>";
document.body.appendChild(x);
client.reply(event, "This message uses HTML, which your client does not support.", document.body.outerHTML);
}
export default {
name: "fedimbed",
desc: "embed fediverse post contents",
hooks: {
message: onMessage
}
}

45
modules/meow.js Normal file
View file

@ -0,0 +1,45 @@
var meows = [
"mreow",
"miau",
"mewo",
"maow",
"mrow",
"mrao",
"meow",
"mew",
"nya",
];
var emoticons = [
":3",
"^w^",
"=^w^=",
"-w-",
":333"
];
async function onMessage(client, event) {
if(event.event.content["m.new_content"] != null) return;
for(const meow of meows) {
if(event.event.content.body.toLowerCase().includes(meow)) {
var reply = meows.random();
reply += "!".repeat(Math.random()*5)
if(Math.random() > 0.5) {
reply += " " + emoticons.random();
}
client.reply(event, reply);
break;
}
}
}
export default {
name: "meow",
desc: ":33",
hooks: {
message: onMessage
}
}

20
package.json Normal file
View file

@ -0,0 +1,20 @@
{
"name": "possumbot",
"type": "module",
"version": "0.0.0",
"description": "General purpose Matrix bot",
"main": "index.js",
"author": "Ashley Graves",
"license": "GPL-v3",
"dependencies": {
"@matrix-org/olm": "^3.2.15",
"@peertube/http-signature": "^1.7.0",
"cli-color": "^2.0.4",
"dotenv": "^16.4.5",
"html-entities": "^2.5.2",
"jsdom": "^24.1.1",
"matrix-js-sdk": "^32.0.0",
"node-fetch": "^3.3.2",
"sync-rpc": "^1.3.6"
}
}

843
pnpm-lock.yaml Normal file
View file

@ -0,0 +1,843 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
'@matrix-org/olm':
specifier: ^3.2.15
version: 3.2.15
'@peertube/http-signature':
specifier: ^1.7.0
version: 1.7.0
cli-color:
specifier: ^2.0.4
version: 2.0.4
dotenv:
specifier: ^16.4.5
version: 16.4.5
html-entities:
specifier: ^2.5.2
version: 2.5.2
jsdom:
specifier: ^24.1.1
version: 24.1.1
matrix-js-sdk:
specifier: ^32.0.0
version: 32.4.0
node-fetch:
specifier: ^3.3.2
version: 3.3.2
sync-rpc:
specifier: ^1.3.6
version: 1.3.6
packages:
'@babel/runtime@7.25.0':
resolution: {integrity: sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==}
engines: {node: '>=6.9.0'}
'@matrix-org/matrix-sdk-crypto-wasm@4.10.0':
resolution: {integrity: sha512-zOqKVAYPfzs6Hav/Km9F5xWwoQ0bxDuoUU0/121m03Fg2VnfcHk43TjKImZolFc7IlgXwVGoda9Pp9Z/eTVKJA==}
engines: {node: '>= 10'}
'@matrix-org/olm@3.2.15':
resolution: {integrity: sha512-S7lOrndAK9/8qOtaTq/WhttJC/o4GAzdfK0MUPpo8ApzsJEC0QjtwrkC3KBXdFP1cD1MXi/mlKR7aaoVMKgs6Q==}
'@peertube/http-signature@1.7.0':
resolution: {integrity: sha512-aGQIwo6/sWtyyqhVK4e1MtxYz4N1X8CNt6SOtCc+Wnczs5S5ONaLHDDR8LYaGn0MgOwvGgXyuZ5sJIfd7iyoUw==}
engines: {node: '>=0.10'}
'@types/events@3.0.3':
resolution: {integrity: sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==}
'@types/retry@0.12.0':
resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==}
agent-base@7.1.1:
resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==}
engines: {node: '>= 14'}
another-json@0.2.0:
resolution: {integrity: sha512-/Ndrl68UQLhnCdsAzEXLMFuOR546o2qbYRqCglaNHbjXrwG1ayTcdwr3zkSGOGtGXDyR5X9nCFfnyG2AFJIsqg==}
asn1@0.2.6:
resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==}
assert-plus@1.0.0:
resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==}
engines: {node: '>=0.8'}
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
base-x@4.0.0:
resolution: {integrity: sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==}
bcrypt-pbkdf@1.0.2:
resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==}
bs58@5.0.0:
resolution: {integrity: sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==}
cli-color@2.0.4:
resolution: {integrity: sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==}
engines: {node: '>=0.10'}
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
content-type@1.0.5:
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
engines: {node: '>= 0.6'}
core-util-is@1.0.2:
resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==}
cssstyle@4.0.1:
resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==}
engines: {node: '>=18'}
d@1.0.2:
resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==}
engines: {node: '>=0.12'}
dashdash@1.14.1:
resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==}
engines: {node: '>=0.10'}
data-uri-to-buffer@4.0.1:
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
engines: {node: '>= 12'}
data-urls@5.0.0:
resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
engines: {node: '>=18'}
debug@4.3.6:
resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
decimal.js@10.4.3:
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
dotenv@16.4.5:
resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==}
engines: {node: '>=12'}
ecc-jsbn@0.1.2:
resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==}
entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
es5-ext@0.10.64:
resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==}
engines: {node: '>=0.10'}
es6-iterator@2.0.3:
resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==}
es6-symbol@3.1.4:
resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==}
engines: {node: '>=0.12'}
es6-weak-map@2.0.3:
resolution: {integrity: sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==}
esniff@2.0.1:
resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==}
engines: {node: '>=0.10'}
event-emitter@0.3.5:
resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==}
events@3.3.0:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
ext@1.7.0:
resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==}
extsprintf@1.3.0:
resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==}
engines: {'0': node >=0.6.0}
fetch-blob@3.2.0:
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
engines: {node: ^12.20 || >= 14.13}
form-data@4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
formdata-polyfill@4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
get-port@3.2.0:
resolution: {integrity: sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==}
engines: {node: '>=4'}
getpass@0.1.7:
resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==}
html-encoding-sniffer@4.0.0:
resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
engines: {node: '>=18'}
html-entities@2.5.2:
resolution: {integrity: sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==}
http-proxy-agent@7.0.2:
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
engines: {node: '>= 14'}
https-proxy-agent@7.0.5:
resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==}
engines: {node: '>= 14'}
iconv-lite@0.6.3:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
is-potential-custom-element-name@1.0.1:
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
is-promise@2.2.2:
resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==}
jsbn@0.1.1:
resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==}
jsdom@24.1.1:
resolution: {integrity: sha512-5O1wWV99Jhq4DV7rCLIoZ/UIhyQeDR7wHVyZAHAshbrvZsLs+Xzz7gtwnlJTJDjleiTKh54F4dXrX70vJQTyJQ==}
engines: {node: '>=18'}
peerDependencies:
canvas: ^2.11.2
peerDependenciesMeta:
canvas:
optional: true
json-schema@0.4.0:
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
jsprim@1.4.2:
resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==}
engines: {node: '>=0.6.0'}
jwt-decode@4.0.0:
resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==}
engines: {node: '>=18'}
loglevel@1.9.1:
resolution: {integrity: sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==}
engines: {node: '>= 0.6.0'}
lru-queue@0.1.0:
resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==}
matrix-events-sdk@0.0.1:
resolution: {integrity: sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==}
matrix-js-sdk@32.4.0:
resolution: {integrity: sha512-mzWfF4rJaTFLDfkedqP2jh/i1v5pv6xRHPkAQLn1ytXi72TFKLlKQmjaNUXfQYkmriIYnGYYQwBXQeJgwaT8SQ==}
engines: {node: '>=18.0.0'}
matrix-widget-api@1.8.2:
resolution: {integrity: sha512-kdmks3CvFNPIYN669Y4rO13KrazDvX8KHC7i6jOzJs8uZ8s54FNkuRVVyiQHeVCSZG5ixUqW9UuCj9lf03qxTQ==}
memoizee@0.4.17:
resolution: {integrity: sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==}
engines: {node: '>=0.12'}
mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
next-tick@1.1.0:
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
node-fetch@3.3.2:
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
nwsapi@2.2.12:
resolution: {integrity: sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==}
oidc-client-ts@3.0.1:
resolution: {integrity: sha512-xX8unZNtmtw3sOz4FPSqDhkLFnxCDsdo2qhFEH2opgWnF/iXMFoYdBQzkwCxAZVgt3FT3DnuBY3k80EZHT0RYg==}
engines: {node: '>=18'}
p-retry@4.6.2:
resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==}
engines: {node: '>=8'}
parse5@7.1.2:
resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
psl@1.9.0:
resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==}
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
querystringify@2.2.0:
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
regenerator-runtime@0.14.1:
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
requires-port@1.0.0:
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
retry@0.13.1:
resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
engines: {node: '>= 4'}
rrweb-cssom@0.6.0:
resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==}
rrweb-cssom@0.7.1:
resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==}
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
saxes@6.0.0:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
engines: {node: '>=v12.22.7'}
sdp-transform@2.14.2:
resolution: {integrity: sha512-icY6jVao7MfKCieyo1AyxFYm1baiM+fA00qW/KrNNVlkxHAd34riEKuEkUe4bBb3gJwLJZM+xT60Yj1QL8rHiA==}
hasBin: true
sshpk@1.18.0:
resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==}
engines: {node: '>=0.10.0'}
hasBin: true
symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
sync-rpc@1.3.6:
resolution: {integrity: sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==}
timers-ext@0.1.8:
resolution: {integrity: sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==}
engines: {node: '>=0.12'}
tough-cookie@4.1.4:
resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
engines: {node: '>=6'}
tr46@5.0.0:
resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==}
engines: {node: '>=18'}
tweetnacl@0.14.5:
resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==}
type@2.7.3:
resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==}
unhomoglyph@1.0.6:
resolution: {integrity: sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg==}
universalify@0.2.0:
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
engines: {node: '>= 4.0.0'}
url-parse@1.5.10:
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
uuid@9.0.1:
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
hasBin: true
verror@1.10.0:
resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==}
engines: {'0': node >=0.6.0}
w3c-xmlserializer@5.0.0:
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
engines: {node: '>=18'}
web-streams-polyfill@3.3.3:
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
engines: {node: '>= 8'}
webidl-conversions@7.0.0:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
engines: {node: '>=12'}
whatwg-encoding@3.1.1:
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
engines: {node: '>=18'}
whatwg-mimetype@4.0.0:
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
engines: {node: '>=18'}
whatwg-url@14.0.0:
resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==}
engines: {node: '>=18'}
ws@8.18.0:
resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
xml-name-validator@5.0.0:
resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
engines: {node: '>=18'}
xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
snapshots:
'@babel/runtime@7.25.0':
dependencies:
regenerator-runtime: 0.14.1
'@matrix-org/matrix-sdk-crypto-wasm@4.10.0': {}
'@matrix-org/olm@3.2.15': {}
'@peertube/http-signature@1.7.0':
dependencies:
assert-plus: 1.0.0
jsprim: 1.4.2
sshpk: 1.18.0
'@types/events@3.0.3': {}
'@types/retry@0.12.0': {}
agent-base@7.1.1:
dependencies:
debug: 4.3.6
transitivePeerDependencies:
- supports-color
another-json@0.2.0: {}
asn1@0.2.6:
dependencies:
safer-buffer: 2.1.2
assert-plus@1.0.0: {}
asynckit@0.4.0: {}
base-x@4.0.0: {}
bcrypt-pbkdf@1.0.2:
dependencies:
tweetnacl: 0.14.5
bs58@5.0.0:
dependencies:
base-x: 4.0.0
cli-color@2.0.4:
dependencies:
d: 1.0.2
es5-ext: 0.10.64
es6-iterator: 2.0.3
memoizee: 0.4.17
timers-ext: 0.1.8
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
content-type@1.0.5: {}
core-util-is@1.0.2: {}
cssstyle@4.0.1:
dependencies:
rrweb-cssom: 0.6.0
d@1.0.2:
dependencies:
es5-ext: 0.10.64
type: 2.7.3
dashdash@1.14.1:
dependencies:
assert-plus: 1.0.0
data-uri-to-buffer@4.0.1: {}
data-urls@5.0.0:
dependencies:
whatwg-mimetype: 4.0.0
whatwg-url: 14.0.0
debug@4.3.6:
dependencies:
ms: 2.1.2
decimal.js@10.4.3: {}
delayed-stream@1.0.0: {}
dotenv@16.4.5: {}
ecc-jsbn@0.1.2:
dependencies:
jsbn: 0.1.1
safer-buffer: 2.1.2
entities@4.5.0: {}
es5-ext@0.10.64:
dependencies:
es6-iterator: 2.0.3
es6-symbol: 3.1.4
esniff: 2.0.1
next-tick: 1.1.0
es6-iterator@2.0.3:
dependencies:
d: 1.0.2
es5-ext: 0.10.64
es6-symbol: 3.1.4
es6-symbol@3.1.4:
dependencies:
d: 1.0.2
ext: 1.7.0
es6-weak-map@2.0.3:
dependencies:
d: 1.0.2
es5-ext: 0.10.64
es6-iterator: 2.0.3
es6-symbol: 3.1.4
esniff@2.0.1:
dependencies:
d: 1.0.2
es5-ext: 0.10.64
event-emitter: 0.3.5
type: 2.7.3
event-emitter@0.3.5:
dependencies:
d: 1.0.2
es5-ext: 0.10.64
events@3.3.0: {}
ext@1.7.0:
dependencies:
type: 2.7.3
extsprintf@1.3.0: {}
fetch-blob@3.2.0:
dependencies:
node-domexception: 1.0.0
web-streams-polyfill: 3.3.3
form-data@4.0.0:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
formdata-polyfill@4.0.10:
dependencies:
fetch-blob: 3.2.0
get-port@3.2.0: {}
getpass@0.1.7:
dependencies:
assert-plus: 1.0.0
html-encoding-sniffer@4.0.0:
dependencies:
whatwg-encoding: 3.1.1
html-entities@2.5.2: {}
http-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.1
debug: 4.3.6
transitivePeerDependencies:
- supports-color
https-proxy-agent@7.0.5:
dependencies:
agent-base: 7.1.1
debug: 4.3.6
transitivePeerDependencies:
- supports-color
iconv-lite@0.6.3:
dependencies:
safer-buffer: 2.1.2
is-potential-custom-element-name@1.0.1: {}
is-promise@2.2.2: {}
jsbn@0.1.1: {}
jsdom@24.1.1:
dependencies:
cssstyle: 4.0.1
data-urls: 5.0.0
decimal.js: 10.4.3
form-data: 4.0.0
html-encoding-sniffer: 4.0.0
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.5
is-potential-custom-element-name: 1.0.1
nwsapi: 2.2.12
parse5: 7.1.2
rrweb-cssom: 0.7.1
saxes: 6.0.0
symbol-tree: 3.2.4
tough-cookie: 4.1.4
w3c-xmlserializer: 5.0.0
webidl-conversions: 7.0.0
whatwg-encoding: 3.1.1
whatwg-mimetype: 4.0.0
whatwg-url: 14.0.0
ws: 8.18.0
xml-name-validator: 5.0.0
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
json-schema@0.4.0: {}
jsprim@1.4.2:
dependencies:
assert-plus: 1.0.0
extsprintf: 1.3.0
json-schema: 0.4.0
verror: 1.10.0
jwt-decode@4.0.0: {}
loglevel@1.9.1: {}
lru-queue@0.1.0:
dependencies:
es5-ext: 0.10.64
matrix-events-sdk@0.0.1: {}
matrix-js-sdk@32.4.0:
dependencies:
'@babel/runtime': 7.25.0
'@matrix-org/matrix-sdk-crypto-wasm': 4.10.0
another-json: 0.2.0
bs58: 5.0.0
content-type: 1.0.5
jwt-decode: 4.0.0
loglevel: 1.9.1
matrix-events-sdk: 0.0.1
matrix-widget-api: 1.8.2
oidc-client-ts: 3.0.1
p-retry: 4.6.2
sdp-transform: 2.14.2
unhomoglyph: 1.0.6
uuid: 9.0.1
matrix-widget-api@1.8.2:
dependencies:
'@types/events': 3.0.3
events: 3.3.0
memoizee@0.4.17:
dependencies:
d: 1.0.2
es5-ext: 0.10.64
es6-weak-map: 2.0.3
event-emitter: 0.3.5
is-promise: 2.2.2
lru-queue: 0.1.0
next-tick: 1.1.0
timers-ext: 0.1.8
mime-db@1.52.0: {}
mime-types@2.1.35:
dependencies:
mime-db: 1.52.0
ms@2.1.2: {}
next-tick@1.1.0: {}
node-domexception@1.0.0: {}
node-fetch@3.3.2:
dependencies:
data-uri-to-buffer: 4.0.1
fetch-blob: 3.2.0
formdata-polyfill: 4.0.10
nwsapi@2.2.12: {}
oidc-client-ts@3.0.1:
dependencies:
jwt-decode: 4.0.0
p-retry@4.6.2:
dependencies:
'@types/retry': 0.12.0
retry: 0.13.1
parse5@7.1.2:
dependencies:
entities: 4.5.0
psl@1.9.0: {}
punycode@2.3.1: {}
querystringify@2.2.0: {}
regenerator-runtime@0.14.1: {}
requires-port@1.0.0: {}
retry@0.13.1: {}
rrweb-cssom@0.6.0: {}
rrweb-cssom@0.7.1: {}
safer-buffer@2.1.2: {}
saxes@6.0.0:
dependencies:
xmlchars: 2.2.0
sdp-transform@2.14.2: {}
sshpk@1.18.0:
dependencies:
asn1: 0.2.6
assert-plus: 1.0.0
bcrypt-pbkdf: 1.0.2
dashdash: 1.14.1
ecc-jsbn: 0.1.2
getpass: 0.1.7
jsbn: 0.1.1
safer-buffer: 2.1.2
tweetnacl: 0.14.5
symbol-tree@3.2.4: {}
sync-rpc@1.3.6:
dependencies:
get-port: 3.2.0
timers-ext@0.1.8:
dependencies:
es5-ext: 0.10.64
next-tick: 1.1.0
tough-cookie@4.1.4:
dependencies:
psl: 1.9.0
punycode: 2.3.1
universalify: 0.2.0
url-parse: 1.5.10
tr46@5.0.0:
dependencies:
punycode: 2.3.1
tweetnacl@0.14.5: {}
type@2.7.3: {}
unhomoglyph@1.0.6: {}
universalify@0.2.0: {}
url-parse@1.5.10:
dependencies:
querystringify: 2.2.0
requires-port: 1.0.0
uuid@9.0.1: {}
verror@1.10.0:
dependencies:
assert-plus: 1.0.0
core-util-is: 1.0.2
extsprintf: 1.3.0
w3c-xmlserializer@5.0.0:
dependencies:
xml-name-validator: 5.0.0
web-streams-polyfill@3.3.3: {}
webidl-conversions@7.0.0: {}
whatwg-encoding@3.1.1:
dependencies:
iconv-lite: 0.6.3
whatwg-mimetype@4.0.0: {}
whatwg-url@14.0.0:
dependencies:
tr46: 5.0.0
webidl-conversions: 7.0.0
ws@8.18.0: {}
xml-name-validator@5.0.0: {}
xmlchars@2.2.0: {}