From d7470aa7718cc1f3908a145dabeb4b110b55c4f2 Mon Sep 17 00:00:00 2001 From: John Holdun Date: Wed, 4 Oct 2023 01:13:01 -0700 Subject: [PATCH 1/4] Port actor data and account.json to application.db --- server.js | 16 +++---- src/activity-pub-db.js | 32 +++++++------ src/activitypub.js | 4 +- src/bookmarks-db.js | 20 ++++---- src/database.js | 88 ++++++++++++++++++++++++++++++++++ src/pages/about.hbs | 2 +- src/routes/activitypub/user.js | 3 +- src/routes/admin.js | 10 ++-- src/routes/bookmark.js | 8 +++- src/routes/core.js | 15 ++++-- src/schema.sql | 12 +++++ src/signature.js | 9 ++-- src/util.js | 26 +++++++--- 13 files changed, 188 insertions(+), 57 deletions(-) create mode 100644 src/database.js create mode 100644 src/schema.sql diff --git a/server.js b/server.js index c2d7137..2cebce8 100644 --- a/server.js +++ b/server.js @@ -4,10 +4,11 @@ import cors from 'cors'; import { create } from 'express-handlebars'; import escapeHTML from 'escape-html'; -import { domain, account, simpleLogger, actorInfo, replaceEmptyText } from './src/util.js'; +import { domain, simpleLogger, getActorInfo, replaceEmptyText } from './src/util.js'; import session, { isAuthenticated } from './src/session-auth.js'; import * as bookmarksDb from './src/bookmarks-db.js'; import * as apDb from './src/activity-pub-db.js'; +import * as db from './src/database.js'; import routes from './src/routes/index.js'; @@ -22,15 +23,16 @@ app.use(express.json()); app.use(express.json({ type: 'application/activity+json' })); app.use(session()); -app.use((req, res, next) => { +app.use(async (req, res, next) => { res.locals.loggedIn = req.session.loggedIn; + const { displayName } = await getActorInfo(); + res.locals.siteName = displayName; return next(); }); -app.set('site_name', actorInfo.displayName || 'Postmarks'); app.set('bookmarksDb', bookmarksDb); app.set('apDb', apDb); -app.set('account', account); +app.set('db', db); app.set('domain', domain); app.disable('x-powered-by'); @@ -61,12 +63,6 @@ const hbs = create({ const returnText = escapeHTML(text); return returnText?.replace('\n', '
'); }, - siteName() { - return app.get('site_name'); - }, - account() { - return app.get('account'); - }, feedUrl() { return `https://${app.get('domain')}/index.xml`; }, diff --git a/src/activity-pub-db.js b/src/activity-pub-db.js index f26fdf3..58c1910 100644 --- a/src/activity-pub-db.js +++ b/src/activity-pub-db.js @@ -10,18 +10,18 @@ import fs from 'fs'; import sqlite3 from 'sqlite3'; import { open } from 'sqlite'; import crypto from 'crypto'; -import { account, domain, actorInfo } from './util.js'; +import { getActorInfo, domain } from './util.js'; const dbFile = './.data/activitypub.db'; let db; -function actorJson(pubkey) { +function actorJson(actorInfo, pubkey) { return { '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'], - id: `https://${domain}/u/${account}`, + id: `https://${domain}/u/${actorInfo.username}`, type: 'Person', - preferredUsername: `${account}`, + preferredUsername: actorInfo.username, name: actorInfo.displayName, summary: actorInfo.description, icon: { @@ -30,19 +30,19 @@ function actorJson(pubkey) { url: actorInfo.avatar, }, inbox: `https://${domain}/api/inbox`, - outbox: `https://${domain}/u/${account}/outbox`, - followers: `https://${domain}/u/${account}/followers`, - following: `https://${domain}/u/${account}/following`, + outbox: `https://${domain}/u/${actorInfo.username}/outbox`, + followers: `https://${domain}/u/${actorInfo.username}/followers`, + following: `https://${domain}/u/${actorInfo.username}/following`, publicKey: { - id: `https://${domain}/u/${account}#main-key`, - owner: `https://${domain}/u/${account}`, + id: `https://${domain}/u/${actorInfo.username}#main-key`, + owner: `https://${domain}/u/${actorInfo.username}`, publicKeyPem: pubkey, }, }; } -function webfingerJson() { +function webfingerJson(account) { return { subject: `acct:${account}@${domain}`, @@ -199,8 +199,9 @@ async function firstTimeSetup(actorName) { async (err, publicKey, privateKey) => { if (err) return reject(err); try { - const actorRecord = actorJson(publicKey); - const webfingerRecord = webfingerJson(); + const actorInfo = await getActorInfo(); + const actorRecord = actorJson(actorInfo, publicKey); + const webfingerRecord = webfingerJson(account); await db.run( 'INSERT OR REPLACE INTO accounts (name, actor, pubkey, privkey, webfinger) VALUES (?, ?, ?, ?, ?)', @@ -221,7 +222,7 @@ async function firstTimeSetup(actorName) { function setup() { // activitypub not set up yet, skip until we have the data we need - if (actorInfo.disabled) { + if (false) { return; } @@ -234,7 +235,8 @@ function setup() { }).then(async (dBase) => { db = dBase; - const actorName = `${account}@${domain}`; + const actorInfo = await getActorInfo(); + const actorName = `${actorInfo.username}@${domain}`; try { if (!exists) { @@ -243,7 +245,7 @@ function setup() { // re-run the profile portion of the actor setup every time in case the avatar, description, etc have changed const publicKey = await getPublicKey(); - const actorRecord = actorJson(publicKey); + const actorRecord = actorJson(actorInfo, publicKey); await db.run('UPDATE accounts SET name = ?, actor = ?', actorName, JSON.stringify(actorRecord)); } catch (dbError) { console.error(dbError); diff --git a/src/activitypub.js b/src/activitypub.js index 09fd880..3ea99be 100644 --- a/src/activitypub.js +++ b/src/activitypub.js @@ -3,7 +3,7 @@ import crypto from 'crypto'; import escapeHTML from 'escape-html'; import { signedGetJSON, signedPostJSON } from './signature.js'; -import { actorInfo, actorMatchesUsername, replaceEmptyText } from './util.js'; +import { actorMatchesUsername, replaceEmptyText } from './util.js'; function getGuidFromPermalink(urlString) { return urlString.match(/(?:\/m\/)([a-zA-Z0-9+/]+)/)[1]; @@ -211,7 +211,7 @@ export async function lookupActorInfo(actorUsername) { } export async function broadcastMessage(bookmark, action, db, account, domain) { - if (actorInfo.disabled) { + if (false /* TODO actorInfo.disabled */) { return; // no fediverse setup, so no purpose trying to send messages } diff --git a/src/bookmarks-db.js b/src/bookmarks-db.js index 8689b0a..6d90483 100644 --- a/src/bookmarks-db.js +++ b/src/bookmarks-db.js @@ -11,9 +11,7 @@ import { open } from 'sqlite'; // unclear why eslint can't resolve this package // eslint-disable-next-line import/no-unresolved, node/no-missing-import import { stripHtml } from 'string-strip-html'; -import { timeSince, account, domain } from './util.js'; - -const ACCOUNT_MENTION_REGEX = new RegExp(`^@${account}@${domain} `); +import { timeSince, getActorInfo, domain } from './util.js'; // Initialize the database const dbFile = './.data/bookmarks.db'; @@ -27,10 +25,10 @@ function stripHtmlFromComment(comment) { return { ...comment, content: stripHtml(comment.content).result }; } -function stripMentionFromComment(comment) { +function stripMentionFromComment(account, comment) { return { ...comment, - content: comment.content.replace(ACCOUNT_MENTION_REGEX, ''), + content: comment.content.replace(new RegExp(`^@${account}@${domain} `), ''), }; } @@ -72,8 +70,8 @@ function massageBookmark(bookmark) { return addBookmarkDomain(addTags(insertRelativeTimestamp(bookmark))); } -function massageComment(comment) { - return generateLinkedDisplayName(stripMentionFromComment(stripHtmlFromComment(insertRelativeTimestamp(comment)))); +function massageComment(account, comment) { + return generateLinkedDisplayName(stripMentionFromComment(account, stripHtmlFromComment(insertRelativeTimestamp(comment)))); } /* @@ -345,9 +343,11 @@ export async function toggleCommentVisibility(commentId) { } export async function getAllCommentsForBookmark(bookmarkId) { + const { username: account } = await getActorInfo(); + try { const results = await db.all('SELECT * FROM comments WHERE bookmark_id = ?', bookmarkId); - return results.map((c) => massageComment(c)); + return results.map((c) => massageComment(account, c)); } catch (dbError) { console.error(dbError); } @@ -355,9 +355,11 @@ export async function getAllCommentsForBookmark(bookmarkId) { } export async function getVisibleCommentsForBookmark(bookmarkId) { + const { username: account } = await getActorInfo(); + try { const results = await db.all('SELECT * FROM comments WHERE visible = 1 AND bookmark_id = ?', bookmarkId); - return results.map((c) => massageComment(c)); + return results.map((c) => massageComment(account, c)); } catch (dbError) { console.error(dbError); } diff --git a/src/database.js b/src/database.js new file mode 100644 index 0000000..2420fb1 --- /dev/null +++ b/src/database.js @@ -0,0 +1,88 @@ +import fs from 'fs'; +import sqlite3 from 'sqlite3'; + +const schema = fs.readFileSync('./src/schema.sql').toString(); +const connect = new Promise((resolve, reject) => { + const result = new sqlite3.Database('./.data/application.db', (error) => { + if (error) { + reject(error); + } else { + console.log('i have hereby connected :)') + result.exec(schema, (execError) => { + if (execError) { + reject(execError); + } else { + resolve(result); + } + }); + } + }); +}); + +const query = (method) => async (...args) => { + const db = await connect; + return new Promise((resolve, reject) => { + db[method](...args, (error, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); +} + +const run = query('run'); +const get = query('get'); +const all = query('all'); + +export const getSetting = async (name) => { + const result = await get('select value from settings where name = ?', name); + + if (!result) { + return null; + } + + const { value } = result; + + if (typeof value === 'string') { + return JSON.parse(value); + } + + return null; +} + +export const getSettings = async (names) => { + // TODO: There must be a way to get node-sqlite3 to accept parameters for an + // `IN` clause but I cannot find it. For now let's naïvely assume that every + // name matches this pattern. This probably isn't a requirement that's worth + // enforcing elsewhere in business logic, and this exact bit of code will + // likely like to weird bugs in the future, but for now it's maybe better to + // be safe! + if (!names.every(name => name.match(/^[a-z0-9-_ .\/]+$/i))) { + throw new Error('Names contain unexpected characters'); + } + + const rows = await all(` + select name, value + from settings + where name in (${names.map(n => `'${n}'`).join(',')}) + `); + + return Object.fromEntries(rows.map(({ name, value }) => [name, JSON.parse(value)])); +} + +export const setSetting = async (name, value) => { + const serializedValue = JSON.stringify(value); + + return run( + ` + insert into settings (name, value) + values (?, ?) + on conflict (name) do update set value = ? + `, + name, + serializedValue, + serializedValue + ); +}; diff --git a/src/pages/about.hbs b/src/pages/about.hbs index b6a26d2..db89a27 100644 --- a/src/pages/about.hbs +++ b/src/pages/about.hbs @@ -1,4 +1,4 @@ -{{#if actorInfo.disabled}} +{{#if actorInfo.disabled}}

This is a bookmarking site running on software called Postmarks. It’s diff --git a/src/routes/activitypub/user.js b/src/routes/activitypub/user.js index 21f96fc..0d4ed1a 100644 --- a/src/routes/activitypub/user.js +++ b/src/routes/activitypub/user.js @@ -1,5 +1,6 @@ import express from 'express'; import { synthesizeActivity } from '../../activitypub.js'; +import { getActorInfo } from '../../util.js'; const router = express.Router(); @@ -95,7 +96,7 @@ router.get('/:name/following', async (req, res) => { router.get('/:name/outbox', async (req, res) => { const domain = req.app.get('domain'); - const account = req.app.get('account'); + const { username: account } = await getActorInfo(); const apDb = req.app.get('apDb'); function pageLink(p) { diff --git a/src/routes/admin.js b/src/routes/admin.js index 8847362..b405674 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -1,7 +1,7 @@ import express from 'express'; // eslint-disable-next-line import/no-unresolved, node/no-missing-import import { stringify as csvStringify } from 'csv-stringify/sync'; // https://github.com/adaltas/node-csv/issues/323 -import { domain, actorInfo, parseJSON } from '../util.js'; +import { domain, getActorInfo, parseJSON } from '../util.js'; import { isAuthenticated } from '../session-auth.js'; import { lookupActorInfo, createFollowMessage, createUnfollowMessage, signAndSend, getInboxFromActorProfile } from '../activitypub.js'; @@ -42,7 +42,7 @@ router.get('/followers', isAuthenticated, async (req, res) => { const apDb = req.app.get('apDb'); - if (actorInfo.disabled) { + if (false /* TODO actorinfo.disabled */) { return res.render('nonfederated', params); } @@ -75,7 +75,7 @@ router.get('/following', isAuthenticated, async (req, res) => { const apDb = req.app.get('apDb'); - if (actorInfo.disabled) { + if (false /* TODO actorinfo.disabled */) { return res.render('nonfederated', params); } @@ -200,7 +200,7 @@ router.post('/followers/unblock', isAuthenticated, async (req, res) => { router.post('/following/follow', isAuthenticated, async (req, res) => { const db = req.app.get('apDb'); - const account = req.app.get('account'); + const { username: account } = await getActorInfo(); const canonicalUrl = await lookupActorInfo(req.body.actor); @@ -221,7 +221,7 @@ router.post('/following/follow', isAuthenticated, async (req, res) => { router.post('/following/unfollow', isAuthenticated, async (req, res) => { const db = req.app.get('apDb'); - const account = req.app.get('account'); + const { username: account } = await getActorInfo(); const oldFollowsText = (await db.getFollowing()) || '[]'; diff --git a/src/routes/bookmark.js b/src/routes/bookmark.js index d6ec94f..7a8c68f 100644 --- a/src/routes/bookmark.js +++ b/src/routes/bookmark.js @@ -1,7 +1,7 @@ import express from 'express'; import ogScraper from 'open-graph-scraper'; -import { data, account, domain, removeEmpty } from '../util.js'; +import { data, getActorInfo, domain, removeEmpty } from '../util.js'; import { broadcastMessage } from '../activitypub.js'; import { isAuthenticated } from '../session-auth.js'; @@ -129,6 +129,8 @@ router.post('/:id/delete', isAuthenticated, async (req, res) => { await bookmarksDb.deleteBookmark(id); + const { username: account } = await getActorInfo(); + broadcastMessage({ id }, 'delete', apDb, account, domain); return req.query.raw ? res.send(params) : res.redirect('/'); @@ -223,6 +225,8 @@ router.post('/:id?', isAuthenticated, async (req, res) => { }); await apDb.setPermissionsForBookmark(id, req.body.allowed || '', req.body.blocked || ''); + const { username: account } = await getActorInfo(); + broadcastMessage(bookmark, 'update', apDb, account, domain); } } else { @@ -253,6 +257,8 @@ router.post('/:id?', isAuthenticated, async (req, res) => { tags, }); + const { username: account } = await getActorInfo(); + broadcastMessage(bookmark, 'create', apDb, account, domain); } diff --git a/src/routes/core.js b/src/routes/core.js index ecb2c20..a6f8b9b 100644 --- a/src/routes/core.js +++ b/src/routes/core.js @@ -1,6 +1,6 @@ import express from 'express'; import * as linkify from 'linkifyjs'; -import { data, actorInfo } from '../util.js'; +import { data, getActorInfo } from '../util.js'; import { isAuthenticated } from '../session-auth.js'; const router = express.Router(); @@ -10,6 +10,7 @@ router.get('/', async (req, res) => { const params = {}; const bookmarksDb = req.app.get('bookmarksDb'); + const db = req.app.get('db'); const limit = Math.max(req.query?.limit || 10, 1); const offset = Math.max(req.query?.offset || 0, 0); @@ -53,6 +54,8 @@ router.get('/', async (req, res) => { }); router.get('/about', async (req, res) => { + const actorInfo = await getActorInfo(); + res.render('about', { title: 'About', actorInfo, @@ -97,7 +100,9 @@ router.get('/index.xml', async (req, res) => { params.last_updated = lastUpdated.toISOString(); } - params.feedTitle = req.app.get('site_name'); + const { displayName, username: account } = await getActorInfo(); + params.account = account; + params.feedTitle = displayName; params.layout = false; res.type('application/atom+xml'); @@ -124,9 +129,13 @@ router.get('/tagged/*.xml', async (req, res) => { params.last_updated = bookmarks[0].created_at; } - params.feedTitle = `${req.app.get('site_name')}: Bookmarks tagged '${tags.join(' and ')}'`; + const { displayName, username: account } = await getActorInfo(); + + params.feedTitle = `${displayName}: Bookmarks tagged '${tags.join(' and ')}'`; params.layout = false; + params.account = account; + res.type('application/atom+xml'); return res.render('bookmarks-xml', params); }); diff --git a/src/schema.sql b/src/schema.sql new file mode 100644 index 0000000..cfeccd0 --- /dev/null +++ b/src/schema.sql @@ -0,0 +1,12 @@ +create table if not exists settings ( + name text unique not null check (name <> ""), + value text not null +); + +insert into settings (name, value) +values + ('username', '"bookmarks"'), + ('avatar', '"https://cdn.glitch.global/8eaf209c-2fa9-4353-9b99-e8d8f3a5f8d4/postmarks-logo-white-small.png?v=1693610556689"'), + ('displayName', '"Postmarks"'), + ('description', '"An ActivityPub bookmarking and sharing site built with Postmarks"') +on conflict (name) do nothing; diff --git a/src/signature.js b/src/signature.js index 473d10d..ffc5820 100644 --- a/src/signature.js +++ b/src/signature.js @@ -1,7 +1,7 @@ import crypto from 'crypto'; import fetch from 'node-fetch'; -import { account, domain } from './util.js'; +import { getActorInfo, domain } from './util.js'; import { getPrivateKey } from './activity-pub-db.js'; /** @@ -67,12 +67,13 @@ function getSignatureParams(body, method, url) { /** * Returns the full "Signature" header to be included in the signed request * + * @param {string} account - The actor's username * @param {string} signature - Base-64 encoded request signature * @param {string[]} signatureKeys - Array of param names used when generating the signature * * @returns {string} */ -function getSignatureHeader(signature, signatureKeys) { +function getSignatureHeader(account, signature, signatureKeys) { return [ `keyId="https://${domain}/u/${account}"`, `algorithm="rsa-sha256"`, @@ -90,6 +91,8 @@ function getSignatureHeader(signature, signatureKeys) { * @returns {Promise} */ export async function signedFetch(url, init = {}) { + const { username: account } = await getActorInfo(); + const privkey = await getPrivateKey(`${account}@${domain}`); if (!privkey) { throw new Error(`No private key found for ${account}.`); @@ -103,7 +106,7 @@ export async function signedFetch(url, init = {}) { .map(([k, v]) => `${k}: ${v}`) .join('\n'); const signature = getSignature(privkey, stringToSign); - const signatureHeader = getSignatureHeader(signature, signatureKeys); + const signatureHeader = getSignatureHeader(account, signature, signatureKeys); return fetch(url, { body, diff --git a/src/util.js b/src/util.js index 0a41a4d..21b3cf6 100644 --- a/src/util.js +++ b/src/util.js @@ -2,26 +2,38 @@ import fs from 'fs'; import { readFile } from 'fs/promises'; import chalk from 'chalk'; import * as dotenv from 'dotenv'; +import * as db from './database.js'; dotenv.config(); +const ACTOR_SETTING_NAMES = ['username', 'avatar', 'displayName', 'description'] +const IS_ACCOUNT_FILE_IMPORTED = 'isAccountFileImported'; + export const data = { errorMessage: 'Whoops! Error connecting to the database–please try again!', setupMessage: "🚧 Whoops! Looks like the database isn't setup yet! 🚧", }; -let actorFileData = {}; try { const accountFile = await readFile(new URL('../account.json', import.meta.url)); - actorFileData = JSON.parse(accountFile); - actorFileData.disabled = false; + const accountFileData = JSON.parse(accountFile); + const isAccountFileImported = await db.getSetting(IS_ACCOUNT_FILE_IMPORTED); + if (isAccountFileImported) { + console.log('Postmarks detected an account.json file that will no longer be read. You should remove this file.') + } else { + for (const name of ACTOR_SETTING_NAMES) { + if (accountFileData.hasOwnProperty(name)) { + await db.setSetting(name, accountFileData[name]); + } + } + await db.setSetting(IS_ACCOUNT_FILE_IMPORTED, true); + console.log('Your account.json file has been imported to the database. You should now remove this file.') + } } catch (e) { - console.log('no account.json file found, assuming non-fediverse mode for now. restart the app to check again'); - actorFileData = { disabled: true }; + console.log('uhhh', e) } -export const actorInfo = actorFileData; -export const account = actorInfo.username || 'bookmarks'; +export const getActorInfo = () => db.getSettings(ACTOR_SETTING_NAMES); export const domain = (() => { if (process.env.PUBLIC_BASE_URL) { From 09a4bc7b6e7e7c4d28e40f62597b0ab7b984316f Mon Sep 17 00:00:00 2001 From: John Holdun Date: Wed, 4 Oct 2023 01:44:53 -0700 Subject: [PATCH 2/4] lint --- src/activity-pub-db.js | 7 +---- src/activitypub.js | 4 +-- src/database.js | 61 +++++++++++++++++++++++++++--------------- src/routes/admin.js | 8 ++++-- src/routes/core.js | 1 - src/util.js | 19 +++++++------ 6 files changed, 57 insertions(+), 43 deletions(-) diff --git a/src/activity-pub-db.js b/src/activity-pub-db.js index 58c1910..f56483e 100644 --- a/src/activity-pub-db.js +++ b/src/activity-pub-db.js @@ -201,7 +201,7 @@ async function firstTimeSetup(actorName) { try { const actorInfo = await getActorInfo(); const actorRecord = actorJson(actorInfo, publicKey); - const webfingerRecord = webfingerJson(account); + const webfingerRecord = webfingerJson(actorInfo.username); await db.run( 'INSERT OR REPLACE INTO accounts (name, actor, pubkey, privkey, webfinger) VALUES (?, ?, ?, ?, ?)', @@ -221,11 +221,6 @@ async function firstTimeSetup(actorName) { } function setup() { - // activitypub not set up yet, skip until we have the data we need - if (false) { - return; - } - // Initialize the database const exists = fs.existsSync(dbFile); diff --git a/src/activitypub.js b/src/activitypub.js index 3ea99be..999088b 100644 --- a/src/activitypub.js +++ b/src/activitypub.js @@ -211,9 +211,7 @@ export async function lookupActorInfo(actorUsername) { } export async function broadcastMessage(bookmark, action, db, account, domain) { - if (false /* TODO actorInfo.disabled */) { - return; // no fediverse setup, so no purpose trying to send messages - } + // TODO bail if activitypub not set up const result = await db.getFollowers(); const followers = JSON.parse(result); diff --git a/src/database.js b/src/database.js index 2420fb1..019f0b1 100644 --- a/src/database.js +++ b/src/database.js @@ -7,7 +7,7 @@ const connect = new Promise((resolve, reject) => { if (error) { reject(error); } else { - console.log('i have hereby connected :)') + console.log('i have hereby connected :)'); result.exec(schema, (execError) => { if (execError) { reject(execError); @@ -19,18 +19,20 @@ const connect = new Promise((resolve, reject) => { }); }); -const query = (method) => async (...args) => { - const db = await connect; - return new Promise((resolve, reject) => { - db[method](...args, (error, result) => { - if (error) { - reject(error); - } else { - resolve(result); - } +const query = + (method) => + async (...args) => { + const db = await connect; + return new Promise((resolve, reject) => { + db[method](...args, (error, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); }); - }); -} + }; const run = query('run'); const get = query('get'); @@ -50,7 +52,7 @@ export const getSetting = async (name) => { } return null; -} +}; export const getSettings = async (names) => { // TODO: There must be a way to get node-sqlite3 to accept parameters for an @@ -59,30 +61,47 @@ export const getSettings = async (names) => { // enforcing elsewhere in business logic, and this exact bit of code will // likely like to weird bugs in the future, but for now it's maybe better to // be safe! - if (!names.every(name => name.match(/^[a-z0-9-_ .\/]+$/i))) { + if (!names.every((name) => name.match(/^[a-z0-9-_ ./]+$/i))) { throw new Error('Names contain unexpected characters'); } const rows = await all(` select name, value from settings - where name in (${names.map(n => `'${n}'`).join(',')}) + where name in (${names.map((n) => `'${n}'`).join(',')}) `); return Object.fromEntries(rows.map(({ name, value }) => [name, JSON.parse(value)])); -} +}; -export const setSetting = async (name, value) => { - const serializedValue = JSON.stringify(value); +export const setSettings = async (obj) => { + // TODO: See caveat in getSettings + if (!Object.keys(obj).every((name) => name.match(/^[a-z0-9-_ ./]+$/i))) { + throw new Error('Names contain unexpected characters'); + } + + const values = Object.entries(obj).map( + ([name, value]) => + // TODO: Escape this properly + `('${name}', '${JSON.stringify(value).replace("'", "\\'")}')`, + ); + await run(` + insert into settings + (name, value) + values ${values.join(',')} + on conflict (name) do update set value = excluded.value + `); +}; + +export const setSetting = async (name, value) => { return run( ` insert into settings (name, value) values (?, ?) - on conflict (name) do update set value = ? + on conflict (name) do update set value = excluded.value `, name, - serializedValue, - serializedValue + JSON.stringify(value), ); }; diff --git a/src/routes/admin.js b/src/routes/admin.js index b405674..c4aa697 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -42,7 +42,9 @@ router.get('/followers', isAuthenticated, async (req, res) => { const apDb = req.app.get('apDb'); - if (false /* TODO actorinfo.disabled */) { + const { actorInfo } = await getActorInfo(); + // TODO + if (actorInfo.disabled) { return res.render('nonfederated', params); } @@ -75,7 +77,9 @@ router.get('/following', isAuthenticated, async (req, res) => { const apDb = req.app.get('apDb'); - if (false /* TODO actorinfo.disabled */) { + const { actorInfo } = await getActorInfo(); + // TODO + if (actorInfo.disabled) { return res.render('nonfederated', params); } diff --git a/src/routes/core.js b/src/routes/core.js index a6f8b9b..81e8379 100644 --- a/src/routes/core.js +++ b/src/routes/core.js @@ -10,7 +10,6 @@ router.get('/', async (req, res) => { const params = {}; const bookmarksDb = req.app.get('bookmarksDb'); - const db = req.app.get('db'); const limit = Math.max(req.query?.limit || 10, 1); const offset = Math.max(req.query?.offset || 0, 0); diff --git a/src/util.js b/src/util.js index 21b3cf6..f5a4e7a 100644 --- a/src/util.js +++ b/src/util.js @@ -6,7 +6,7 @@ import * as db from './database.js'; dotenv.config(); -const ACTOR_SETTING_NAMES = ['username', 'avatar', 'displayName', 'description'] +const ACTOR_SETTING_NAMES = ['username', 'avatar', 'displayName', 'description']; const IS_ACCOUNT_FILE_IMPORTED = 'isAccountFileImported'; export const data = { @@ -19,18 +19,17 @@ try { const accountFileData = JSON.parse(accountFile); const isAccountFileImported = await db.getSetting(IS_ACCOUNT_FILE_IMPORTED); if (isAccountFileImported) { - console.log('Postmarks detected an account.json file that will no longer be read. You should remove this file.') + console.log('Postmarks detected an account.json file that will no longer be read. You should remove this file.'); } else { - for (const name of ACTOR_SETTING_NAMES) { - if (accountFileData.hasOwnProperty(name)) { - await db.setSetting(name, accountFileData[name]); - } - } - await db.setSetting(IS_ACCOUNT_FILE_IMPORTED, true); - console.log('Your account.json file has been imported to the database. You should now remove this file.') + await db.setSettings({ + ...Object.fromEntries(Object.entries(accountFileData).filter(([name]) => ACTOR_SETTING_NAMES.includes(name))), + [IS_ACCOUNT_FILE_IMPORTED]: true, + }); + console.log('Your account.json file has been imported to the database. You should now remove this file.'); } } catch (e) { - console.log('uhhh', e) + // TODO: Check for existence of account.json instead of catching error + console.log('Failed to read account.json', e); } export const getActorInfo = () => db.getSettings(ACTOR_SETTING_NAMES); From 3aa54a941ee33ef43c01a212fc7933ba6bb29cc9 Mon Sep 17 00:00:00 2001 From: John Holdun Date: Tue, 14 Nov 2023 19:44:05 -0800 Subject: [PATCH 3/4] Additional refactoring whoa --- package-lock.json | 823 ++++------------------------ package.json | 5 +- server.js | 3 +- src/activity-pub-db.js | 251 --------- src/activitypub.js | 162 +++--- src/boot.js | 194 +++++++ src/database.js | 144 +++-- src/pages/admin/data.hbs | 12 +- src/routes/activitypub/inbox.js | 127 ++--- src/routes/activitypub/message.js | 5 +- src/routes/activitypub/user.js | 101 ++-- src/routes/activitypub/webfinger.js | 24 +- src/routes/admin.js | 181 ++---- src/routes/bookmark.js | 46 +- src/schema.sql | 28 + src/signature.js | 16 +- src/util.js | 28 +- 17 files changed, 732 insertions(+), 1418 deletions(-) delete mode 100644 src/activity-pub-db.js create mode 100644 src/boot.js diff --git a/package-lock.json b/package-lock.json index 44cf298..ac6a4ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "eslint": "^8.43.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.29.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^5.0.0", "nodemon": "^2.0.20", @@ -60,9 +61,6 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "node_modules/@eslint-community/regexpp": { @@ -92,9 +90,6 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" } }, "node_modules/@eslint/eslintrc/node_modules/debug": { @@ -107,11 +102,6 @@ }, "engines": { "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } } }, "node_modules/@eslint/eslintrc/node_modules/ms": { @@ -159,11 +149,6 @@ }, "engines": { "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } } }, "node_modules/@humanwhocodes/config-array/node_modules/ms": { @@ -179,10 +164,6 @@ "dev": true, "engines": { "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" } }, "node_modules/@humanwhocodes/object-schema": { @@ -219,14 +200,6 @@ }, "engines": { "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } } }, "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": { @@ -321,7 +294,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "deprecated": "This functionality has been moved to @npmcli/fs", "optional": true, "dependencies": { "mkdirp": "^1.0.4", @@ -346,9 +318,6 @@ }, "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" } }, "node_modules/@sindresorhus/is": { @@ -357,9 +326,6 @@ "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", "engines": { "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" } }, "node_modules/@szmarczak/http-timer": { @@ -391,8 +357,7 @@ "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@types/lodash": { "version": "4.14.197", @@ -428,10 +393,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } + "dev": true }, "node_modules/agent-base": { "version": "6.0.2", @@ -453,11 +415,6 @@ }, "engines": { "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } } }, "node_modules/agent-base/node_modules/ms": { @@ -489,11 +446,6 @@ }, "engines": { "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } } }, "node_modules/agentkeepalive/node_modules/depd": { @@ -534,10 +486,6 @@ "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" } }, "node_modules/ansi-regex": { @@ -558,9 +506,6 @@ }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/anymatch": { @@ -607,9 +552,6 @@ "dependencies": { "call-bind": "^1.0.2", "is-array-buffer": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/array-includes": { @@ -617,7 +559,6 @@ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -637,7 +578,6 @@ "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -657,7 +597,6 @@ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -676,7 +615,6 @@ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -706,9 +644,6 @@ }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/available-typed-arrays": { @@ -718,9 +653,6 @@ "dev": true, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/balanced-match": { @@ -830,9 +762,6 @@ }, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/bytes": { @@ -887,9 +816,6 @@ }, "engines": { "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" } }, "node_modules/cacache/node_modules/minipass": { @@ -936,9 +862,6 @@ "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/callsites": { @@ -956,9 +879,6 @@ "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/chardet": { @@ -981,9 +901,6 @@ }, "engines": { "node": ">= 6" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" } }, "node_modules/cheerio-select": { @@ -997,9 +914,6 @@ "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" } }, "node_modules/chokidar": { @@ -1007,12 +921,6 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -1183,9 +1091,6 @@ "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" } }, "node_modules/css-what": { @@ -1194,9 +1099,6 @@ "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", "engines": { "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" } }, "node_modules/csv-stringify": { @@ -1230,9 +1132,6 @@ }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/decompress-response/node_modules/mimic-response": { @@ -1241,9 +1140,6 @@ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/deep-is": { @@ -1265,9 +1161,6 @@ }, "engines": { "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/default-browser-id": { @@ -1281,9 +1174,6 @@ }, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/defer-to-connect": { @@ -1301,9 +1191,6 @@ "dev": true, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/define-properties": { @@ -1317,9 +1204,6 @@ }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/delegates": { @@ -1372,21 +1256,12 @@ "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, "node_modules/domelementtype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" }, "node_modules/domhandler": { "version": "5.0.3", @@ -1397,9 +1272,6 @@ }, "engines": { "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" } }, "node_modules/domutils": { @@ -1410,9 +1282,6 @@ "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" } }, "node_modules/dotenv": { @@ -1460,9 +1329,6 @@ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "engines": { "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/env-paths": { @@ -1528,9 +1394,6 @@ }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/es-set-tostringtag": { @@ -1548,13 +1411,12 @@ } }, "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", "dev": true, - "peer": true, "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" } }, "node_modules/es-to-primitive": { @@ -1569,9 +1431,6 @@ }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/es6-promisify": { @@ -1594,9 +1453,6 @@ "dev": true, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint": { @@ -1648,9 +1504,6 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-config-airbnb-base": { @@ -1666,10 +1519,6 @@ }, "engines": { "node": "^10.12.0 || >=12.0.0" - }, - "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.2" } }, "node_modules/eslint-config-airbnb-base/node_modules/semver": { @@ -1688,9 +1537,6 @@ "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" } }, "node_modules/eslint-import-resolver-node": { @@ -1698,7 +1544,6 @@ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, - "peer": true, "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", @@ -1710,7 +1555,6 @@ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", "dev": true, - "peer": true, "dependencies": { "debug": "^3.2.7" }, @@ -1734,36 +1578,29 @@ }, "engines": { "node": ">=8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" } }, "node_modules/eslint-plugin-import": { - "version": "2.28.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz", - "integrity": "sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==", + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", + "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", "dev": true, - "peer": true, "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.findlastindex": "^1.2.2", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", "debug": "^3.2.7", "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", + "eslint-import-resolver-node": "^0.3.9", "eslint-module-utils": "^2.8.0", - "has": "^1.0.3", - "is-core-module": "^2.13.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.fromentries": "^2.0.6", - "object.groupby": "^1.0.0", - "object.values": "^1.1.6", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", "semver": "^6.3.1", "tsconfig-paths": "^3.14.2" }, @@ -1779,7 +1616,6 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, - "peer": true, "dependencies": { "esutils": "^2.0.2" }, @@ -1792,7 +1628,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -1812,9 +1647,6 @@ }, "engines": { "node": ">=8.10.0" - }, - "peerDependencies": { - "eslint": ">=5.16.0" } }, "node_modules/eslint-plugin-node/node_modules/semver": { @@ -1837,22 +1669,6 @@ }, "engines": { "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } } }, "node_modules/eslint-scope": { @@ -1866,9 +1682,6 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-utils": { @@ -1881,9 +1694,6 @@ }, "engines": { "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" } }, "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { @@ -1902,9 +1712,6 @@ "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/chalk": { @@ -1918,9 +1725,6 @@ }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/eslint/node_modules/debug": { @@ -1933,11 +1737,6 @@ }, "engines": { "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } } }, "node_modules/eslint/node_modules/glob-parent": { @@ -1991,9 +1790,6 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" } }, "node_modules/esquery": { @@ -2056,9 +1852,6 @@ }, "engines": { "node": "^14.18.0 || ^16.14.0 || >=18.0.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, "node_modules/express": { @@ -2225,9 +2018,6 @@ "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/express/node_modules/content-disposition": { @@ -2336,11 +2126,6 @@ "node": ">= 0.6" } }, - "node_modules/express/node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, "node_modules/express/node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -2447,10 +2232,7 @@ "node_modules/express/node_modules/object-inspect": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" }, "node_modules/express/node_modules/on-finished": { "version": "2.4.1", @@ -2489,9 +2271,6 @@ }, "engines": { "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/express/node_modules/range-parser": { @@ -2519,21 +2298,7 @@ "node_modules/express/node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "node_modules/express/node_modules/safer-buffer": { "version": "2.1.2", @@ -2595,9 +2360,6 @@ "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/express/node_modules/statuses": { @@ -2705,16 +2467,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" @@ -2758,9 +2510,6 @@ }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/flat-cache": { @@ -2838,10 +2587,26 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/function.prototype.name": { "version": "1.1.6", @@ -2856,19 +2621,13 @@ }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "dev": true }, "node_modules/gauge": { "version": "3.0.2", @@ -2898,9 +2657,6 @@ "has": "^1.0.3", "has-proto": "^1.0.1", "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/get-stream": { @@ -2909,9 +2665,6 @@ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/get-symbol-description": { @@ -2925,9 +2678,6 @@ }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/glob": { @@ -2943,9 +2693,6 @@ }, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" } }, "node_modules/glob-parent": { @@ -2989,9 +2736,6 @@ }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globalthis": { @@ -3004,9 +2748,6 @@ }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/gopd": { @@ -3016,9 +2757,6 @@ "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/got": { @@ -3040,9 +2778,6 @@ }, "engines": { "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" } }, "node_modules/graceful-fs": { @@ -3091,10 +2826,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "dev": true }, "node_modules/has-flag": { "version": "3.0.0", @@ -3112,9 +2844,6 @@ "dev": true, "dependencies": { "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { @@ -3123,9 +2852,6 @@ "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-symbols": { @@ -3134,9 +2860,6 @@ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-tostringtag": { @@ -3149,9 +2872,6 @@ }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-unicode": { @@ -3159,32 +2879,27 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/html-entities": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", - "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ] + "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==" }, "node_modules/htmlparser2": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", @@ -3236,11 +2951,6 @@ }, "engines": { "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } } }, "node_modules/http-proxy-agent/node_modules/ms": { @@ -3282,11 +2992,6 @@ }, "engines": { "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } } }, "node_modules/https-proxy-agent/node_modules/ms": { @@ -3349,9 +3054,6 @@ }, "engines": { "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/imurmurhash": { @@ -3421,9 +3123,6 @@ "call-bind": "^1.0.2", "get-intrinsic": "^1.2.0", "is-typed-array": "^1.1.10" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-bigint": { @@ -3433,9 +3132,6 @@ "dev": true, "dependencies": { "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-binary-path": { @@ -3461,9 +3157,6 @@ }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-callable": { @@ -3473,18 +3166,15 @@ "dev": true, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dev": true, "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3500,9 +3190,6 @@ }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-docker": { @@ -3515,9 +3202,6 @@ }, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-extglob": { @@ -3562,9 +3246,6 @@ }, "engines": { "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-lambda": { @@ -3580,9 +3261,6 @@ "dev": true, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-number": { @@ -3604,9 +3282,6 @@ }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-path-inside": { @@ -3629,9 +3304,6 @@ }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-shared-array-buffer": { @@ -3641,9 +3313,6 @@ "dev": true, "dependencies": { "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-stream": { @@ -3653,9 +3322,6 @@ "dev": true, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-string": { @@ -3668,9 +3334,6 @@ }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-symbol": { @@ -3683,9 +3346,6 @@ }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-typed-array": { @@ -3698,9 +3358,6 @@ }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-weakref": { @@ -3710,9 +3367,6 @@ "dev": true, "dependencies": { "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-wsl": { @@ -3737,9 +3391,6 @@ }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/isarray": { @@ -3788,7 +3439,6 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, - "peer": true, "dependencies": { "minimist": "^1.2.0" }, @@ -3832,9 +3482,6 @@ }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lodash-es": { @@ -3854,9 +3501,6 @@ "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lru-cache": { @@ -3879,9 +3523,6 @@ }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/make-dir/node_modules/semver": { @@ -3993,9 +3634,6 @@ "dev": true, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/mimic-response": { @@ -4004,9 +3642,6 @@ "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/minimatch": { @@ -4023,10 +3658,7 @@ "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, "node_modules/minipass": { "version": "4.2.1", @@ -4066,15 +3698,13 @@ "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", "optional": true, "dependencies": { + "encoding": "^0.1.12", "minipass": "^3.1.0", "minipass-sized": "^1.0.3", "minizlib": "^2.0.0" }, "engines": { "node": ">=8" - }, - "optionalDependencies": { - "encoding": "^0.1.12" } }, "node_modules/minipass-fetch/node_modules/minipass": { @@ -4230,16 +3860,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], "engines": { "node": ">=10.5.0" } @@ -4255,10 +3875,6 @@ }, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" } }, "node_modules/node-gyp": { @@ -4332,9 +3948,6 @@ }, "engines": { "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" } }, "node_modules/node-gyp/node_modules/nopt": { @@ -4404,10 +4017,6 @@ }, "engines": { "node": ">=8.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" } }, "node_modules/nopt": { @@ -4420,9 +4029,6 @@ }, "bin": { "nopt": "bin/nopt.js" - }, - "engines": { - "node": "*" } }, "node_modules/normalize-path": { @@ -4440,9 +4046,6 @@ "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", "engines": { "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/npm-run-path": { @@ -4455,9 +4058,6 @@ }, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/npm-run-path/node_modules/path-key": { @@ -4467,9 +4067,6 @@ "dev": true, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/npmlog": { @@ -4489,9 +4086,6 @@ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dependencies": { "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" } }, "node_modules/object-assign": { @@ -4505,10 +4099,7 @@ "node_modules/object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" }, "node_modules/object-keys": { "version": "1.1.1", @@ -4532,9 +4123,6 @@ }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/object.entries": { @@ -4556,7 +4144,6 @@ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -4574,7 +4161,6 @@ "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -4587,7 +4173,6 @@ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -4637,9 +4222,6 @@ }, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/open": { @@ -4655,9 +4237,6 @@ }, "engines": { "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/open-graph-scraper": { @@ -4721,9 +4300,6 @@ }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-locate": { @@ -4736,9 +4312,6 @@ }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-map": { @@ -4751,9 +4324,6 @@ }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/parent-module": { @@ -4774,9 +4344,6 @@ "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", "dependencies": { "entities": "^4.4.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" } }, "node_modules/parse5-htmlparser2-tree-adapter": { @@ -4786,9 +4353,6 @@ "dependencies": { "domhandler": "^5.0.2", "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" } }, "node_modules/parseurl": { @@ -4844,9 +4408,6 @@ "dev": true, "engines": { "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/prelude-ls": { @@ -4868,9 +4429,6 @@ }, "engines": { "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" } }, "node_modules/prettier-linter-helpers": { @@ -4928,30 +4486,13 @@ }, "engines": { "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "dev": true }, "node_modules/quick-lru": { "version": "5.1.1", @@ -4959,9 +4500,6 @@ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/random-bytes": { @@ -5069,9 +4607,6 @@ }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/regexpp": { @@ -5081,9 +4616,6 @@ "dev": true, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" } }, "node_modules/resolve": { @@ -5098,9 +4630,6 @@ }, "bin": { "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/resolve-alpn": { @@ -5126,9 +4655,6 @@ }, "engines": { "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/retry": { @@ -5164,9 +4690,6 @@ }, "bin": { "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" } }, "node_modules/rimraf/node_modules/glob": { @@ -5183,9 +4706,6 @@ }, "engines": { "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" } }, "node_modules/run-applescript": { @@ -5198,9 +4718,6 @@ }, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/run-applescript/node_modules/execa": { @@ -5221,9 +4738,6 @@ }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, "node_modules/run-applescript/node_modules/human-signals": { @@ -5242,9 +4756,6 @@ "dev": true, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/run-applescript/node_modules/mimic-fn": { @@ -5278,9 +4789,6 @@ }, "engines": { "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/run-applescript/node_modules/strip-final-newline": { @@ -5297,20 +4805,6 @@ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], "dependencies": { "queue-microtask": "^1.2.2" } @@ -5328,29 +4822,12 @@ }, "engines": { "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "node_modules/safe-regex-test": { "version": "1.0.0", @@ -5361,9 +4838,6 @@ "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", "is-regex": "^1.1.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/safer-buffer": { @@ -5419,9 +4893,6 @@ "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/signal-exit": { @@ -5498,11 +4969,6 @@ }, "engines": { "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } } }, "node_modules/socks-proxy-agent/node_modules/ms": { @@ -5528,7 +4994,6 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.6.tgz", "integrity": "sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw==", - "hasInstallScript": true, "dependencies": { "@mapbox/node-pre-gyp": "^1.0.0", "node-addon-api": "^4.2.0", @@ -5536,14 +5001,6 @@ }, "optionalDependencies": { "node-gyp": "8.x" - }, - "peerDependencies": { - "node-gyp": "8.x" - }, - "peerDependenciesMeta": { - "node-gyp": { - "optional": true - } } }, "node_modules/ssri": { @@ -5656,9 +5113,6 @@ }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimend": { @@ -5670,9 +5124,6 @@ "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { @@ -5684,9 +5135,6 @@ "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/strip-ansi": { @@ -5705,7 +5153,6 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -5717,9 +5164,6 @@ "dev": true, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/strip-json-comments": { @@ -5729,9 +5173,6 @@ "dev": true, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/supports-color": { @@ -5753,9 +5194,6 @@ "dev": true, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/synckit": { @@ -5769,9 +5207,6 @@ }, "engines": { "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" } }, "node_modules/tar": { @@ -5808,9 +5243,6 @@ "dev": true, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/to-regex-range": { @@ -5855,7 +5287,6 @@ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", "dev": true, - "peer": true, "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -5888,9 +5319,6 @@ "dev": true, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/type-is": { @@ -5932,9 +5360,6 @@ }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/typed-array-byte-offset": { @@ -5951,9 +5376,6 @@ }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/typed-array-length": { @@ -5965,9 +5387,6 @@ "call-bind": "^1.0.2", "for-each": "^0.3.3", "is-typed-array": "^1.1.9" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/uglify-js": { @@ -6003,9 +5422,6 @@ "has-bigints": "^1.0.2", "has-symbols": "^1.0.3", "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/undefsafe": { @@ -6119,9 +5535,6 @@ "is-number-object": "^1.0.4", "is-string": "^1.0.5", "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/which-typed-array": { @@ -6138,9 +5551,6 @@ }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/wide-align": { @@ -6173,9 +5583,6 @@ "dev": true, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } } }, @@ -6428,8 +5835,7 @@ "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "peer": true + "dev": true }, "@types/lodash": { "version": "4.14.197", @@ -6459,8 +5865,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} + "dev": true }, "agent-base": { "version": "6.0.2", @@ -6600,7 +6005,6 @@ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -6614,7 +6018,6 @@ "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -6628,7 +6031,6 @@ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -6641,7 +6043,6 @@ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -7288,13 +6689,12 @@ } }, "es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", "dev": true, - "peer": true, "requires": { - "has": "^1.0.3" + "hasown": "^2.0.0" } }, "es-to-primitive": { @@ -7444,15 +6844,13 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", - "dev": true, - "requires": {} + "dev": true }, "eslint-import-resolver-node": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, - "peer": true, "requires": { "debug": "^3.2.7", "is-core-module": "^2.13.0", @@ -7464,7 +6862,6 @@ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", "dev": true, - "peer": true, "requires": { "debug": "^3.2.7" } @@ -7480,27 +6877,26 @@ } }, "eslint-plugin-import": { - "version": "2.28.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz", - "integrity": "sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==", + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", + "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", "dev": true, - "peer": true, "requires": { - "array-includes": "^3.1.6", - "array.prototype.findlastindex": "^1.2.2", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", "debug": "^3.2.7", "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", + "eslint-import-resolver-node": "^0.3.9", "eslint-module-utils": "^2.8.0", - "has": "^1.0.3", - "is-core-module": "^2.13.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.fromentries": "^2.0.6", - "object.groupby": "^1.0.0", - "object.values": "^1.1.6", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", "semver": "^6.3.1", "tsconfig-paths": "^3.14.2" }, @@ -7510,7 +6906,6 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, - "peer": true, "requires": { "esutils": "^2.0.2" } @@ -7519,8 +6914,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "peer": true + "dev": true } } }, @@ -7807,11 +7201,6 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, "http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -8243,10 +7632,17 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "function.prototype.name": { "version": "1.1.6", @@ -8468,6 +7864,15 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, + "hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, "html-entities": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", @@ -8702,12 +8107,12 @@ "dev": true }, "is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dev": true, "requires": { - "has": "^1.0.3" + "hasown": "^2.0.0" } }, "is-date-object": { @@ -8908,7 +8313,6 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, - "peer": true, "requires": { "minimist": "^1.2.0" } @@ -9466,7 +8870,6 @@ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -9478,7 +8881,6 @@ "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -9491,7 +8893,6 @@ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -10259,8 +9660,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "peer": true + "dev": true }, "strip-final-newline": { "version": "3.0.0", @@ -10362,7 +9762,6 @@ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", "dev": true, - "peer": true, "requires": { "@types/json5": "^0.0.29", "json5": "^1.0.2", diff --git a/package.json b/package.json index c3f5cd7..7b1b551 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "csv-stringify": "^6.4.2", "dotenv": "^16.0.3", "es6-promisify": "^7.0.0", + "escape-html": "^1.0.3", "express": "^4.18.2", "express-basic-auth": "^1.2.1", "express-handlebars": "^6.0.7", @@ -26,8 +27,7 @@ "open-graph-scraper": "^5.2.3", "sqlite": "^5.0.1", "sqlite3": "^5.1.5", - "string-strip-html": "^13.4.2", - "escape-html": "^1.0.3" + "string-strip-html": "^13.4.2" }, "engines": { "node": ">=16.x" @@ -49,6 +49,7 @@ "eslint": "^8.43.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.29.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^5.0.0", "nodemon": "^2.0.20", diff --git a/server.js b/server.js index 2cebce8..f6c6c7a 100644 --- a/server.js +++ b/server.js @@ -7,8 +7,8 @@ import escapeHTML from 'escape-html'; import { domain, simpleLogger, getActorInfo, replaceEmptyText } from './src/util.js'; import session, { isAuthenticated } from './src/session-auth.js'; import * as bookmarksDb from './src/bookmarks-db.js'; -import * as apDb from './src/activity-pub-db.js'; import * as db from './src/database.js'; +import './src/boot.js'; import routes from './src/routes/index.js'; @@ -31,7 +31,6 @@ app.use(async (req, res, next) => { }); app.set('bookmarksDb', bookmarksDb); -app.set('apDb', apDb); app.set('db', db); app.set('domain', domain); diff --git a/src/activity-pub-db.js b/src/activity-pub-db.js deleted file mode 100644 index f56483e..0000000 --- a/src/activity-pub-db.js +++ /dev/null @@ -1,251 +0,0 @@ -/** - * Module handles activitypub data management - * - * Server API calls the methods in here to query and update the SQLite database - */ - -// Utilities we need -import * as path from 'path'; -import fs from 'fs'; -import sqlite3 from 'sqlite3'; -import { open } from 'sqlite'; -import crypto from 'crypto'; -import { getActorInfo, domain } from './util.js'; - -const dbFile = './.data/activitypub.db'; -let db; - -function actorJson(actorInfo, pubkey) { - return { - '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'], - - id: `https://${domain}/u/${actorInfo.username}`, - type: 'Person', - preferredUsername: actorInfo.username, - name: actorInfo.displayName, - summary: actorInfo.description, - icon: { - type: 'Image', - mediaType: `image/${path.extname(actorInfo.avatar).slice(1)}`, - url: actorInfo.avatar, - }, - inbox: `https://${domain}/api/inbox`, - outbox: `https://${domain}/u/${actorInfo.username}/outbox`, - followers: `https://${domain}/u/${actorInfo.username}/followers`, - following: `https://${domain}/u/${actorInfo.username}/following`, - - publicKey: { - id: `https://${domain}/u/${actorInfo.username}#main-key`, - owner: `https://${domain}/u/${actorInfo.username}`, - publicKeyPem: pubkey, - }, - }; -} - -function webfingerJson(account) { - return { - subject: `acct:${account}@${domain}`, - - links: [ - { - rel: 'self', - type: 'application/activity+json', - href: `https://${domain}/u/${account}`, - }, - ], - }; -} - -export async function getFollowers() { - const result = await db?.get('select followers from accounts limit 1'); - return result?.followers; -} - -export async function setFollowers(followersJson) { - return db?.run('update accounts set followers=?', followersJson); -} - -export async function getFollowing() { - const result = await db?.get('select following from accounts limit 1'); - return result?.following; -} - -export async function setFollowing(followingJson) { - return db?.run('update accounts set following=?', followingJson); -} - -export async function getBlocks() { - const result = await db?.get('select blocks from accounts limit 1'); - return result?.blocks; -} - -export async function setBlocks(blocksJson) { - return db?.run('update accounts set blocks=?', blocksJson); -} - -export async function getActor() { - const result = await db?.get('select actor from accounts limit 1'); - return result?.actor; -} - -export async function getWebfinger() { - const result = await db?.get('select webfinger from accounts limit 1'); - return result?.webfinger; -} - -export async function getPublicKey() { - const result = await db?.get('select pubkey from accounts limit 1'); - return result?.pubkey; -} - -export async function getPrivateKey() { - const result = await db?.get('select privkey from accounts limit 1'); - return result?.privkey; -} - -export async function getGuidForBookmarkId(id) { - return (await db?.get('select guid from messages where bookmark_id = ?', id))?.guid; -} - -export async function getBookmarkIdFromMessageGuid(guid) { - return (await db?.get('select bookmark_id from messages where guid = ?', guid))?.bookmark_id; -} - -export async function getMessage(guid) { - return db?.get('select message from messages where guid = ?', guid); -} - -export async function getMessageCount() { - return (await db?.get('select count(message) as count from messages'))?.count; -} - -export async function getMessages(offset = 0, limit = 20) { - return db?.all('select message from messages order by bookmark_id desc limit ? offset ?', limit, offset); -} - -export async function findMessageGuid(bookmarkId) { - return (await db?.get('select guid from messages where bookmark_id = ?', bookmarkId))?.guid; -} - -export async function deleteMessage(guid) { - await db?.get('delete from messages where guid = ?', guid); -} - -export async function getGlobalPermissions() { - return db?.get('select * from permissions where bookmark_id = 0'); -} - -export async function setPermissionsForBookmark(id, allowed, blocked) { - return db?.run('insert or replace into permissions(bookmark_id, allowed, blocked) values (?, ?, ?)', id, allowed, blocked); -} - -export async function setGlobalPermissions(allowed, blocked) { - return setPermissionsForBookmark(0, allowed, blocked); -} - -export async function getPermissionsForBookmark(id) { - return db?.get('select * from permissions where bookmark_id = ?', id); -} - -export async function insertMessage(guid, bookmarkId, json) { - return db?.run('insert or replace into messages(guid, bookmark_id, message) values(?, ?, ?)', guid, bookmarkId, json); -} - -export async function findMessage(object) { - return db?.all('select * from messages where message like ?', `%${object}%`); -} - -async function firstTimeSetup(actorName) { - // eslint-disable-next-line no-bitwise - const newDb = new sqlite3.Database(dbFile, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, (err) => { - if (err) { - throw new Error(`unable to open or create database: ${err}`); - } - }); - - newDb.close(); - - // now do it again, using the async/await library - await open({ - filename: dbFile, - driver: sqlite3.Database, - }).then(async (dBase) => { - db = dBase; - }); - - await db.run( - 'CREATE TABLE IF NOT EXISTS accounts (name TEXT PRIMARY KEY, privkey TEXT, pubkey TEXT, webfinger TEXT, actor TEXT, followers TEXT, following TEXT, messages TEXT, blocks TEXT)', - ); - - // if there is no `messages` table in the DB, create an empty table - // TODO: index messages on bookmark_id - await db.run('CREATE TABLE IF NOT EXISTS messages (guid TEXT PRIMARY KEY, message TEXT, bookmark_id INTEGER)'); - await db.run('CREATE TABLE IF NOT EXISTS permissions (bookmark_id INTEGER NOT NULL UNIQUE, allowed TEXT, blocked TEXT)'); - - return new Promise((resolve, reject) => { - crypto.generateKeyPair( - 'rsa', - { - modulusLength: 4096, - publicKeyEncoding: { - type: 'spki', - format: 'pem', - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'pem', - }, - }, - async (err, publicKey, privateKey) => { - if (err) return reject(err); - try { - const actorInfo = await getActorInfo(); - const actorRecord = actorJson(actorInfo, publicKey); - const webfingerRecord = webfingerJson(actorInfo.username); - - await db.run( - 'INSERT OR REPLACE INTO accounts (name, actor, pubkey, privkey, webfinger) VALUES (?, ?, ?, ?, ?)', - actorName, - JSON.stringify(actorRecord), - publicKey, - privateKey, - JSON.stringify(webfingerRecord), - ); - return resolve(); - } catch (e) { - return reject(e); - } - }, - ); - }); -} - -function setup() { - // Initialize the database - const exists = fs.existsSync(dbFile); - - open({ - filename: dbFile, - driver: sqlite3.Database, - }).then(async (dBase) => { - db = dBase; - - const actorInfo = await getActorInfo(); - const actorName = `${actorInfo.username}@${domain}`; - - try { - if (!exists) { - await firstTimeSetup(actorName); - } - - // re-run the profile portion of the actor setup every time in case the avatar, description, etc have changed - const publicKey = await getPublicKey(); - const actorRecord = actorJson(actorInfo, publicKey); - await db.run('UPDATE accounts SET name = ?, actor = ?', actorName, JSON.stringify(actorRecord)); - } catch (dbError) { - console.error(dbError); - } - }); -} - -setup(); diff --git a/src/activitypub.js b/src/activitypub.js index 999088b..523558f 100644 --- a/src/activitypub.js +++ b/src/activitypub.js @@ -4,12 +4,13 @@ import escapeHTML from 'escape-html'; import { signedGetJSON, signedPostJSON } from './signature.js'; import { actorMatchesUsername, replaceEmptyText } from './util.js'; +import * as db from './database.js'; function getGuidFromPermalink(urlString) { return urlString.match(/(?:\/m\/)([a-zA-Z0-9+/]+)/)[1]; } -export async function signAndSend(message, name, domain, db, targetDomain, inbox) { +export async function signAndSend(message, name, domain, targetDomain, inbox) { try { const response = await signedPostJSON(inbox, { body: JSON.stringify(message), @@ -80,7 +81,7 @@ export function createNoteObject(bookmark, account, domain) { return noteMessage; } -function createMessage(noteObject, bookmarkId, account, domain, db) { +async function createMessage(noteObject, bookmarkId, account, domain) { const guidCreate = crypto.randomBytes(16).toString('hex'); const message = { @@ -92,40 +93,48 @@ function createMessage(noteObject, bookmarkId, account, domain, db) { object: noteObject, }; - db.insertMessage(getGuidFromPermalink(noteObject.id), bookmarkId, JSON.stringify(noteObject)); + // TODO: does "insert or replace" work? + await db.run( + ` + insert or replace into messages + (guid, bookmark_id, message) + values (?, ?, ?) + `, + getGuidFromPermalink(noteObject.id), + bookmarkId, + JSON.stringify(noteObject), + ); return message; } -async function createUpdateMessage(bookmark, account, domain, db) { - const guid = await db.getGuidForBookmarkId(bookmark.id); +async function createUpdateMessage(bookmark, account, domain) { + const guid = (await db.get('select guid from messages where bookmark_id = ?', bookmark.id))?.guid; + + let note = `https://${domain}/m/${guid}`; // if the bookmark was created but not published to activitypub // we might need to just make our own note object to send along - let note; if (guid === undefined) { note = createNoteObject(bookmark, account, domain); - createMessage(note, bookmark.id, account, domain, db); - } else { - note = `https://${domain}/m/${guid}`; + await createMessage(note, bookmark.id, account, domain); } - const updateMessage = { + return { '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'], summary: `${account} updated the bookmark`, type: 'Create', // this should be 'Update' but Mastodon does weird things with Updates actor: `https://${domain}/u/${account}`, object: note, }; - - return updateMessage; } -async function createDeleteMessage(bookmark, account, domain, db) { - const guid = await db.findMessageGuid(bookmark.id); - await db.deleteMessage(guid); +async function createDeleteMessage(bookmark, account, domain) { + const guid = (await db.get('select guid from messages where bookmark_id = ?', bookmark.id))?.guid; + + await db.run('delete from messages where bookmark_id = ?', bookmark.id); - const deleteMessage = { + return { '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'], id: `https://${domain}/m/${guid}`, type: 'Delete', @@ -136,12 +145,11 @@ async function createDeleteMessage(bookmark, account, domain, db) { id: `https://${domain}/m/${guid}`, }, }; - - return deleteMessage; } -export async function createFollowMessage(account, domain, target, db) { +export async function createFollowMessage(account, domain, target) { const guid = crypto.randomBytes(16).toString('hex'); + const followMessage = { '@context': 'https://www.w3.org/ns/activitystreams', id: guid, @@ -150,33 +158,42 @@ export async function createFollowMessage(account, domain, target, db) { object: target, }; - db.insertMessage(guid, null, JSON.stringify(followMessage)); + await db.run( + ` + insert or replace into messages + (guid, bookmark_id, message) + values (?, ?, ?) + `, + guid, + null, + JSON.stringify(followMessage), + ); return followMessage; } -export async function createUnfollowMessage(account, domain, target, db) { +export async function createUnfollowMessage(account, domain, target) { const undoGuid = crypto.randomBytes(16).toString('hex'); - const messageRows = await db.findMessage(target); + const messageRows = await db.all('select * from messages where message like ?', `%${target}%`); console.log('result', messageRows); - const followMessages = messageRows?.filter((row) => { + const followMessage = messageRows.find((row) => { const message = JSON.parse(row.message || '{}'); return message.type === 'Follow' && message.object === target; }); - if (followMessages?.length > 0) { - const undoMessage = { + if (followMessage) { + return { '@context': 'https://www.w3.org/ns/activitystreams', type: 'Undo', id: undoGuid, actor: `${domain}/u/${account}`, - object: followMessages.slice(-1).message, + object: followMessage.message, }; - return undoMessage; } + console.log('tried to find a Follow record in order to unfollow, but failed'); return null; } @@ -210,58 +227,61 @@ export async function lookupActorInfo(actorUsername) { } } -export async function broadcastMessage(bookmark, action, db, account, domain) { +export async function broadcastMessage(bookmark, action, account, domain) { // TODO bail if activitypub not set up - const result = await db.getFollowers(); - const followers = JSON.parse(result); + let followers = (await db.all('select actor from followers')).map((r) => r.actor); - if (followers === null) { + if (!followers.length) { console.log(`No followers for account ${account}@${domain}`); - } else { - const bookmarkPermissions = await db.getPermissionsForBookmark(bookmark.id); - const globalPermissions = await db.getGlobalPermissions(); - const blocklist = - bookmarkPermissions?.blocked - ?.split('\n') - ?.concat(globalPermissions?.blocked?.split('\n')) - .filter((x) => !x?.match(/^@([^@]+)@(.+)$/)) || []; - - // now let's try to remove the blocked users - followers.filter((actor) => { - const matches = blocklist.forEach((username) => { - actorMatchesUsername(actor, username); - }); - - return !matches?.some((x) => x); + return; + } + + const blocked = await db.all( + ` + select actor + from permissions + where status = 0 + and (bookmark_id = 0 or bookmark_id = ?) + `, + bookmark.id, + ); + const blocklist = blocked.map((b) => b.actor); + + // now let's try to remove the blocked users + followers = followers.filter((actor) => { + const matches = blocklist.forEach((username) => { + actorMatchesUsername(actor, username); }); - const noteObject = createNoteObject(await bookmark, account, domain); - let message; - switch (action) { - case 'create': - message = createMessage(noteObject, bookmark.id, account, domain, db); - break; - case 'update': - message = await createUpdateMessage(bookmark, account, domain, db); - break; - case 'delete': - message = await createDeleteMessage(bookmark, account, domain, db); - break; - default: - console.log('unsupported action!'); - return; - } + return !matches?.some((x) => x); + }); - console.log(`sending this message to all followers: ${JSON.stringify(message)}`); + const noteObject = createNoteObject(await bookmark, account, domain); + let message; + switch (action) { + case 'create': + message = await createMessage(noteObject, bookmark.id, account, domain); + break; + case 'update': + message = await createUpdateMessage(bookmark, account, domain); + break; + case 'delete': + message = await createDeleteMessage(bookmark, account, domain); + break; + default: + console.log('unsupported action!'); + return; + } - // eslint-disable-next-line no-restricted-syntax - for (const follower of followers) { - const inbox = `${follower}/inbox`; - const myURL = new URL(follower); - const targetDomain = myURL.host; - signAndSend(message, account, domain, db, targetDomain, inbox); - } + console.log(`sending this message to all followers: ${JSON.stringify(message)}`); + + // eslint-disable-next-line no-restricted-syntax + for (const follower of followers) { + // TODO: Don't assume that this is where the user's inbox is + const inbox = `${follower}/inbox`; + const { host: targetDomain } = new URL(follower); + signAndSend(message, account, domain, targetDomain, inbox); } } diff --git a/src/boot.js b/src/boot.js new file mode 100644 index 0000000..b1d9ace --- /dev/null +++ b/src/boot.js @@ -0,0 +1,194 @@ +import crypto from 'crypto'; +import * as dotenv from 'dotenv'; +import fs from 'fs'; +import { readFile } from 'fs/promises'; +import { open } from 'sqlite'; +import sqlite3 from 'sqlite3'; + +import * as db from './database.js'; +import { ACTOR_SETTING_NAMES } from './util.js'; + +dotenv.config(); + +const IS_ACTIVITYPUB_DB_IMPORTED = 'isActivitypubDbImported'; +const IS_ACCOUNT_FILE_IMPORTED = 'isAccountFileImported'; + +// If we don't have public and private keys, generate them +const generateKeys = async () => { + const { PUBLIC_KEY, PRIVATE_KEY } = db; + + const existingKeys = await db.settings.all([PUBLIC_KEY, PRIVATE_KEY]); + + if (existingKeys[PUBLIC_KEY] && existingKeys[PRIVATE_KEY]) { + return; + } + + await new Promise((resolve, reject) => { + crypto.generateKeyPair( + 'rsa', + { + modulusLength: 4096, + publicKeyEncoding: { + type: 'spki', + format: 'pem', + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem', + }, + }, + async (err, publicKey, privateKey) => { + if (err) { + return reject(err); + } + + await db.settings.set({ + [PUBLIC_KEY]: publicKey, + [PRIVATE_KEY]: privateKey, + }); + + return resolve(); + }, + ); + }); +}; + +// If a legacy activitypub.db database exists and hasn't already been added to +// application.db, import it and inform the user +const importActivitypubDb = async () => { + const dbFile = './.data/activitypub.db'; + + if (!fs.existsSync(dbFile)) { + return; + } + + const isActivitypubDbImported = await db.settings.get(IS_ACTIVITYPUB_DB_IMPORTED); + + if (isActivitypubDbImported) { + console.log('Postmarks detected an activitypub.db file that will no longer be read. You should remove this file.'); + return; + } + + const legacyApDb = await open({ filename: dbFile, driver: sqlite3.Database }); + const legacyAccount = await legacyApDb.get(` + select + name, + actor as actorJson, + privkey as privateKey, + pubkey as publicKey, + followers, + following, + blocks + from accounts + limit 1 + `); + + // There is theoretically an edge case where there's no account record in + // activitypub.db but there are messages and permissions. Let's choose to not + // preserve the data in that edge case. + if (!legacyAccount) { + return; + } + + let newSettings = { + username: legacyAccount.username, + publicKey: legacyAccount.publicKey, + privateKey: legacyAccount.privateKey, + }; + + if (legacyAccount.actorJson) { + const actor = JSON.parse(legacyAccount.actorJson); + newSettings = { + ...newSettings, + displayName: actor.name, + description: actor.summary, + avatar: actor.icon.url, + }; + } + + newSettings = Object.fromEntries(Object.entries(newSettings).filter(([, value]) => Boolean(value))); + + newSettings[IS_ACTIVITYPUB_DB_IMPORTED] = true; + + if (Object.keys(newSettings).length) { + await db.settings.set(newSettings); + } + + // eslint-disable-next-line no-restricted-syntax + for (const key of ['followers', 'following', 'blocks']) { + const records = JSON.parse(legacyAccount[key] || '[]').map((actor) => ({ actor })); + + if (records.length) { + const [insert, values] = db.buildInsert(records); + // eslint-disable-next-line no-await-in-loop + await db.run(`insert into ${key} ${insert}`, values); + } + } + + const messages = await legacyApDb.all('select guid, message, bookmark_id from messages'); + + if (messages.length) { + const [insert, values] = db.buildInsert(messages); + await db.run(`insert into messages ${insert}`, values); + } + + const legacyPermissions = await legacyApDb.all('select bookmark_id, allowed, blocked from permissions'); + + const permissions = []; + + legacyPermissions.forEach(({ bookmark_id: bookmarkId, allowed, blocked }) => { + JSON.parse(allowed).forEach((actor) => { + permissions.push({ bookmark_id: bookmarkId, actor, status: 1 }); + }); + + JSON.parse(blocked).forEach((actor) => { + permissions.push({ bookmark_id: bookmarkId, actor, status: 0 }); + }); + }); + + if (permissions.length) { + const [insert, values] = db.buildInsert(permissions); + await db.run(`insert into permissions ${insert}`, values); + } + + await db.settings.set({ + [IS_ACTIVITYPUB_DB_IMPORTED]: true, + }); + + console.log('Your activitypub.db file has been imported to the database. You should now remove this file.'); +}; + +// If a legacy account.json database exists and hasn't already been added to +// application.db, import it and inform the user +const importAccountJson = async () => { + const jsonPath = new URL('../account.json', import.meta.url); + + if (!fs.existsSync(jsonPath)) { + return; + } + + const isAccountFileImported = await db.settings.get(IS_ACCOUNT_FILE_IMPORTED); + + if (isAccountFileImported) { + console.log('Postmarks detected an account.json file that will no longer be read. You should remove this file.'); + return; + } + + const accountFile = await readFile(jsonPath); + const accountFileData = JSON.parse(accountFile); + + await db.settings.set({ + ...Object.fromEntries(Object.entries(accountFileData).filter(([name]) => ACTOR_SETTING_NAMES.includes(name))), + [IS_ACCOUNT_FILE_IMPORTED]: true, + }); + + console.log('Your account.json file has been imported to the database. You should now remove this file.'); +}; + +const boot = async () => { + await generateKeys(); + await importActivitypubDb(); + await importAccountJson(); +}; + +await boot(); diff --git a/src/database.js b/src/database.js index 019f0b1..2f294d8 100644 --- a/src/database.js +++ b/src/database.js @@ -1,13 +1,15 @@ import fs from 'fs'; import sqlite3 from 'sqlite3'; +export const PUBLIC_KEY = 'publicKey'; +export const PRIVATE_KEY = 'privateKey'; + const schema = fs.readFileSync('./src/schema.sql').toString(); const connect = new Promise((resolve, reject) => { const result = new sqlite3.Database('./.data/application.db', (error) => { if (error) { reject(error); } else { - console.log('i have hereby connected :)'); result.exec(schema, (execError) => { if (execError) { reject(execError); @@ -34,74 +36,102 @@ const query = }); }; -const run = query('run'); -const get = query('get'); -const all = query('all'); +export const run = query('run'); +export const get = query('get'); +export const all = query('all'); -export const getSetting = async (name) => { - const result = await get('select value from settings where name = ?', name); +export const settings = { + all: async (names) => { + // TODO: There must be a way to get node-sqlite3 to accept parameters for an + // `IN` clause but I cannot find it. For now let's naïvely assume that every + // name matches this pattern. This probably isn't a requirement that's worth + // enforcing elsewhere in business logic, and this exact bit of code will + // likely lead to weird bugs in the future, but for now it's maybe better to + // be safe! + if (!names.every((name) => name.match(/^[a-z0-9-_ ./]+$/i))) { + throw new Error('Names contain unexpected characters'); + } - if (!result) { - return null; - } + const rows = await all(` + select name, value + from settings + where name in (${names.map((n) => `'${n}'`).join(',')}) + `); - const { value } = result; + // For the caller of this function, a setting that isn't actually written to + // the database should be indistinguishable from a value that is set to + // `null`, so we backfill any missing settings to our results. + return { + ...Object.fromEntries(names.map((name) => [name, null])), + ...Object.fromEntries(rows.map(({ name, value }) => [name, JSON.parse(value)])), + }; + }, + get: (name) => settings.all([name])[0], + set: async (obj) => { + // TODO: See caveat in settings.all + if (!Object.keys(obj).every((name) => name.match(/^[a-z0-9-_ ./]+$/i))) { + throw new Error('Names contain unexpected characters'); + } - if (typeof value === 'string') { - return JSON.parse(value); - } + const values = Object.entries(obj).map( + ([name, value]) => + // TODO: Escape this properly + `('${name}', '${JSON.stringify(value).replace("'", "\\'")}')`, + ); - return null; + await run(` + insert into settings + (name, value) + values ${values.join(',')} + on conflict (name) do update set value = excluded.value + `); + }, }; -export const getSettings = async (names) => { - // TODO: There must be a way to get node-sqlite3 to accept parameters for an - // `IN` clause but I cannot find it. For now let's naïvely assume that every - // name matches this pattern. This probably isn't a requirement that's worth - // enforcing elsewhere in business logic, and this exact bit of code will - // likely like to weird bugs in the future, but for now it's maybe better to - // be safe! - if (!names.every((name) => name.match(/^[a-z0-9-_ ./]+$/i))) { - throw new Error('Names contain unexpected characters'); +export const getPublicKey = () => settings.get(PUBLIC_KEY); +export const getPrivateKey = () => settings.get(PRIVATE_KEY); + +// Returns an array with two items: a string and an array of values. The string +// is a fragment of an SQL insert statement that comes after +// `insert into table_name` and includes columns and placeholder values; the +// array is a flat list of values that correspond to the placeholders. +export const buildInsert = (records) => { + const recordsArray = records instanceof Array ? records : [records]; + + if (!recordsArray.every((r) => typeof r === 'object')) { + throw new Error('`records` must be either an object or an array of objects'); } - const rows = await all(` - select name, value - from settings - where name in (${names.map((n) => `'${n}'`).join(',')}) - `); + if (!recordsArray.length) { + throw new Error('No records provided'); + } - return Object.fromEntries(rows.map(({ name, value }) => [name, JSON.parse(value)])); -}; + // Get a unique, sorted list of all the keys in all the records + const columns = recordsArray + .map(Object.keys) + .flat() + .filter((m, i, a) => a.indexOf(m) === i) + .sort(); -export const setSettings = async (obj) => { - // TODO: See caveat in getSettings - if (!Object.keys(obj).every((name) => name.match(/^[a-z0-9-_ ./]+$/i))) { - throw new Error('Names contain unexpected characters'); + const keysTest = columns.join(','); + + if (!recordsArray.every((r) => Object.keys(r).sort().join(',') === keysTest)) { + throw new Error('Every object in `records` must contain exactly the same keys'); } - const values = Object.entries(obj).map( - ([name, value]) => - // TODO: Escape this properly - `('${name}', '${JSON.stringify(value).replace("'", "\\'")}')`, - ); - - await run(` - insert into settings - (name, value) - values ${values.join(',')} - on conflict (name) do update set value = excluded.value - `); -}; + // This check is naïve but should prevent SQL injections + columns.forEach((column) => { + if (!column.match(/^[a-z][a-z_]*$/)) { + throw new Error(`Invalid column name "${column}"`); + } + }); -export const setSetting = async (name, value) => { - return run( - ` - insert into settings (name, value) - values (?, ?) - on conflict (name) do update set value = excluded.value - `, - name, - JSON.stringify(value), - ); + // Creates a string like '(?,?,?)' where each `?` corresponds to one string in + // `columns` + const recordString = `(${new Array(columns.length).fill('?').join(',')})`; + + return [ + `(${columns.join(',')}) values ${new Array(records.length).fill(recordString).join(',')}`, + records.map((r) => columns.map((c) => r[c])).flat(), + ]; }; diff --git a/src/pages/admin/data.hbs b/src/pages/admin/data.hbs index 42e596b..46c6e33 100644 --- a/src/pages/admin/data.hbs +++ b/src/pages/admin/data.hbs @@ -8,11 +8,13 @@

  • sqlite database
  • csv
  • -

    - It’s less likely you’ll need this, but you can also download your - activitypub.db - here. -

    +{{#if hasLegacyActivitypubDb}} +

    + It’s less likely you’ll need this, but you can also download your + activitypub.db + here. +

    +{{/if}}

    diff --git a/src/routes/activitypub/inbox.js b/src/routes/activitypub/inbox.js index 712292e..8803844 100644 --- a/src/routes/activitypub/inbox.js +++ b/src/routes/activitypub/inbox.js @@ -1,7 +1,8 @@ import express from 'express'; import crypto from 'crypto'; import * as linkify from 'linkifyjs'; -import { actorMatchesUsername, parseJSON } from '../../util.js'; +import * as db from '../../database.js'; +import { actorMatchesUsername } from '../../util.js'; import { signAndSend, getInboxFromActorProfile } from '../../activitypub.js'; import { signedGetJSON } from '../../signature.js'; @@ -9,7 +10,6 @@ import { signedGetJSON } from '../../signature.js'; const router = express.Router(); async function sendAcceptMessage(thebody, name, domain, req, res, targetDomain) { - const db = req.app.get('apDb'); const guid = crypto.randomBytes(16).toString('hex'); const message = { '@context': 'https://www.w3.org/ns/activitystreams', @@ -21,130 +21,64 @@ async function sendAcceptMessage(thebody, name, domain, req, res, targetDomain) const inbox = await getInboxFromActorProfile(message.object.actor); - signAndSend(message, name, domain, db, targetDomain, inbox); + signAndSend(message, name, domain, targetDomain, inbox); } async function handleFollowRequest(req, res) { const domain = req.app.get('domain'); - const apDb = req.app.get('apDb'); - const myURL = new URL(req.body.actor); - const targetDomain = myURL.hostname; + const { hostname: targetDomain } = new URL(req.body.actor); const name = req.body.object.replace(`https://${domain}/u/`, ''); await sendAcceptMessage(req.body, name, domain, req, res, targetDomain); - // Add the user to the DB of accounts that follow the account - - // get the followers JSON for the user - const oldFollowersText = (await apDb.getFollowers()) || '[]'; - - // update followers - let followers = parseJSON(oldFollowersText); - if (followers) { - followers.push(req.body.actor); - // unique items - followers = [...new Set(followers)]; - } else { - followers = [req.body.actor]; - } - const newFollowersText = JSON.stringify(followers); - try { - // update into DB - await apDb.setFollowers(newFollowersText); - - console.log('updated followers!'); - } catch (e) { - console.log('error storing followers after follow', e); - } + const { actor } = req.body; + await db.run('insert into followers (actor) values ? on conflict (actor) do nothing', actor); return res.status(200); } async function handleUnfollow(req, res) { const domain = req.app.get('domain'); - const apDb = req.app.get('apDb'); - const myURL = new URL(req.body.actor); const targetDomain = myURL.hostname; const name = req.body.object.object.replace(`https://${domain}/u/`, ''); await sendAcceptMessage(req.body, name, domain, req, res, targetDomain); - // get the followers JSON for the user - const oldFollowersText = (await apDb.getFollowers()) || '[]'; - - // update followers - const followers = parseJSON(oldFollowersText); - if (followers) { - followers.forEach((follower, idx) => { - if (follower === req.body.actor) { - followers.splice(idx, 1); - } - }); - } - - const newFollowersText = JSON.stringify(followers); - - try { - await apDb.setFollowers(newFollowersText); - return res.sendStatus(200); - } catch (e) { - console.log('error storing followers after unfollow', e); - return res.status(500); - } -} - -async function handleFollowAccepted(req, res) { - const apDb = req.app.get('apDb'); - - const oldFollowingText = (await apDb.getFollowing()) || '[]'; - - let follows = parseJSON(oldFollowingText); - - if (follows) { - follows.push(req.body.actor); - // unique items - follows = [...new Set(follows)]; - } else { - follows = [req.body.actor]; - } - const newFollowingText = JSON.stringify(follows); - - try { - // update into DB - await apDb.setFollowing(newFollowingText); - - console.log('updated following!'); - return res.status(200); - } catch (e) { - console.log('error storing follows after follow action', e); - return res.status(500); - } + const { actor } = req.body; + await db.run('delete from followers where actor = ?', actor); + return res.sendStatus(200); } async function handleCommentOnBookmark(req, res, inReplyToGuid) { - const apDb = req.app.get('apDb'); - - const bookmarkId = await apDb.getBookmarkIdFromMessageGuid(inReplyToGuid); + const bookmarkId = (await db.get('select bookmark_id from messages where guid = ?', inReplyToGuid))?.bookmark_id; if (typeof bookmarkId !== 'number') { console.log("couldn't find a bookmark this message is related to"); return res.sendStatus(400); } - const bookmarkPermissions = await apDb.getPermissionsForBookmark(bookmarkId); - const globalPermissions = await apDb.getGlobalPermissions(); - - const bookmarkBlocks = bookmarkPermissions?.blocked?.split('\n') || []; - const globalBlocks = globalPermissions?.blocked?.split('\n') || []; + const permissions = await db.all( + ` + select actor, status + from permissions + where (bookmark_id = 0 or bookmark_id = ?) + `, + bookmarkId, + ); - const bookmarkAllows = bookmarkPermissions?.allowed?.split('\n') || []; - const globalAllows = globalPermissions?.allowed?.split('\n') || []; + const blocklist = []; + const allowlist = []; - const blocklist = bookmarkBlocks.concat(globalBlocks).filter((x) => x.match(/^@([^@]+)@(.+)$/)); - const allowlist = bookmarkAllows.concat(globalAllows).filter((x) => x.match(/^@([^@]+)@(.+)$/)); + permissions.forEach(({ actor, status }) => { + if (status) { + allowlist.push(actor); + } else { + blocklist.push(actor); + } + }); - if (blocklist.length > 0 && blocklist.map((username) => actorMatchesUsername(req.body.actor, username)).some((x) => x)) { + if (blocklist.some((username) => actorMatchesUsername(req.body.actor, username)).some((x) => x)) { console.log(`Actor ${req.body.actor} matches a blocklist item, ignoring comment`); return res.sendStatus(403); } @@ -158,7 +92,7 @@ async function handleCommentOnBookmark(req, res, inReplyToGuid) { const commentUrl = req.body.object.id; let visible = 0; - if (allowlist.map((username) => actorMatchesUsername(req.body.actor, username)).some((x) => x)) { + if (allowlist.some((username) => actorMatchesUsername(req.body.actor, username)).some((x) => x)) { console.log(`Actor ${req.body.actor} matches an allowlist item, marking comment visible`); visible = 1; } @@ -218,7 +152,8 @@ router.post('/', async function (req, res) { return handleUnfollow(req, res); } if (req.body.type === 'Accept' && req.body.object?.type === 'Follow') { - return handleFollowAccepted(req, res); + await db.run('insert into following (actor) values ? on conflict (actor) do nothing', req.body.actor); + return res.status(200); } if (req.body.type === 'Delete') { return handleDeleteRequest(req, res); diff --git a/src/routes/activitypub/message.js b/src/routes/activitypub/message.js index 6ab053e..32a6268 100644 --- a/src/routes/activitypub/message.js +++ b/src/routes/activitypub/message.js @@ -1,5 +1,6 @@ import express from 'express'; import { synthesizeActivity } from '../../activitypub.js'; +import * as db from '../../database.js'; const router = express.Router(); @@ -16,14 +17,12 @@ router.get('/:guid', async (req, res) => { return res.status(400).send('Bad request.'); } - const db = req.app.get('apDb'); - if (!req.headers.accept?.includes('json')) { const bookmarkId = await db.getBookmarkIdFromMessageGuid(guid); return res.redirect(`/bookmark/${bookmarkId}`); } - const result = await db.getMessage(guid); + const result = await db.get('select message from messages where guid = ?', guid); if (result === undefined) { return res.status(404).send(`No message found for ${guid}.`); diff --git a/src/routes/activitypub/user.js b/src/routes/activitypub/user.js index 0d4ed1a..30457ee 100644 --- a/src/routes/activitypub/user.js +++ b/src/routes/activitypub/user.js @@ -1,57 +1,60 @@ import express from 'express'; +import path from 'path'; import { synthesizeActivity } from '../../activitypub.js'; -import { getActorInfo } from '../../util.js'; +import { getActorInfo, domain } from '../../util.js'; +import * as db from '../../database.js'; const router = express.Router(); router.get('/:name', async (req, res) => { - let { name } = req.params; - if (!name) { - return res.status(400).send('Bad request.'); - } + const { name } = req.params; + if (!req.headers.accept?.includes('json')) { return res.redirect('/'); } - const db = req.app.get('apDb'); - const domain = req.app.get('domain'); - const username = name; - name = `${name}@${domain}`; + const { username, avatar, displayName, description, publicKey } = await getActorInfo(); - const actor = await db.getActor(); - - if (actor === undefined) { + if (username !== name) { return res.status(404).send(`No actor record found for ${name}.`); } - const tempActor = JSON.parse(actor); - // Added this followers URI for Pleroma compatibility, see https://github.com/dariusk/rss-to-activitypub/issues/11#issuecomment-471390881 - // New Actors should have this followers URI but in case of migration from an old version this will add it in on the fly - if (tempActor.followers === undefined) { - tempActor.followers = `https://${domain}/u/${username}/followers`; - } - if (tempActor.outbox === undefined) { - tempActor.outbox = `https://${domain}/u/${username}/outbox`; - } - return res.json(tempActor); + + return res.json({ + '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'], + + id: `https://${domain}/u/${username}`, + type: 'Person', + preferredUsername: username, + name: displayName, + summary: description, + icon: { + type: 'Image', + mediaType: `image/${path.extname(avatar).slice(1)}`, + url: avatar, + }, + inbox: `https://${domain}/api/inbox`, + outbox: `https://${domain}/u/${username}/outbox`, + followers: `https://${domain}/u/${username}/followers`, + following: `https://${domain}/u/${username}/following`, + + publicKey: { + id: `https://${domain}/u/${username}#main-key`, + owner: `https://${domain}/u/${username}`, + publicKeyPem: publicKey, + }, + }); }); router.get('/:name/followers', async (req, res) => { const { name } = req.params; + if (!name) { return res.status(400).send('Bad request.'); } - const db = req.app.get('apDb'); - const domain = req.app.get('domain'); - - let followers = await db.getFollowers(); - if (followers === undefined) { - followers = []; - } else { - followers = JSON.parse(followers); - } + const followers = (await db.all('select actor from followers')).map(({ actor }) => actor); - const followersCollection = { + return res.json({ type: 'OrderedCollection', totalItems: followers?.length || 0, id: `https://${domain}/u/${name}/followers`, @@ -63,48 +66,42 @@ router.get('/:name/followers', async (req, res) => { id: `https://${domain}/u/${name}/followers?page=1`, }, '@context': ['https://www.w3.org/ns/activitystreams'], - }; - return res.json(followersCollection); + }); }); router.get('/:name/following', async (req, res) => { const { name } = req.params; + if (!name) { return res.status(400).send('Bad request.'); } - const db = req.app.get('apDb'); - const domain = req.app.get('domain'); - const followingText = (await db.getFollowing()) || '[]'; - const following = JSON.parse(followingText); + const following = (await db.all('select actor from following')).map(({ actor }) => actor); - const followingCollection = { + return res.json({ type: 'OrderedCollection', - totalItems: following?.length || 0, + totalItems: following.length, id: `https://${domain}/u/${name}/following`, first: { type: 'OrderedCollectionPage', - totalItems: following?.length || 0, + totalItems: following.length, partOf: `https://${domain}/u/${name}/following`, orderedItems: following, id: `https://${domain}/u/${name}/following?page=1`, }, '@context': ['https://www.w3.org/ns/activitystreams'], - }; - return res.json(followingCollection); + }); }); router.get('/:name/outbox', async (req, res) => { - const domain = req.app.get('domain'); const { username: account } = await getActorInfo(); - const apDb = req.app.get('apDb'); function pageLink(p) { return `https://${domain}/u/${account}/outbox?page=${p}`; } const pageSize = 20; - const totalCount = await apDb.getMessageCount(); + const totalCount = (await db.get('select count(message) as count from messages')).count; const lastPage = Math.ceil(totalCount / pageSize); if (req.query?.page === undefined) { @@ -129,7 +126,17 @@ router.get('/:name/outbox', async (req, res) => { if (page < 1 || page > lastPage) return res.status(400).send('Invalid page number'); const offset = (page - 1) * pageSize; - const notes = await apDb.getMessages(offset, pageSize); + const notes = await db.all( + ` + select message + from messages + order by bookmark_id desc + limit ? + offset ? + `, + pageSize, + offset, + ); const activities = notes.map((n) => synthesizeActivity(JSON.parse(n.message))); const collectionPage = { diff --git a/src/routes/activitypub/webfinger.js b/src/routes/activitypub/webfinger.js index 7ceff24..7237903 100644 --- a/src/routes/activitypub/webfinger.js +++ b/src/routes/activitypub/webfinger.js @@ -1,21 +1,35 @@ import express from 'express'; +import { getActorInfo, domain } from '../../util.js'; + +const ERROR_MESSAGE = 'Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.'; + const router = express.Router(); router.get('/', async (req, res) => { const { resource } = req.query; if (!resource || !resource.includes('acct:')) { - return res.status(400).send('Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.'); + return res.status(400).send(ERROR_MESSAGE); } const name = resource.replace('acct:', ''); - const db = req.app.get('apDb'); - const webfinger = await db.getWebfinger(); - if (webfinger === undefined) { + const { username } = await getActorInfo(); + const actorName = `${username}@${domain}`; + + if (name !== actorName) { return res.status(404).send(`No webfinger record found for ${name}.`); } - return res.json(JSON.parse(webfinger)); + return res.json({ + subject: `acct:${actorName}`, + links: [ + { + rel: 'self', + type: 'application/activity+json', + href: `https://${domain}/u/${username}`, + }, + ], + }); }); export default router; diff --git a/src/routes/admin.js b/src/routes/admin.js index c4aa697..f985507 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -1,7 +1,9 @@ import express from 'express'; +import fs from 'fs'; // eslint-disable-next-line import/no-unresolved, node/no-missing-import import { stringify as csvStringify } from 'csv-stringify/sync'; // https://github.com/adaltas/node-csv/issues/323 -import { domain, getActorInfo, parseJSON } from '../util.js'; +import { domain, getActorInfo } from '../util.js'; +import * as db from '../database.js'; import { isAuthenticated } from '../session-auth.js'; import { lookupActorInfo, createFollowMessage, createUnfollowMessage, signAndSend, getInboxFromActorProfile } from '../activitypub.js'; @@ -40,32 +42,28 @@ router.get('/followers', isAuthenticated, async (req, res) => { params.adminLinks = ADMIN_LINKS; params.currentPath = req.originalUrl; - const apDb = req.app.get('apDb'); - const { actorInfo } = await getActorInfo(); // TODO if (actorInfo.disabled) { return res.render('nonfederated', params); } - const permissions = await apDb.getGlobalPermissions(); + params.followers = (await db.all('select actor from followers')).map(({ actor }) => actor); - try { - const followers = await apDb.getFollowers(); - params.followers = JSON.parse(followers || '[]'); - } catch (e) { - console.log('Error fetching followers for admin page'); - } + params.blocks = (await db.all('select actor from blocks')).map(({ actor }) => actor); - try { - const blocks = await apDb.getBlocks(); - params.blocks = JSON.parse(blocks || '[]'); - } catch (e) { - console.log('Error fetching blocks for admin page'); - } + const permissions = await db.get('select actor, status from permissions where bookmark_id = 0'); - params.allowed = permissions?.allowed || ''; - params.blocked = permissions?.blocked || ''; + params.blocked = []; + params.allowed = []; + + permissions.forEach(({ actor, status }) => { + if (status) { + params.blocked.push(actor); + } else { + params.allowed.push(actor); + } + }); return res.render('admin/followers', params); }); @@ -75,20 +73,14 @@ router.get('/following', isAuthenticated, async (req, res) => { params.adminLinks = ADMIN_LINKS; params.currentPath = req.originalUrl; - const apDb = req.app.get('apDb'); - const { actorInfo } = await getActorInfo(); + // TODO if (actorInfo.disabled) { return res.render('nonfederated', params); } - try { - const following = await apDb.getFollowing(); - params.following = JSON.parse(following || '[]'); - } catch (e) { - console.log('Error fetching followers for admin page'); - } + params.following = (await db.all('select actor from following')).map(({ actor }) => actor); return res.render('admin/following', params); }); @@ -97,6 +89,7 @@ router.get('/data', isAuthenticated, async (req, res) => { const params = req.query.raw ? {} : { title: 'Data export' }; params.adminLinks = ADMIN_LINKS; params.currentPath = req.originalUrl; + params.hasLegacyActivitypubDb = fs.existsSync(`${DATA_PATH}/activitypub.db`); return res.render('admin/data', params); }); @@ -124,6 +117,11 @@ router.get('/bookmarks.csv', isAuthenticated, async (req, res) => { router.get('/activitypub.db', isAuthenticated, async (req, res) => { const filePath = `${DATA_PATH}/activitypub.db`; + if (!fs.existsSync(filePath)) { + res.status(404).send('This Postmarks instance does not include a legacy actiivtypub.db file.'); + return; + } + res.setHeader('Content-Type', 'application/vnd.sqlite3'); res.setHeader('Content-Disposition', 'attachment; filename="activitypub.db"'); @@ -131,79 +129,25 @@ router.get('/activitypub.db', isAuthenticated, async (req, res) => { }); router.post('/followers/block', isAuthenticated, async (req, res) => { - const db = req.app.get('apDb'); - - const oldFollowersText = (await db.getFollowers()) || '[]'; - - // update followers - const followers = parseJSON(oldFollowersText); - if (followers) { - followers.forEach((follower, idx) => { - if (follower === req.body.actor) { - followers.splice(idx, 1); - } - }); - } - - const newFollowersText = JSON.stringify(followers); + const { actor } = req.body; - try { - await db.setFollowers(newFollowersText); - } catch (e) { - console.log('error storing followers after unfollow', e); + if (!actor) { + return res.status(400).send('No actor specified'); } - const oldBlocksText = (await db.getBlocks()) || '[]'; - - let blocks = parseJSON(oldBlocksText); + await db.run('delete from followers where actor = ?', actor); - if (blocks) { - blocks.push(req.body.actor); - // unique items - blocks = [...new Set(blocks)]; - } else { - blocks = [req.body.actor]; - } - const newBlocksText = JSON.stringify(blocks); - try { - // update into DB - await db.setBlocks(newBlocksText); + await db.run('insert into blocks (actor) values ? on conflict (actor) do nothing', actor); - console.log('updated blocks!'); - } catch (e) { - console.log('error storing blocks after block action', e); - } - - res.redirect('/admin/followers'); + return res.redirect('/admin/followers'); }); router.post('/followers/unblock', isAuthenticated, async (req, res) => { - const db = req.app.get('apDb'); - - const oldBlocksText = (await db.getBlocks()) || '[]'; - - const blocks = parseJSON(oldBlocksText); - if (blocks) { - blocks.forEach((block, idx) => { - if (block === req.body.actor) { - blocks.splice(idx, 1); - } - }); - } - - const newBlocksText = JSON.stringify(blocks); - - try { - await db.setBlocks(newBlocksText); - } catch (e) { - console.log('error storing blocks after unblock action', e); - } - + await db.run('delete from blocks where actor = ?', req.body.actor); res.redirect('/admin/followers'); }); router.post('/following/follow', isAuthenticated, async (req, res) => { - const db = req.app.get('apDb'); const { username: account } = await getActorInfo(); const canonicalUrl = await lookupActorInfo(req.body.actor); @@ -212,8 +156,8 @@ router.post('/following/follow', isAuthenticated, async (req, res) => { const inbox = await getInboxFromActorProfile(canonicalUrl); if (inbox) { - const followMessage = await createFollowMessage(account, domain, canonicalUrl, db); - signAndSend(followMessage, account, domain, db, req.body.actor.split('@').slice(-1), inbox); + const followMessage = await createFollowMessage(account, domain, canonicalUrl); + signAndSend(followMessage, account, domain, req.body.actor.split('@').slice(-1), inbox); } return res.redirect('/admin/following'); @@ -224,49 +168,42 @@ router.post('/following/follow', isAuthenticated, async (req, res) => { }); router.post('/following/unfollow', isAuthenticated, async (req, res) => { - const db = req.app.get('apDb'); const { username: account } = await getActorInfo(); + const { actor } = req.body; - const oldFollowsText = (await db.getFollowing()) || '[]'; - - const follows = parseJSON(oldFollowsText); - if (follows) { - follows.forEach((follow, idx) => { - if (follow === req.body.actor) { - follows.splice(idx, 1); - } - }); - - const inbox = await getInboxFromActorProfile(req.body.actor); - - const unfollowMessage = createUnfollowMessage(account, domain, req.body.actor, db); - - signAndSend(unfollowMessage, account, domain, db, new URL(req.body.actor).hostname, inbox); - - const newFollowsText = JSON.stringify(follows); - - try { - await db.setFollowing(newFollowsText); - } catch (e) { - console.log('error storing follows after unfollow action', e); - } - return res.redirect('/admin/following'); - } - return res.status(500).send('Encountered an error processing existing following list'); + await db.run('delete from followers where actor = ?', actor); + const inbox = await getInboxFromActorProfile(actor); + const unfollowMessage = createUnfollowMessage(account, domain, actor); + signAndSend(unfollowMessage, account, domain, new URL(actor).hostname, inbox); + return res.redirect('/admin/following'); }); router.post('/permissions', isAuthenticated, async (req, res) => { - const apDb = req.app.get('apDb'); - - await apDb.setGlobalPermissions(req.body.allowed, req.body.blocked); + const { allowed, blocked } = req.body; + + const records = JSON.parse(allowed) + .map((actor) => ({ bookmark_id: 0, actor, status: 1 })) + .concat(JSON.parse(blocked).map((actor) => ({ bookmark_id: 0, actor, status: 0 }))); + + if (records.length) { + const [insert, values] = db.buildInsert(records); + + await db.run( + ` + insert into permissions + ${insert} + on conflict (bookmark_id, actor) do update set status = excluded.status + `, + values, + ); + } res.redirect('/admin'); }); router.post('/reset', isAuthenticated, async (req, res) => { - const db = req.app.get('bookmarksDb'); - - await db.deleteAllBookmarks(); + const bookmarksDb = req.app.get('bookmarksDb'); + await bookmarksDb.deleteAllBookmarks(); res.redirect('/admin'); }); diff --git a/src/routes/bookmark.js b/src/routes/bookmark.js index 7a8c68f..f22c66f 100644 --- a/src/routes/bookmark.js +++ b/src/routes/bookmark.js @@ -4,6 +4,7 @@ import ogScraper from 'open-graph-scraper'; import { data, getActorInfo, domain, removeEmpty } from '../util.js'; import { broadcastMessage } from '../activitypub.js'; import { isAuthenticated } from '../session-auth.js'; +import * as db from '../database.js'; const router = express.Router(); export default router; @@ -100,7 +101,6 @@ router.get('/:id', async (req, res) => { router.get('/:id/edit', isAuthenticated, async (req, res) => { const params = req.query.raw ? {} : { ephemeral: false }; const bookmarksDb = req.app.get('bookmarksDb'); - const apDb = req.app.get('apDb'); const bookmark = await bookmarksDb.getBookmark(req.params.id); bookmark.tagsArray = encodeURIComponent(JSON.stringify(bookmark.tags?.split(' ').map((b) => b.slice(1)) || [])); @@ -109,9 +109,18 @@ router.get('/:id/edit', isAuthenticated, async (req, res) => { if (!bookmark) { params.error = data.errorMessage; } else { - const permissions = await apDb.getPermissionsForBookmark(req.params.id); - params.allowed = permissions?.allowed; - params.blocked = permissions?.blocked; + params.allowed = []; + params.blocked = []; + + const permissions = await db.all('select * from permissions where bookmark_id = ?', req.params.id); + + permissions.forEach(({ actor, status }) => { + if (status) { + params.allowed.push(actor); + } else { + params.blocked.push(actor); + } + }); params.title = 'Edit Bookmark'; params.bookmark = bookmark; @@ -125,13 +134,12 @@ router.post('/:id/delete', isAuthenticated, async (req, res) => { const params = {}; const { id } = req.params; const bookmarksDb = req.app.get('bookmarksDb'); - const apDb = req.app.get('apDb'); await bookmarksDb.deleteBookmark(id); const { username: account } = await getActorInfo(); - broadcastMessage({ id }, 'delete', apDb, account, domain); + await broadcastMessage({ id }, 'delete', account, domain); return req.query.raw ? res.send(params) : res.redirect('/'); }); @@ -183,7 +191,6 @@ router.post('/multiadd', isAuthenticated, async (req, res) => { router.post('/:id?', isAuthenticated, async (req, res) => { const bookmarksDb = req.app.get('bookmarksDb'); - const apDb = req.app.get('apDb'); const params = {}; const { id } = req.params; @@ -223,11 +230,30 @@ router.post('/:id?', isAuthenticated, async (req, res) => { description: req.body.description.trim(), tags, }); - await apDb.setPermissionsForBookmark(id, req.body.allowed || '', req.body.blocked || ''); + + const { allowed, blocked } = req.body; + + const records = JSON.parse(allowed) + .map((actor) => ({ bookmark_id: id, actor, status: 1 })) + .concat(JSON.parse(blocked).map((actor) => ({ bookmark_id: id, actor, status: 0 }))); + + if (records.length) { + const [insert, values] = db.buildInsert(records); + + await db.run( + ` + insert into permissions + ${insert} + on conflict (bookmark_id, actor) + do update set status = excluded.status + `, + values, + ); + } const { username: account } = await getActorInfo(); - broadcastMessage(bookmark, 'update', apDb, account, domain); + await broadcastMessage(bookmark, 'update', account, domain); } } else { const noTitle = req.body.title === ''; @@ -259,7 +285,7 @@ router.post('/:id?', isAuthenticated, async (req, res) => { const { username: account } = await getActorInfo(); - broadcastMessage(bookmark, 'create', apDb, account, domain); + await broadcastMessage(bookmark, 'create', account, domain); } params.bookmarks = bookmark; diff --git a/src/schema.sql b/src/schema.sql index cfeccd0..80f99c8 100644 --- a/src/schema.sql +++ b/src/schema.sql @@ -10,3 +10,31 @@ values ('displayName', '"Postmarks"'), ('description', '"An ActivityPub bookmarking and sharing site built with Postmarks"') on conflict (name) do nothing; + +create table if not exists followers ( + actor text primary key +); + +create table if not exists following ( + actor text primary key +); + +create table if not exists blocks ( + actor text primary key +); + +-- TODO: index messages on bookmark_id +create table if not exists messages ( + guid text primary key, + message text, + bookmark_id integer +); + +-- TODO add index and unique constraint on (bookmark_id, actor) +create table if not exists permissions ( + bookmark_id integer not null, + actor text not null default '', + -- 0 = blocked + -- 1 = allowed + status integer not null +); diff --git a/src/signature.js b/src/signature.js index ffc5820..c515b57 100644 --- a/src/signature.js +++ b/src/signature.js @@ -2,7 +2,7 @@ import crypto from 'crypto'; import fetch from 'node-fetch'; import { getActorInfo, domain } from './util.js'; -import { getPrivateKey } from './activity-pub-db.js'; +import { getPrivateKey } from './database.js'; /** * Returns base-64 encoded SHA-256 digest of provided data @@ -18,16 +18,16 @@ function getDigest(data) { /** * Returns base-64 encoded string signed with user's RSA private key * - * @param {string} privkey - Postmarks user's private key + * @param {string} privateKey - Postmarks user's private key * @param {string} data - UTF-8 string to sign * * @returns {string} */ -function getSignature(privkey, data) { +function getSignature(privateKey, data) { const signer = crypto.createSign('sha256'); signer.update(data); signer.end(); - return signer.sign(privkey).toString('base64'); + return signer.sign(privateKey).toString('base64'); } /** @@ -93,11 +93,7 @@ function getSignatureHeader(account, signature, signatureKeys) { export async function signedFetch(url, init = {}) { const { username: account } = await getActorInfo(); - const privkey = await getPrivateKey(`${account}@${domain}`); - if (!privkey) { - throw new Error(`No private key found for ${account}.`); - } - + const privateKey = await getPrivateKey(); const { headers = {}, body = null, method = 'GET', ...rest } = init; const signatureParams = getSignatureParams(body, method, url); @@ -105,7 +101,7 @@ export async function signedFetch(url, init = {}) { const stringToSign = Object.entries(signatureParams) .map(([k, v]) => `${k}: ${v}`) .join('\n'); - const signature = getSignature(privkey, stringToSign); + const signature = getSignature(privateKey, stringToSign); const signatureHeader = getSignatureHeader(account, signature, signatureKeys); return fetch(url, { diff --git a/src/util.js b/src/util.js index f5a4e7a..b899dbc 100644 --- a/src/util.js +++ b/src/util.js @@ -1,38 +1,16 @@ +import chalk from 'chalk'; import fs from 'fs'; import { readFile } from 'fs/promises'; -import chalk from 'chalk'; -import * as dotenv from 'dotenv'; import * as db from './database.js'; -dotenv.config(); - -const ACTOR_SETTING_NAMES = ['username', 'avatar', 'displayName', 'description']; -const IS_ACCOUNT_FILE_IMPORTED = 'isAccountFileImported'; +export const ACTOR_SETTING_NAMES = ['username', 'avatar', 'displayName', 'description', 'publicKey']; export const data = { errorMessage: 'Whoops! Error connecting to the database–please try again!', setupMessage: "🚧 Whoops! Looks like the database isn't setup yet! 🚧", }; -try { - const accountFile = await readFile(new URL('../account.json', import.meta.url)); - const accountFileData = JSON.parse(accountFile); - const isAccountFileImported = await db.getSetting(IS_ACCOUNT_FILE_IMPORTED); - if (isAccountFileImported) { - console.log('Postmarks detected an account.json file that will no longer be read. You should remove this file.'); - } else { - await db.setSettings({ - ...Object.fromEntries(Object.entries(accountFileData).filter(([name]) => ACTOR_SETTING_NAMES.includes(name))), - [IS_ACCOUNT_FILE_IMPORTED]: true, - }); - console.log('Your account.json file has been imported to the database. You should now remove this file.'); - } -} catch (e) { - // TODO: Check for existence of account.json instead of catching error - console.log('Failed to read account.json', e); -} - -export const getActorInfo = () => db.getSettings(ACTOR_SETTING_NAMES); +export const getActorInfo = () => db.settings.all(ACTOR_SETTING_NAMES); export const domain = (() => { if (process.env.PUBLIC_BASE_URL) { From 71d985a12a6cee0901d440cc51fa278c2a9ea327 Mon Sep 17 00:00:00 2001 From: John Holdun Date: Tue, 14 Nov 2023 19:50:06 -0800 Subject: [PATCH 4/4] Install dependencies --- package-lock.json | 666 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 624 insertions(+), 42 deletions(-) diff --git a/package-lock.json b/package-lock.json index ac6a4ee..b1994b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,6 +61,9 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "node_modules/@eslint-community/regexpp": { @@ -90,6 +93,9 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/@eslint/eslintrc/node_modules/debug": { @@ -102,6 +108,11 @@ }, "engines": { "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/@eslint/eslintrc/node_modules/ms": { @@ -149,6 +160,11 @@ }, "engines": { "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/@humanwhocodes/config-array/node_modules/ms": { @@ -164,6 +180,10 @@ "dev": true, "engines": { "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, "node_modules/@humanwhocodes/object-schema": { @@ -200,6 +220,14 @@ }, "engines": { "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": { @@ -294,6 +322,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", "optional": true, "dependencies": { "mkdirp": "^1.0.4", @@ -318,6 +347,9 @@ }, "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" } }, "node_modules/@sindresorhus/is": { @@ -326,6 +358,9 @@ "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", "engines": { "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, "node_modules/@szmarczak/http-timer": { @@ -393,7 +428,10 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } }, "node_modules/agent-base": { "version": "6.0.2", @@ -415,6 +453,11 @@ }, "engines": { "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/agent-base/node_modules/ms": { @@ -446,6 +489,11 @@ }, "engines": { "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/agentkeepalive/node_modules/depd": { @@ -486,6 +534,10 @@ "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, "node_modules/ansi-regex": { @@ -506,6 +558,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/anymatch": { @@ -552,6 +607,9 @@ "dependencies": { "call-bind": "^1.0.2", "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/array-includes": { @@ -644,6 +702,9 @@ }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/available-typed-arrays": { @@ -653,6 +714,9 @@ "dev": true, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/balanced-match": { @@ -762,6 +826,9 @@ }, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/bytes": { @@ -816,6 +883,9 @@ }, "engines": { "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/cacache/node_modules/minipass": { @@ -862,6 +932,9 @@ "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/callsites": { @@ -879,6 +952,9 @@ "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/chardet": { @@ -901,6 +977,9 @@ }, "engines": { "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" } }, "node_modules/cheerio-select": { @@ -914,6 +993,9 @@ "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" } }, "node_modules/chokidar": { @@ -921,6 +1003,12 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -1091,6 +1179,9 @@ "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" } }, "node_modules/css-what": { @@ -1099,6 +1190,9 @@ "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", "engines": { "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" } }, "node_modules/csv-stringify": { @@ -1132,6 +1226,9 @@ }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/decompress-response/node_modules/mimic-response": { @@ -1140,6 +1237,9 @@ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/deep-is": { @@ -1161,6 +1261,9 @@ }, "engines": { "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/default-browser-id": { @@ -1174,6 +1277,9 @@ }, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/defer-to-connect": { @@ -1191,6 +1297,9 @@ "dev": true, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/define-properties": { @@ -1204,6 +1313,9 @@ }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/delegates": { @@ -1256,12 +1368,21 @@ "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, "node_modules/domelementtype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] }, "node_modules/domhandler": { "version": "5.0.3", @@ -1272,6 +1393,9 @@ }, "engines": { "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" } }, "node_modules/domutils": { @@ -1282,6 +1406,9 @@ "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" } }, "node_modules/dotenv": { @@ -1329,6 +1456,9 @@ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "engines": { "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/env-paths": { @@ -1394,6 +1524,9 @@ }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/es-set-tostringtag": { @@ -1411,12 +1544,12 @@ } }, "node_modules/es-shim-unscopables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", "dev": true, "dependencies": { - "hasown": "^2.0.0" + "has": "^1.0.3" } }, "node_modules/es-to-primitive": { @@ -1431,6 +1564,9 @@ }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/es6-promisify": { @@ -1453,6 +1589,9 @@ "dev": true, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint": { @@ -1504,6 +1643,9 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-config-airbnb-base": { @@ -1519,6 +1661,10 @@ }, "engines": { "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" } }, "node_modules/eslint-config-airbnb-base/node_modules/semver": { @@ -1537,6 +1683,9 @@ "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" } }, "node_modules/eslint-import-resolver-node": { @@ -1578,6 +1727,12 @@ }, "engines": { "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" } }, "node_modules/eslint-plugin-import": { @@ -1647,6 +1802,9 @@ }, "engines": { "node": ">=8.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" } }, "node_modules/eslint-plugin-node/node_modules/semver": { @@ -1669,6 +1827,22 @@ }, "engines": { "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } } }, "node_modules/eslint-scope": { @@ -1682,6 +1856,9 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-utils": { @@ -1694,6 +1871,9 @@ }, "engines": { "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" } }, "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { @@ -1712,6 +1892,9 @@ "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/chalk": { @@ -1725,6 +1908,9 @@ }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/eslint/node_modules/debug": { @@ -1737,6 +1923,11 @@ }, "engines": { "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/eslint/node_modules/glob-parent": { @@ -1790,6 +1981,9 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esquery": { @@ -1852,6 +2046,9 @@ }, "engines": { "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, "node_modules/express": { @@ -2018,6 +2215,9 @@ "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/express/node_modules/content-disposition": { @@ -2232,7 +2432,10 @@ "node_modules/express/node_modules/object-inspect": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/express/node_modules/on-finished": { "version": "2.4.1", @@ -2271,6 +2474,9 @@ }, "engines": { "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/express/node_modules/range-parser": { @@ -2298,7 +2504,21 @@ "node_modules/express/node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/express/node_modules/safer-buffer": { "version": "2.1.2", @@ -2360,6 +2580,9 @@ "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/express/node_modules/statuses": { @@ -2467,6 +2690,16 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" @@ -2510,6 +2743,9 @@ }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/flat-cache": { @@ -2587,19 +2823,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -2621,13 +2844,19 @@ }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/gauge": { "version": "3.0.2", @@ -2657,6 +2886,9 @@ "has": "^1.0.3", "has-proto": "^1.0.1", "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/get-stream": { @@ -2665,6 +2897,9 @@ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/get-symbol-description": { @@ -2678,6 +2913,9 @@ }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/glob": { @@ -2693,6 +2931,9 @@ }, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/glob-parent": { @@ -2736,6 +2977,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globalthis": { @@ -2748,6 +2992,9 @@ }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/gopd": { @@ -2757,6 +3004,9 @@ "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/got": { @@ -2778,6 +3028,9 @@ }, "engines": { "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" } }, "node_modules/graceful-fs": { @@ -2826,7 +3079,10 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/has-flag": { "version": "3.0.0", @@ -2844,6 +3100,9 @@ "dev": true, "dependencies": { "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { @@ -2852,6 +3111,9 @@ "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-symbols": { @@ -2860,6 +3122,9 @@ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-tostringtag": { @@ -2872,6 +3137,9 @@ }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-unicode": { @@ -2894,12 +3162,29 @@ "node_modules/html-entities": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", - "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==" + "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] }, "node_modules/htmlparser2": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", @@ -2951,6 +3236,11 @@ }, "engines": { "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/http-proxy-agent/node_modules/ms": { @@ -2992,6 +3282,11 @@ }, "engines": { "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/https-proxy-agent/node_modules/ms": { @@ -3054,6 +3349,9 @@ }, "engines": { "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/imurmurhash": { @@ -3123,6 +3421,9 @@ "call-bind": "^1.0.2", "get-intrinsic": "^1.2.0", "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-bigint": { @@ -3132,6 +3433,9 @@ "dev": true, "dependencies": { "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-binary-path": { @@ -3157,6 +3461,9 @@ }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-callable": { @@ -3166,6 +3473,9 @@ "dev": true, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-core-module": { @@ -3190,6 +3500,9 @@ }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-docker": { @@ -3202,6 +3515,9 @@ }, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-extglob": { @@ -3246,6 +3562,9 @@ }, "engines": { "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-lambda": { @@ -3261,6 +3580,9 @@ "dev": true, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-number": { @@ -3282,6 +3604,9 @@ }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-path-inside": { @@ -3304,6 +3629,9 @@ }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-shared-array-buffer": { @@ -3313,6 +3641,9 @@ "dev": true, "dependencies": { "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-stream": { @@ -3322,6 +3653,9 @@ "dev": true, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-string": { @@ -3334,6 +3668,9 @@ }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-symbol": { @@ -3346,6 +3683,9 @@ }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-typed-array": { @@ -3358,6 +3698,9 @@ }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-weakref": { @@ -3367,6 +3710,9 @@ "dev": true, "dependencies": { "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-wsl": { @@ -3391,6 +3737,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/isarray": { @@ -3482,6 +3831,9 @@ }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lodash-es": { @@ -3501,6 +3853,9 @@ "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lru-cache": { @@ -3523,6 +3878,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/make-dir/node_modules/semver": { @@ -3634,6 +3992,9 @@ "dev": true, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/mimic-response": { @@ -3642,6 +4003,9 @@ "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/minimatch": { @@ -3658,7 +4022,10 @@ "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/minipass": { "version": "4.2.1", @@ -3698,13 +4065,15 @@ "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", "optional": true, "dependencies": { - "encoding": "^0.1.12", "minipass": "^3.1.0", "minipass-sized": "^1.0.3", "minizlib": "^2.0.0" }, "engines": { "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" } }, "node_modules/minipass-fetch/node_modules/minipass": { @@ -3860,6 +4229,16 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], "engines": { "node": ">=10.5.0" } @@ -3875,6 +4254,10 @@ }, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, "node_modules/node-gyp": { @@ -3948,6 +4331,9 @@ }, "engines": { "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/node-gyp/node_modules/nopt": { @@ -4017,6 +4403,10 @@ }, "engines": { "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" } }, "node_modules/nopt": { @@ -4029,6 +4419,9 @@ }, "bin": { "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" } }, "node_modules/normalize-path": { @@ -4046,6 +4439,9 @@ "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", "engines": { "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/npm-run-path": { @@ -4058,6 +4454,9 @@ }, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/npm-run-path/node_modules/path-key": { @@ -4067,6 +4466,9 @@ "dev": true, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/npmlog": { @@ -4086,6 +4488,9 @@ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dependencies": { "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" } }, "node_modules/object-assign": { @@ -4099,7 +4504,10 @@ "node_modules/object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/object-keys": { "version": "1.1.1", @@ -4123,6 +4531,9 @@ }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/object.entries": { @@ -4222,6 +4633,9 @@ }, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/open": { @@ -4237,6 +4651,9 @@ }, "engines": { "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/open-graph-scraper": { @@ -4300,6 +4717,9 @@ }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-locate": { @@ -4312,6 +4732,9 @@ }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-map": { @@ -4324,6 +4747,9 @@ }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/parent-module": { @@ -4344,6 +4770,9 @@ "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", "dependencies": { "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, "node_modules/parse5-htmlparser2-tree-adapter": { @@ -4353,6 +4782,9 @@ "dependencies": { "domhandler": "^5.0.2", "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, "node_modules/parseurl": { @@ -4408,6 +4840,9 @@ "dev": true, "engines": { "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/prelude-ls": { @@ -4429,6 +4864,9 @@ }, "engines": { "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, "node_modules/prettier-linter-helpers": { @@ -4486,13 +4924,30 @@ }, "engines": { "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/quick-lru": { "version": "5.1.1", @@ -4500,6 +4955,9 @@ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/random-bytes": { @@ -4607,6 +5065,9 @@ }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/regexpp": { @@ -4616,6 +5077,9 @@ "dev": true, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" } }, "node_modules/resolve": { @@ -4630,6 +5094,9 @@ }, "bin": { "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/resolve-alpn": { @@ -4655,6 +5122,9 @@ }, "engines": { "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/retry": { @@ -4690,6 +5160,9 @@ }, "bin": { "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/rimraf/node_modules/glob": { @@ -4706,6 +5179,9 @@ }, "engines": { "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/run-applescript": { @@ -4718,6 +5194,9 @@ }, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/run-applescript/node_modules/execa": { @@ -4738,6 +5217,9 @@ }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, "node_modules/run-applescript/node_modules/human-signals": { @@ -4756,6 +5238,9 @@ "dev": true, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/run-applescript/node_modules/mimic-fn": { @@ -4789,6 +5274,9 @@ }, "engines": { "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/run-applescript/node_modules/strip-final-newline": { @@ -4805,6 +5293,20 @@ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { "queue-microtask": "^1.2.2" } @@ -4822,12 +5324,29 @@ }, "engines": { "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/safe-regex-test": { "version": "1.0.0", @@ -4838,6 +5357,9 @@ "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/safer-buffer": { @@ -4893,6 +5415,9 @@ "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/signal-exit": { @@ -4969,6 +5494,11 @@ }, "engines": { "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/socks-proxy-agent/node_modules/ms": { @@ -4994,6 +5524,7 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.6.tgz", "integrity": "sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw==", + "hasInstallScript": true, "dependencies": { "@mapbox/node-pre-gyp": "^1.0.0", "node-addon-api": "^4.2.0", @@ -5001,6 +5532,14 @@ }, "optionalDependencies": { "node-gyp": "8.x" + }, + "peerDependencies": { + "node-gyp": "8.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } } }, "node_modules/ssri": { @@ -5113,6 +5652,9 @@ }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimend": { @@ -5124,6 +5666,9 @@ "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { @@ -5135,6 +5680,9 @@ "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/strip-ansi": { @@ -5164,6 +5712,9 @@ "dev": true, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/strip-json-comments": { @@ -5173,6 +5724,9 @@ "dev": true, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/supports-color": { @@ -5194,6 +5748,9 @@ "dev": true, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/synckit": { @@ -5207,6 +5764,9 @@ }, "engines": { "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" } }, "node_modules/tar": { @@ -5243,6 +5803,9 @@ "dev": true, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/to-regex-range": { @@ -5319,6 +5882,9 @@ "dev": true, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/type-is": { @@ -5360,6 +5926,9 @@ }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/typed-array-byte-offset": { @@ -5376,6 +5945,9 @@ }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/typed-array-length": { @@ -5387,6 +5959,9 @@ "call-bind": "^1.0.2", "for-each": "^0.3.3", "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/uglify-js": { @@ -5422,6 +5997,9 @@ "has-bigints": "^1.0.2", "has-symbols": "^1.0.3", "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/undefsafe": { @@ -5535,6 +6113,9 @@ "is-number-object": "^1.0.4", "is-string": "^1.0.5", "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/which-typed-array": { @@ -5551,6 +6132,9 @@ }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/wide-align": { @@ -5583,6 +6167,9 @@ "dev": true, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } } }, @@ -5865,7 +6452,8 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "agent-base": { "version": "6.0.2", @@ -6689,12 +7277,12 @@ } }, "es-shim-unscopables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", "dev": true, "requires": { - "hasown": "^2.0.0" + "has": "^1.0.3" } }, "es-to-primitive": { @@ -6844,7 +7432,8 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", - "dev": true + "dev": true, + "requires": {} }, "eslint-import-resolver-node": { "version": "0.3.9", @@ -7632,13 +8221,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true - }, "function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",