Compare commits
10 Commits
47e5919893
...
94c10d07e1
Author | SHA1 | Date | |
---|---|---|---|
|
94c10d07e1 | ||
|
e8a223f8f9 | ||
|
abd7873ec7 | ||
|
cf674f7cc6 | ||
|
09ffd5588c | ||
|
54991cd386 | ||
|
bd85718107 | ||
|
4dd1531a9c | ||
|
b56b694270 | ||
|
59da569c96 |
3
.dokku/README.md
Normal file
3
.dokku/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
https://dokku.com/docs/advanced-usage/deployment-tasks/?h=monorepo#changing-the-appjson-location
|
||||||
|
|
||||||
|
https://dokku.com/docs/deployment/builders/dockerfiles/
|
8
.dokku/next.app.json
Normal file
8
.dokku/next.app.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"dokku": {
|
||||||
|
"predeploy": "echo hello-world-predeploy",
|
||||||
|
"postdeploy": "echo hello-world-postdeploy"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
ARCHITECHTURE.md
Normal file
10
ARCHITECHTURE.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
git monorepo.
|
||||||
|
|
||||||
|
pnpm required for workspaces.
|
||||||
|
|
||||||
|
Yarn required for packages/strapi
|
||||||
|
|
||||||
|
Development uses docker compose with dotenv.
|
||||||
|
|
||||||
|
Production uses dokku.
|
||||||
|
|
19
Dockerfile
19
Dockerfile
@ -1,19 +0,0 @@
|
|||||||
FROM node:20-slim AS base
|
|
||||||
ENV NEXT_TELEMETRY_DISABLED 1
|
|
||||||
RUN corepack enable
|
|
||||||
|
|
||||||
FROM base AS build
|
|
||||||
WORKDIR /usr/src/fp-monorepo
|
|
||||||
RUN mkdir /usr/src/next
|
|
||||||
COPY ./pnpm-lock.yaml ./
|
|
||||||
COPY ./pnpm-workspace.yaml ./
|
|
||||||
COPY ./packages/next/package.json ./packages/next/
|
|
||||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.pnpm-store pnpm install
|
|
||||||
COPY . .
|
|
||||||
RUN pnpm deploy --filter=fp-next /usr/src/next
|
|
||||||
|
|
||||||
FROM base AS dev
|
|
||||||
WORKDIR /app
|
|
||||||
COPY --from=build /usr/src/next /app
|
|
||||||
CMD ["pnpm", "run", "dev"]
|
|
||||||
|
|
103
compose.prod.yml
103
compose.prod.yml
@ -1,103 +0,0 @@
|
|||||||
version: '3.4'
|
|
||||||
|
|
||||||
|
|
||||||
services:
|
|
||||||
|
|
||||||
link2cid:
|
|
||||||
container_name: fp-link2cid
|
|
||||||
image: insanity54/link2cid:latest
|
|
||||||
ports:
|
|
||||||
- "3939:3939"
|
|
||||||
environment:
|
|
||||||
API_KEY: ${LINK2CID_API_KEY}
|
|
||||||
IPFS_URL: "http://ipfs0:5001"
|
|
||||||
|
|
||||||
ipfs0:
|
|
||||||
container_name: fp-ipfs0
|
|
||||||
image: ipfs/kubo:release
|
|
||||||
ports:
|
|
||||||
- "5001:5001"
|
|
||||||
volumes:
|
|
||||||
- ./packages/ipfs0:/data/ipfs
|
|
||||||
|
|
||||||
cluster0:
|
|
||||||
container_name: fp-cluster0
|
|
||||||
image: ipfs/ipfs-cluster:latest
|
|
||||||
depends_on:
|
|
||||||
- ipfs0
|
|
||||||
environment:
|
|
||||||
CLUSTER_PEERNAME: cluster0
|
|
||||||
CLUSTER_SECRET: ${CLUSTER_SECRET} # From shell variable if set
|
|
||||||
CLUSTER_IPFSHTTP_NODEMULTIADDRESS: /dns4/ipfs0/tcp/5001
|
|
||||||
CLUSTER_CRDT_TRUSTEDPEERS: '*' # Trust all peers in Cluster
|
|
||||||
CLUSTER_RESTAPI_HTTPLISTENMULTIADDRESS: /ip4/0.0.0.0/tcp/9094 # Expose API
|
|
||||||
CLUSTER_RESTAPI_BASICAUTHCREDENTIALS: ${CLUSTER_RESTAPI_BASICAUTHCREDENTIALS}
|
|
||||||
CLUSTER_MONITORPINGINTERVAL: 2s # Speed up peer discovery
|
|
||||||
ports:
|
|
||||||
- "127.0.0.1:9094:9094"
|
|
||||||
volumes:
|
|
||||||
- ./packages/cluster0:/data/ipfs-cluster
|
|
||||||
|
|
||||||
strapi:
|
|
||||||
container_name: fp-strapi
|
|
||||||
image: elestio/strapi-development
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
environment:
|
|
||||||
ADMIN_PASSWORD: ${STRAPI_ADMIN_PASSWORD}
|
|
||||||
ADMIN_EMAIL: ${STRAPI_ADMIN_EMAIL}
|
|
||||||
BASE_URL: ${STRAPI_BASE_URL}
|
|
||||||
SMTP_HOST: 172.17.0.1
|
|
||||||
SMTP_PORT: 25
|
|
||||||
SMTP_AUTH_STRATEGY: NONE
|
|
||||||
SMTP_FROM_EMAIL: sender@email.com
|
|
||||||
DATABASE_CLIENT: postgres
|
|
||||||
DATABASE_PORT: ${DATABASE_PORT}
|
|
||||||
DATABASE_NAME: ${DATABASE_NAME}
|
|
||||||
DATABASE_USERNAME: ${DATABASE_USERNAME}
|
|
||||||
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
|
|
||||||
JWT_SECRET: ${STRAPI_JWT_SECRET}
|
|
||||||
ADMIN_JWT_SECRET: ${STRAPI_ADMIN_JWT_SECRET}
|
|
||||||
APP_KEYS: ${STRAPI_APP_KEYS}
|
|
||||||
NODE_ENV: development
|
|
||||||
DATABASE_HOST: db
|
|
||||||
API_TOKEN_SALT: ${STRAPI_API_TOKEN_SALT}
|
|
||||||
TRANSFER_TOKEN_SALT: ${STRAPI_TRANSFER_TOKEN_SALT}
|
|
||||||
ports:
|
|
||||||
- "1337:1337"
|
|
||||||
volumes:
|
|
||||||
- ./packages/strapi/config:/opt/app/config
|
|
||||||
- ./packages/strapi/src:/opt/app/src
|
|
||||||
# - ./packages/strapi/package.json:/opt/package.json
|
|
||||||
# - ./packages/strapi/yarn.lock:/opt/yarn.lock
|
|
||||||
- ./packages/strapi/.env:/opt/app/.env
|
|
||||||
- ./packages/strapi/public/uploads:/opt/app/public/uploads
|
|
||||||
# - ./packages/strapi/entrypoint.sh:/opt/app/entrypoint.sh
|
|
||||||
|
|
||||||
next:
|
|
||||||
container_name: fp-next
|
|
||||||
build:
|
|
||||||
context: ./packages/next
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
environment:
|
|
||||||
REVALIDATION_TOKEN: ${NEXT_REVALIDATION_TOKEN}
|
|
||||||
NODE_ENV: production
|
|
||||||
ports:
|
|
||||||
- "3000:3000"
|
|
||||||
volumes:
|
|
||||||
- ./packages/next/
|
|
||||||
|
|
||||||
|
|
||||||
db:
|
|
||||||
container_name: fp-db
|
|
||||||
image: postgres:latest
|
|
||||||
restart: always
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: ${DATABASE_NAME}
|
|
||||||
POSTGRES_USER: ${DATABASE_USERNAME}
|
|
||||||
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
|
|
||||||
PGDATA: /var/lib/postgresql/data
|
|
||||||
volumes:
|
|
||||||
- ./packages/db/pgdata:/var/lib/postgresql/data
|
|
||||||
ports:
|
|
||||||
- "5433:5432"
|
|
@ -14,12 +14,18 @@ services:
|
|||||||
link2cid:
|
link2cid:
|
||||||
container_name: fp-link2cid
|
container_name: fp-link2cid
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
image: insanity54/link2cid:latest
|
build:
|
||||||
|
context: ./packages/link2cid
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
target: dev
|
||||||
ports:
|
ports:
|
||||||
- "3939:3939"
|
- "3939:3939"
|
||||||
environment:
|
environment:
|
||||||
API_KEY: ${LINK2CID_API_KEY}
|
API_KEY: ${LINK2CID_API_KEY}
|
||||||
IPFS_URL: "http://ipfs0:5001"
|
IPFS_URL: "http://ipfs0:5001"
|
||||||
|
PORT: 3939
|
||||||
|
volumes:
|
||||||
|
- ./packages/link2cid/index.js:/app/index.js
|
||||||
|
|
||||||
ipfs0:
|
ipfs0:
|
||||||
container_name: fp-ipfs0
|
container_name: fp-ipfs0
|
||||||
|
32
next.Dockerfile
Normal file
32
next.Dockerfile
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
FROM node:20-slim AS base
|
||||||
|
|
||||||
|
# Install dependencies only when needed
|
||||||
|
FROM base AS deps
|
||||||
|
RUN corepack enable
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
FROM deps AS build
|
||||||
|
ARG NEXT_PUBLIC_SITE_URL=foo
|
||||||
|
ARG NEXT_PUBLIC_STRAPI_URL=foo
|
||||||
|
ARG NEXT_PUBLIC_UPPY_COMPANION_URL=foo
|
||||||
|
ENV NEXT_PUBLIC_SITE_URL ${NEXT_PUBLIC_SITE_URL}
|
||||||
|
ENV NEXT_PUBLIC_STRAPI_URL ${NEXT_PUBLIC_STRAPI_URL}
|
||||||
|
ENV NEXT_PUBLIC_UPPY_COMPANION_URL ${NEXT_PUBLIC_UPPY_COMPANION_URL}
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
COPY . .
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
|
RUN --mount=type=cache,id=pnpm-store,target=/root/.pnpm-store pnpm install
|
||||||
|
RUN pnpm run -r build
|
||||||
|
RUN pnpm deploy --filter=fp-next /app
|
||||||
|
|
||||||
|
|
||||||
|
FROM deps AS dev
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /usr/src/app /app
|
||||||
|
CMD ["pnpm", "run", "dev"]
|
||||||
|
|
||||||
|
FROM deps AS next
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /usr/src/app /app
|
||||||
|
CMD ["pnpm", "start"]
|
19
package.json
Normal file
19
package.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "futureporn",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"kompose": "kompose convert --file compose.yml -c --out ./charts",
|
||||||
|
"deploy:bot": "echo @todo",
|
||||||
|
"deploy:next": "git push origin:next main",
|
||||||
|
"deploy:link2cid": "echo @todo",
|
||||||
|
"deploy:strapi": "echo @todo",
|
||||||
|
"deploy:uppy": "echo @todo",
|
||||||
|
"deploy": "echo @todo"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "@cj_clippy",
|
||||||
|
"license": "CC0-1.0"
|
||||||
|
}
|
1
packages/bot/.nvmrc
Normal file
1
packages/bot/.nvmrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
lts/iron
|
19
packages/bot/Dockerfile
Normal file
19
packages/bot/Dockerfile
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
FROM node:20-alpine AS base
|
||||||
|
ENV PNPM_HOME="/pnpm"
|
||||||
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
|
WORKDIR /app
|
||||||
|
RUN corepack enable
|
||||||
|
|
||||||
|
FROM base AS build
|
||||||
|
COPY ./packages/bot/package.json ./
|
||||||
|
COPY ./packages/bot/src ./
|
||||||
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install
|
||||||
|
|
||||||
|
FROM build AS dev
|
||||||
|
ENTRYPOINT ["pnpm"]
|
||||||
|
CMD ["run", "dev"]
|
||||||
|
|
||||||
|
FROM build AS run
|
||||||
|
ENTRYPOINT ["pnpm"]
|
||||||
|
CMD ["start"]
|
||||||
|
|
106
packages/bot/components.js
Normal file
106
packages/bot/components.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
const { ButtonStyles, Client, ComponentTypes, ChannelTypes } = require("oceanic.js");
|
||||||
|
|
||||||
|
const client = new Client({
|
||||||
|
auth: `Bot ${process.env.DISCORD_TOKEN}`,
|
||||||
|
gateway: {
|
||||||
|
intents: ["GUILD_MESSAGES"] // If the message does not start with a mention to or somehow relate to your client, you will need the MESSAGE_CONTENT intent as well
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("ready", () => console.log("Ready as", client.user.tag));
|
||||||
|
|
||||||
|
client.on("messageCreate", async (msg) => {
|
||||||
|
if(msg.content.includes("!component")) {
|
||||||
|
await client.rest.channels.createMessage(msg.channelID, {
|
||||||
|
content: `Here's some buttons for you, ${msg.author.mention}.`,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
// The top level component must always be an action row.
|
||||||
|
// Full list of types: https://docs.oceanic.ws/latest/enums/Constants.ComponentTypes.html
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.MessageActionRow.html
|
||||||
|
type: ComponentTypes.ACTION_ROW,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.TextButton.html
|
||||||
|
type: ComponentTypes.BUTTON,
|
||||||
|
style: ButtonStyles.PRIMARY, // The style of button - full list: https://docs.oceanic.ws/latest/enums/Constants.ButtonStyles.html
|
||||||
|
customID: "some-string-you-will-see-later",
|
||||||
|
label: "Click!",
|
||||||
|
disabled: false, // If the button is disabled, false by default.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: ComponentTypes.BUTTON,
|
||||||
|
style: ButtonStyles.PRIMARY,
|
||||||
|
customID: "some-other-string",
|
||||||
|
label: "This Is Disabled",
|
||||||
|
disabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.URLButton.html
|
||||||
|
type: ComponentTypes.BUTTON,
|
||||||
|
style: ButtonStyles.LINK,
|
||||||
|
label: "Open Link",
|
||||||
|
url: "https://docs.oceanic.ws"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// The top level component must always be an action row.
|
||||||
|
// Full list of types: https://docs.oceanic.ws/latest/enums/Constants.ComponentTypes.html
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.MessageActionRow.html
|
||||||
|
type: ComponentTypes.ACTION_ROW,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.SelectMenu.html
|
||||||
|
type: ComponentTypes.STRING_SELECT,
|
||||||
|
customID: "string-select",
|
||||||
|
disabled: false,
|
||||||
|
maxValues: 1, // The maximum number of values that can be selected (default 1)
|
||||||
|
minValues: 1, // The minimum number of values that can be selected (default 1)
|
||||||
|
options: [
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.SelectOption.html
|
||||||
|
{
|
||||||
|
default: true, // If this option is selected by default
|
||||||
|
description: "The description of the option", // Optional description
|
||||||
|
label: "Option One",
|
||||||
|
value: "value-1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Option Two",
|
||||||
|
value: "option-2"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
placeholder: "Some Placeholder Text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// The top level component must always be an action row.
|
||||||
|
// Full list of types: https://docs.oceanic.ws/latest/enums/Constants.ComponentTypes.html
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.MessageActionRow.html
|
||||||
|
type: ComponentTypes.ACTION_ROW,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.SelectMenu.html
|
||||||
|
type: ComponentTypes.CHANNEL_SELECT,
|
||||||
|
channelTypes: [ChannelTypes.GUILD_TEXT, ChannelTypes.GUILD_VOICE], // The types of channels that can be selected
|
||||||
|
customID: "channel-select",
|
||||||
|
disabled: false,
|
||||||
|
maxValues: 1, // The maximum number of values that can be selected (default 1)
|
||||||
|
minValues: 1, // The minimum number of values that can be selected (default 1)
|
||||||
|
placeholder: "Some Placeholder Text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// An error handler
|
||||||
|
client.on("error", (error) => {
|
||||||
|
console.error("Something went wrong:", error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connect to Discord
|
||||||
|
client.connect();
|
94
packages/bot/embeds.js
Normal file
94
packages/bot/embeds.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
const { Client } = require("oceanic.js");
|
||||||
|
const { readFileSync } = require("fs");
|
||||||
|
|
||||||
|
const client = new Client({
|
||||||
|
auth: `Bot ${process.env.DISCORD_TOKEN}`,
|
||||||
|
gateway: {
|
||||||
|
intents: ["GUILD_MESSAGES"] // If the message does not start with a mention to or somehow relate to your client, you will need the MESSAGE_CONTENT intent as well
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("ready", () => console.log("Ready as", client.user.tag));
|
||||||
|
|
||||||
|
client.on("messageCreate", async (msg) => {
|
||||||
|
if(msg.content.includes("!embed")) {
|
||||||
|
console.log(`'!embeds' was seen in chat!`)
|
||||||
|
console.log(msg)
|
||||||
|
await client.rest.channels.createMessage(msg.channelID, {
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.EmbedOptions.html
|
||||||
|
// Up to 10 in one message
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.EmbedAuthorOptions.html
|
||||||
|
author: {
|
||||||
|
name: "Author Name",
|
||||||
|
// An image url, or attachment://filename.ext
|
||||||
|
iconURL: "https://i.furry.cool/DonPride.png", // Optional
|
||||||
|
url: "https://docs.oceanic.ws" // Optional
|
||||||
|
},
|
||||||
|
// Array of https://docs.oceanic.ws/latest/interfaces/Types_Channels.EmbedField.html
|
||||||
|
// Up to 25 in one message
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "Field One",
|
||||||
|
value: "Field One Value",
|
||||||
|
inline: true // If this field should be displayed inline (default: false)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Field Two",
|
||||||
|
value: "Field Two Value",
|
||||||
|
inline: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.EmbedFooterOptions.html
|
||||||
|
footer: {
|
||||||
|
text: "Footer Text",
|
||||||
|
// An image url, or attachment://filename.ext
|
||||||
|
iconURL: "https://i.furry.cool/DonPride.png" // Optional
|
||||||
|
},
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.EmbedImageOptions.html
|
||||||
|
image: {
|
||||||
|
// An image url, or attachment://filename.ext
|
||||||
|
url: "https://i.furry.cool/DonPride.png"
|
||||||
|
},
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.EmbedThumbnailOptions.html
|
||||||
|
thumbnail: {
|
||||||
|
// An image url, or attachment://filename.ext
|
||||||
|
url: "https://i.furry.cool/DonPride.png"
|
||||||
|
},
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.EmbedOptions.html
|
||||||
|
color: 0xFFA500, // Base-10 color (0x prefix can be used for hex codes)
|
||||||
|
description: "My Cool Embed",
|
||||||
|
timestamp: new Date().toISOString(), // The current time - ISO 8601 format
|
||||||
|
title: "My Amazing Embed",
|
||||||
|
url: "https://docs.oceanic.ws"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
} else if(msg.content.includes("!file")) {
|
||||||
|
await client.rest.channels.createMessage(msg.channelID, {
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
image: {
|
||||||
|
// This can also be used for author & footer images
|
||||||
|
url: "attachment://image.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "image.png",
|
||||||
|
contents: readFileSync(`${__dirname}/image.png`)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// An error handler
|
||||||
|
client.on("error", (error) => {
|
||||||
|
console.error("Something went wrong:", error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connect to Discord
|
||||||
|
client.connect();
|
261
packages/bot/futurebutt.js
Normal file
261
packages/bot/futurebutt.js
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
const { ButtonStyles, Client, ComponentTypes, ChannelTypes } = require("oceanic.js");
|
||||||
|
|
||||||
|
const client = new Client({
|
||||||
|
auth: `Bot ${process.env.DISCORD_TOKEN}`,
|
||||||
|
gateway: {
|
||||||
|
intents: ["GUILD_MESSAGES"] // If the message does not start with a mention to or somehow relate to your client, you will need the MESSAGE_CONTENT intent as well
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("ready", () => console.log("Ready as", client.user.tag));
|
||||||
|
|
||||||
|
client.on("messageCreate", async (msg) => {
|
||||||
|
console.log(msg.content)
|
||||||
|
if(msg.content.includes("!test")) {
|
||||||
|
await client.rest.channels.createMessage(msg.channelID, {
|
||||||
|
content: `HGERE IZ BUTTN'z 5 u, ${msg.author.mention}.`,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
// The top level component must always be an action row.
|
||||||
|
// Full list of types: https://docs.oceanic.ws/latest/enums/Constants.ComponentTypes.html
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.MessageActionRow.html
|
||||||
|
type: ComponentTypes.ACTION_ROW,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.TextButton.html
|
||||||
|
type: ComponentTypes.BUTTON,
|
||||||
|
style: ButtonStyles.PRIMARY, // The style of button - full list: https://docs.oceanic.ws/latest/enums/Constants.ButtonStyles.html
|
||||||
|
customID: "some-string-you-will-see-later",
|
||||||
|
label: "Click!",
|
||||||
|
disabled: false, // If the button is disabled, false by default.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: ComponentTypes.BUTTON,
|
||||||
|
style: ButtonStyles.PRIMARY,
|
||||||
|
customID: "some-other-string",
|
||||||
|
label: "This Is Disabled",
|
||||||
|
disabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.URLButton.html
|
||||||
|
type: ComponentTypes.BUTTON,
|
||||||
|
style: ButtonStyles.LINK,
|
||||||
|
label: "Open Link",
|
||||||
|
url: "https://docs.oceanic.ws"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// The top level component must always be an action row.
|
||||||
|
// Full list of types: https://docs.oceanic.ws/latest/enums/Constants.ComponentTypes.html
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.MessageActionRow.html
|
||||||
|
type: ComponentTypes.ACTION_ROW,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.SelectMenu.html
|
||||||
|
type: ComponentTypes.STRING_SELECT,
|
||||||
|
customID: "string-select",
|
||||||
|
disabled: false,
|
||||||
|
maxValues: 1, // The maximum number of values that can be selected (default 1)
|
||||||
|
minValues: 1, // The minimum number of values that can be selected (default 1)
|
||||||
|
options: [
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.SelectOption.html
|
||||||
|
{
|
||||||
|
default: true, // If this option is selected by default
|
||||||
|
description: "The description of the option", // Optional description
|
||||||
|
label: "Option One",
|
||||||
|
value: "value-1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Option Two",
|
||||||
|
value: "option-2"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
placeholder: "Some Placeholder Text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// The top level component must always be an action row.
|
||||||
|
// Full list of types: https://docs.oceanic.ws/latest/enums/Constants.ComponentTypes.html
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.MessageActionRow.html
|
||||||
|
type: ComponentTypes.ACTION_ROW,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.SelectMenu.html
|
||||||
|
type: ComponentTypes.CHANNEL_SELECT,
|
||||||
|
channelTypes: [ChannelTypes.GUILD_TEXT, ChannelTypes.GUILD_VOICE], // The types of channels that can be selected
|
||||||
|
customID: "channel-select",
|
||||||
|
disabled: false,
|
||||||
|
maxValues: 1, // The maximum number of values that can be selected (default 1)
|
||||||
|
minValues: 1, // The minimum number of values that can be selected (default 1)
|
||||||
|
placeholder: "Some Placeholder Text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
client.on("interactionCreate", async(interaction) => {
|
||||||
|
console.log(`interaction!@`)
|
||||||
|
console.log(interaction)
|
||||||
|
switch(interaction.type) {
|
||||||
|
// https://docs.oceanic.ws/latest/classes/CommandInteraction.CommandInteraction.html
|
||||||
|
case InteractionTypes.APPLICATION_COMMAND: {
|
||||||
|
// defer interactions as soon as possible, you have three seconds to send any initial response
|
||||||
|
// if you wait too long, the interaction may be invalidated
|
||||||
|
await interaction.defer();
|
||||||
|
// If you want the response to be ephemeral, you can provide the flag to the defer function, like so:
|
||||||
|
// await interaction.defer(MessageFlags.EPHEMERAL);
|
||||||
|
|
||||||
|
// data = https://docs.oceanic.ws/latest/interfaces/Types_Interactions.ApplicationCommandInteractionData.html
|
||||||
|
switch(interaction.data.type) {
|
||||||
|
// Chat Input commands are what you use in the chat, i.e. slash commands
|
||||||
|
case ApplicationCommandTypes.CHAT_INPUT: {
|
||||||
|
if(interaction.data.name === "greet") {
|
||||||
|
// assume we have two options, user (called user) then string (called greeting) - first is required, second is not
|
||||||
|
|
||||||
|
// Get an option named `user` with the type USER - https://docs.oceanic.ws/dev/classes/InteractionOptionsWrapper.InteractionOptionsWrapper.html#getUser
|
||||||
|
// Setting the second parameter to true will throw an error if the option is not present
|
||||||
|
const user = interaction.data.options.getUser("user", true);
|
||||||
|
const greeting = interaction.data.options.getString("greeting", false) || "Hello, ";
|
||||||
|
|
||||||
|
// since we've already deferred the interaction, we cannot use createMessage (this is an initial response)
|
||||||
|
// we can only have one initial response, so we use createFollowup
|
||||||
|
await interaction.createFollowup({
|
||||||
|
content: `${greeting} ${user.mention}!`,
|
||||||
|
allowedMentions: {
|
||||||
|
users: [user.id]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chat Input application command interactions also have a set of resolved data, which is structured as so:
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Interactions.ApplicationCommandInteractionResolvedData.html
|
||||||
|
// the options wrapper pulls values out of resolved automatically, if you use the right method
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// User application commands are shown in the context menu when right-clicking on users
|
||||||
|
// `data` will have a target (and targetID) property with the user that the command was executed on
|
||||||
|
// These don't have options
|
||||||
|
case ApplicationCommandTypes.USER: {
|
||||||
|
if(interaction.data.name === "ping") {
|
||||||
|
await interaction.createFollowup({
|
||||||
|
content: `Pong! ${interaction.data.target.mention}`,
|
||||||
|
allowedMentions: {
|
||||||
|
users: [interaction.data.target.id]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message application commands are shown in the context menu when right-clicking on messages
|
||||||
|
// `data` will have a target (and targetID) property with the message that the command was executed on
|
||||||
|
// Same as user commands, these don't have options
|
||||||
|
case ApplicationCommandTypes.MESSAGE: {
|
||||||
|
if(interaction.data.name === "author") {
|
||||||
|
await interaction.createFollowup({
|
||||||
|
content: `${interaction.data.target.author.mention} is the author of that message!`,
|
||||||
|
allowedMentions: {
|
||||||
|
users: [interaction.data.target.author.id]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.oceanic.ws/latest/classes/ComponentInteraction.ComponentInteraction.html
|
||||||
|
case InteractionTypes.MESSAGE_COMPONENT: {
|
||||||
|
// same spiel as above
|
||||||
|
await interaction.defer();
|
||||||
|
// when you create a message with components, this will correspond with what you provided as the customID there
|
||||||
|
if(interaction.data.componentType === ComponentTypes.BUTTON) {
|
||||||
|
if(interaction.data.customID === "edit-message") {
|
||||||
|
// Edits the original message. This has an initial response variant: editParent
|
||||||
|
await interaction.editOriginal({
|
||||||
|
content: `This message was edited by ${interaction.user.mention}!`,
|
||||||
|
allowedMentions: {
|
||||||
|
users: [interaction.user.id]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if(interaction.data.customID === "my-amazing-button") {
|
||||||
|
await interaction.createFollowup({
|
||||||
|
content: "You clicked an amazing button!"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if(interaction.data.componentType === ComponentTypes.SELECT_MENU) {
|
||||||
|
// The `values` property under data contains all the selected values
|
||||||
|
await interaction.createFollowup({
|
||||||
|
content: `You selected: **${interaction.data.values.join("**, **")}**`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.oceanic.ws/latest/classes/AutocompleteInteraction.AutocompleteInteraction.html
|
||||||
|
case InteractionTypes.APPLICATION_COMMAND_AUTOCOMPLETE: {
|
||||||
|
// Autocomplete Interactions cannot be deferred
|
||||||
|
switch(interaction.data.name) {
|
||||||
|
case "test-autocomplete": {
|
||||||
|
// Autocomplete interactions data has a partial `options` property, which is the tree of options that are currently being filled in
|
||||||
|
// along with one at the end, which will have focused
|
||||||
|
// Setting the first parameter to true will throw an error if no focused option is present
|
||||||
|
const option = interaction.data.options.getFocused(true);
|
||||||
|
switch(option.name) {
|
||||||
|
case "test-option": {
|
||||||
|
return interaction.result([
|
||||||
|
{
|
||||||
|
name: "Choice 1",
|
||||||
|
nameLocalizations: {
|
||||||
|
"es-ES": "Opción 1"
|
||||||
|
},
|
||||||
|
value: "choice-1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Choice 2",
|
||||||
|
nameLocalizations: {
|
||||||
|
"es-ES": "Opción 2"
|
||||||
|
},
|
||||||
|
value: "choice-2"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.oceanic.ws/latest/classes/ModalSubmitInteraction.ModalSubmitInteraction.html
|
||||||
|
case InteractionTypes.MODAL_SUBMIT: {
|
||||||
|
// this will correspond with the customID you provided when creating the modal
|
||||||
|
switch(interaction.data.customID) {
|
||||||
|
case "test-modal": {
|
||||||
|
// the `components` property under data contains all the components that were submitted
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.ModalActionRow.html
|
||||||
|
console.log(interaction.data.components);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// An error handler
|
||||||
|
client.on("error", (error) => {
|
||||||
|
console.error("Something went wrong:", error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connect to Discord
|
||||||
|
client.connect();
|
171
packages/bot/interactions.js
Normal file
171
packages/bot/interactions.js
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
const { Client, InteractionTypes, MessageFlags, ComponentTypes, ApplicationCommandTypes } = require("oceanic.js");
|
||||||
|
|
||||||
|
const client = new Client({
|
||||||
|
auth: `Bot ${process.env.DISCORD_TOKEN}`,
|
||||||
|
gateway: {
|
||||||
|
intents: 0 // No intents are needed if you are only using interactions
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
client.on("ready", async() => {
|
||||||
|
console.log("Ready as", client.user.tag);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("interactionCreate", async(interaction) => {
|
||||||
|
switch(interaction.type) {
|
||||||
|
// https://docs.oceanic.ws/latest/classes/CommandInteraction.CommandInteraction.html
|
||||||
|
case InteractionTypes.APPLICATION_COMMAND: {
|
||||||
|
// defer interactions as soon as possible, you have three seconds to send any initial response
|
||||||
|
// if you wait too long, the interaction may be invalidated
|
||||||
|
await interaction.defer();
|
||||||
|
// If you want the response to be ephemeral, you can provide the flag to the defer function, like so:
|
||||||
|
// await interaction.defer(MessageFlags.EPHEMERAL);
|
||||||
|
|
||||||
|
// data = https://docs.oceanic.ws/latest/interfaces/Types_Interactions.ApplicationCommandInteractionData.html
|
||||||
|
switch(interaction.data.type) {
|
||||||
|
// Chat Input commands are what you use in the chat, i.e. slash commands
|
||||||
|
case ApplicationCommandTypes.CHAT_INPUT: {
|
||||||
|
if(interaction.data.name === "greet") {
|
||||||
|
// assume we have two options, user (called user) then string (called greeting) - first is required, second is not
|
||||||
|
|
||||||
|
// Get an option named `user` with the type USER - https://docs.oceanic.ws/dev/classes/InteractionOptionsWrapper.InteractionOptionsWrapper.html#getUser
|
||||||
|
// Setting the second parameter to true will throw an error if the option is not present
|
||||||
|
const user = interaction.data.options.getUser("user", true);
|
||||||
|
const greeting = interaction.data.options.getString("greeting", false) || "Hello, ";
|
||||||
|
|
||||||
|
// since we've already deferred the interaction, we cannot use createMessage (this is an initial response)
|
||||||
|
// we can only have one initial response, so we use createFollowup
|
||||||
|
await interaction.createFollowup({
|
||||||
|
content: `${greeting} ${user.mention}!`,
|
||||||
|
allowedMentions: {
|
||||||
|
users: [user.id]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chat Input application command interactions also have a set of resolved data, which is structured as so:
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Interactions.ApplicationCommandInteractionResolvedData.html
|
||||||
|
// the options wrapper pulls values out of resolved automatically, if you use the right method
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// User application commands are shown in the context menu when right-clicking on users
|
||||||
|
// `data` will have a target (and targetID) property with the user that the command was executed on
|
||||||
|
// These don't have options
|
||||||
|
case ApplicationCommandTypes.USER: {
|
||||||
|
if(interaction.data.name === "ping") {
|
||||||
|
await interaction.createFollowup({
|
||||||
|
content: `Pong! ${interaction.data.target.mention}`,
|
||||||
|
allowedMentions: {
|
||||||
|
users: [interaction.data.target.id]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message application commands are shown in the context menu when right-clicking on messages
|
||||||
|
// `data` will have a target (and targetID) property with the message that the command was executed on
|
||||||
|
// Same as user commands, these don't have options
|
||||||
|
case ApplicationCommandTypes.MESSAGE: {
|
||||||
|
if(interaction.data.name === "author") {
|
||||||
|
await interaction.createFollowup({
|
||||||
|
content: `${interaction.data.target.author.mention} is the author of that message!`,
|
||||||
|
allowedMentions: {
|
||||||
|
users: [interaction.data.target.author.id]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.oceanic.ws/latest/classes/ComponentInteraction.ComponentInteraction.html
|
||||||
|
case InteractionTypes.MESSAGE_COMPONENT: {
|
||||||
|
// same spiel as above
|
||||||
|
await interaction.defer();
|
||||||
|
// when you create a message with components, this will correspond with what you provided as the customID there
|
||||||
|
if(interaction.data.componentType === ComponentTypes.BUTTON) {
|
||||||
|
if(interaction.data.customID === "edit-message") {
|
||||||
|
// Edits the original message. This has an initial response variant: editParent
|
||||||
|
await interaction.editOriginal({
|
||||||
|
content: `This message was edited by ${interaction.user.mention}!`,
|
||||||
|
allowedMentions: {
|
||||||
|
users: [interaction.user.id]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if(interaction.data.customID === "my-amazing-button") {
|
||||||
|
await interaction.createFollowup({
|
||||||
|
content: "You clicked an amazing button!"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if(interaction.data.componentType === ComponentTypes.SELECT_MENU) {
|
||||||
|
// The `values` property under data contains all the selected values
|
||||||
|
await interaction.createFollowup({
|
||||||
|
content: `You selected: **${interaction.data.values.join("**, **")}**`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.oceanic.ws/latest/classes/AutocompleteInteraction.AutocompleteInteraction.html
|
||||||
|
case InteractionTypes.APPLICATION_COMMAND_AUTOCOMPLETE: {
|
||||||
|
// Autocomplete Interactions cannot be deferred
|
||||||
|
switch(interaction.data.name) {
|
||||||
|
case "test-autocomplete": {
|
||||||
|
// Autocomplete interactions data has a partial `options` property, which is the tree of options that are currently being filled in
|
||||||
|
// along with one at the end, which will have focused
|
||||||
|
// Setting the first parameter to true will throw an error if no focused option is present
|
||||||
|
const option = interaction.data.options.getFocused(true);
|
||||||
|
switch(option.name) {
|
||||||
|
case "test-option": {
|
||||||
|
return interaction.result([
|
||||||
|
{
|
||||||
|
name: "Choice 1",
|
||||||
|
nameLocalizations: {
|
||||||
|
"es-ES": "Opción 1"
|
||||||
|
},
|
||||||
|
value: "choice-1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Choice 2",
|
||||||
|
nameLocalizations: {
|
||||||
|
"es-ES": "Opción 2"
|
||||||
|
},
|
||||||
|
value: "choice-2"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.oceanic.ws/latest/classes/ModalSubmitInteraction.ModalSubmitInteraction.html
|
||||||
|
case InteractionTypes.MODAL_SUBMIT: {
|
||||||
|
// this will correspond with the customID you provided when creating the modal
|
||||||
|
switch(interaction.data.customID) {
|
||||||
|
case "test-modal": {
|
||||||
|
// the `components` property under data contains all the components that were submitted
|
||||||
|
// https://docs.oceanic.ws/latest/interfaces/Types_Channels.ModalActionRow.html
|
||||||
|
console.log(interaction.data.components);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// An error handler
|
||||||
|
client.on("error", (error) => {
|
||||||
|
console.error("Something went wrong:", error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connect to Discord
|
||||||
|
client.connect();
|
27
packages/bot/package.json
Normal file
27
packages/bot/package.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "fp-bot",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"dev": "node --import=tsx --watch ./src/index.ts"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "CC0-1.0",
|
||||||
|
"engines": {
|
||||||
|
"node": "v20.x.x",
|
||||||
|
"npm": ">=6.x.x"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"tsx": "^4.7.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/express": "^4.17.21",
|
||||||
|
"@types/node": "^20.11.0",
|
||||||
|
"discordeno": "^18.0.1",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"oceanic.js": "^1.9.0"
|
||||||
|
}
|
||||||
|
}
|
28
packages/bot/src/configs.ts
Normal file
28
packages/bot/src/configs.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { getBotIdFromToken, Intents } from 'discordeno';
|
||||||
|
|
||||||
|
|
||||||
|
/** The bot id, derived from the bot token. */
|
||||||
|
export const BOT_ID = getBotIdFromToken(process.env.DISCORD_TOKEN as string);
|
||||||
|
export const EVENT_HANDLER_URL = `http://${process.env.EVENT_HANDLER_HOST}:${process.env.EVENT_HANDLER_PORT}`;
|
||||||
|
export const REST_URL = `http://${process.env.REST_HOST}:${process.env.REST_PORT}`;
|
||||||
|
export const GATEWAY_URL = `http://${process.env.GATEWAY_HOST}:${process.env.GATEWAY_PORT}`;
|
||||||
|
|
||||||
|
// Gateway Proxy Configurations
|
||||||
|
/** The gateway intents you would like to use. */
|
||||||
|
export const INTENTS: Intents =
|
||||||
|
// SETUP-DD-TEMP: Add the intents you want enabled here. Or Delete the intents you don't want in your bot.
|
||||||
|
Intents.DirectMessageReactions |
|
||||||
|
Intents.DirectMessageTyping |
|
||||||
|
Intents.DirectMessages |
|
||||||
|
Intents.GuildBans |
|
||||||
|
Intents.GuildEmojis |
|
||||||
|
Intents.GuildIntegrations |
|
||||||
|
Intents.GuildInvites |
|
||||||
|
Intents.GuildMembers |
|
||||||
|
Intents.GuildMessageReactions |
|
||||||
|
Intents.GuildMessageTyping |
|
||||||
|
Intents.GuildMessages |
|
||||||
|
Intents.GuildPresences |
|
||||||
|
Intents.GuildVoiceStates |
|
||||||
|
Intents.GuildWebhooks |
|
||||||
|
Intents.Guilds;
|
58
packages/bot/src/index.ts
Normal file
58
packages/bot/src/index.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
|
||||||
|
import { BASE_URL, createRestManager } from 'discordeno';
|
||||||
|
import express, { Request, Response } from 'express';
|
||||||
|
// import { setupAnalyticsHooks } from '../analytics.js';
|
||||||
|
import { REST_URL } from './configs.js';
|
||||||
|
|
||||||
|
const DISCORD_TOKEN = process.env.DISCORD_TOKEN as string;
|
||||||
|
const REST_AUTHORIZATION = process.env.REST_AUTHORIZATION as string;
|
||||||
|
const REST_PORT = process.env.REST_PORT as string;
|
||||||
|
|
||||||
|
const rest = createRestManager({
|
||||||
|
token: DISCORD_TOKEN,
|
||||||
|
secretKey: REST_AUTHORIZATION,
|
||||||
|
customUrl: REST_URL,
|
||||||
|
debug: console.log,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add send fetching analytics hook to rest
|
||||||
|
// setupAnalyticsHooks(rest);
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
rest.convertRestError = (errorStack, data) => {
|
||||||
|
if (!data) return { message: errorStack.message };
|
||||||
|
return { ...data, message: errorStack.message };
|
||||||
|
};
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
express.urlencoded({
|
||||||
|
extended: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
app.all('/*', async (req: Request, res: Response) => {
|
||||||
|
if (!REST_AUTHORIZATION || REST_AUTHORIZATION !== req.headers.authorization) {
|
||||||
|
return res.status(401).json({ error: 'Invalid authorization key.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await rest.runMethod(rest, req.method, `${BASE_URL}${req.url}`, req.body);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
res.status(200).json(result);
|
||||||
|
} else {
|
||||||
|
res.status(204).json();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.log(error);
|
||||||
|
res.status(500).json(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(REST_PORT, () => {
|
||||||
|
console.log(`REST listening at ${REST_URL}`);
|
||||||
|
});
|
9
packages/bot/src/rest/README.md
Normal file
9
packages/bot/src/rest/README.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# REST Proxy
|
||||||
|
|
||||||
|
This folder will contain the code for our REST proxy. This is going to become the single source that all of our bot will
|
||||||
|
use to communciate to the Discord API.
|
||||||
|
|
||||||
|
## Further Steps
|
||||||
|
|
||||||
|
- Express framework to create the listener however, you can replace it with anything you like. Express is quite a
|
||||||
|
bloated framework. Feel free to optimize to a better framework.
|
30
packages/bot/tsconfig.json
Normal file
30
packages/bot/tsconfig.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2022",
|
||||||
|
"module": "es2022",
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"importHelpers": true,
|
||||||
|
"allowUnusedLabels": false,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"strict": true,
|
||||||
|
"stripInternal": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"useUnknownInCatchVariables": false,
|
||||||
|
"allowUnreachableCode": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "node"
|
||||||
|
},
|
||||||
|
"include": ["./src/**/*", ".env"],
|
||||||
|
"ts-node": {
|
||||||
|
"esm": true,
|
||||||
|
"experimentalSpecifierResolution": "node",
|
||||||
|
"swc": true
|
||||||
|
}
|
||||||
|
}
|
144
packages/link2cid/.gitignore
vendored
Normal file
144
packages/link2cid/.gitignore
vendored
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
# Created by https://www.toptal.com/developers/gitignore/api/node
|
||||||
|
# Edit at https://www.toptal.com/developers/gitignore?templates=node
|
||||||
|
|
||||||
|
### Node ###
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
### Node Patch ###
|
||||||
|
# Serverless Webpack directories
|
||||||
|
.webpack/
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
|
||||||
|
# SvelteKit build / generate output
|
||||||
|
.svelte-kit
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/node
|
20
packages/link2cid/Dockerfile
Normal file
20
packages/link2cid/Dockerfile
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Reference-- https://pnpm.io/docker
|
||||||
|
|
||||||
|
FROM node:20-alpine AS base
|
||||||
|
ENV PNPM_HOME="/pnpm"
|
||||||
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
|
RUN corepack enable
|
||||||
|
WORKDIR /app
|
||||||
|
COPY ./package.json /app
|
||||||
|
EXPOSE 3939
|
||||||
|
|
||||||
|
FROM base AS dev
|
||||||
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install
|
||||||
|
CMD ["pnpm", "run", "dev"]
|
||||||
|
|
||||||
|
FROM base
|
||||||
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod
|
||||||
|
COPY ./index.js /app
|
||||||
|
ENTRYPOINT ["pnpm"]
|
||||||
|
CMD ["start"]
|
||||||
|
|
90
packages/link2cid/README.md
Normal file
90
packages/link2cid/README.md
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
# link2cid
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
I wish I could give [kubo](https://github.com/ipfs/kubo) or [IPFS cluster](https://ipfscluster.io/) a URI to a file and then they would download the file and add to ipfs, returning me a [CID](https://docs.ipfs.tech/concepts/glossary/#cid).
|
||||||
|
|
||||||
|
However, neither [kubo](https://github.com/ipfs/kubo) nor [IPFS cluster](https://ipfscluster.io/) can do this.
|
||||||
|
|
||||||
|
link2cid solves this issue with a REST API for adding a file at `url` to IPFS.
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Configure environment
|
||||||
|
|
||||||
|
Create a `.env` file. See `.env.example` for an example. Important environment variables are `API_KEY`, `PORT`, and `IPFS_URL`.
|
||||||
|
|
||||||
|
Install and run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
Make a GET REST request to `/add` with `url` as a query parameter. Expect a [SSE](https://wikipedia.org/wiki/Server-sent_events) response.
|
||||||
|
|
||||||
|
## dokku
|
||||||
|
|
||||||
|
dokku builder-dockerfile:set link2cid dockerfile-path link2cid.Dockerfile
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
#### [HTTPIE](https://httpie.io)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
http -A bearer -a $API_KEY --stream 'http://localhost:3939/add?url=https://upload.wikimedia.org/wikipedia/commons/7/70/Example.png' Accept:text/event-stream
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Access-Control-Allow-Origin: *
|
||||||
|
Cache-Control: no-cache
|
||||||
|
Connection: keep-alive
|
||||||
|
Content-Type: text/event-stream; charset=utf-8
|
||||||
|
Date: Thu, 21 Dec 2023 11:20:24 GMT
|
||||||
|
Transfer-Encoding: identity
|
||||||
|
X-Powered-By: Express
|
||||||
|
|
||||||
|
:ok
|
||||||
|
|
||||||
|
event: dlProgress
|
||||||
|
data: {
|
||||||
|
"percent": 100
|
||||||
|
}
|
||||||
|
|
||||||
|
event: addProgress
|
||||||
|
data: {
|
||||||
|
"percent": 100
|
||||||
|
}
|
||||||
|
|
||||||
|
event: end
|
||||||
|
data: {
|
||||||
|
"cid": "bafkreidj3jo7efguloaixz6vgivljlmowagagjtqv4yanyqgty2hrvg6km"
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Javascript
|
||||||
|
|
||||||
|
@todo this is incomplete/untested
|
||||||
|
|
||||||
|
```js
|
||||||
|
await fetch('http://localhost:3939/add?url=https://upload.wikimedia.org/wikipedia/commons/7/70/Example.png', {
|
||||||
|
headers: {
|
||||||
|
'accept': 'text/event-stream',
|
||||||
|
'authorization': `Bearer ${API_KEY}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Dev notes
|
||||||
|
|
||||||
|
### Generate API_KEY
|
||||||
|
|
||||||
|
```js
|
||||||
|
require('crypto').randomBytes(64).toString('hex')
|
||||||
|
```
|
||||||
|
|
||||||
|
### `TypeError: data.split is not a function`
|
||||||
|
|
||||||
|
If you see this error, make sure data in SSE event payload is a string, not a number.
|
284
packages/link2cid/index.js
Normal file
284
packages/link2cid/index.js
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
require('dotenv').config();
|
||||||
|
const express = require('express');
|
||||||
|
const bodyParser = require('body-parser');
|
||||||
|
const cors = require('cors');
|
||||||
|
const fs = require('fs');
|
||||||
|
const fsp = require('fs/promises');
|
||||||
|
const { openAsBlob } = require('node:fs');
|
||||||
|
const { rm, stat } = require('fs/promises');
|
||||||
|
const os = require('os');
|
||||||
|
const path = require('path');
|
||||||
|
const SseStream = require('ssestream').default;
|
||||||
|
const { Transform, Readable } = require('node:stream');
|
||||||
|
const { pipeline } = require('node:stream/promises');
|
||||||
|
const { differenceInSeconds } = require('date-fns');
|
||||||
|
|
||||||
|
const cidRegex = /Qm[1-9A-HJ-NP-Za-km-z]{44,}|b[A-Za-z2-7]{58,}|B[A-Z2-7]{58,}|z[1-9A-HJ-NP-Za-km-z]{48,}|F[0-9A-F]{50,}/;
|
||||||
|
const app = express();
|
||||||
|
app.use(cors());
|
||||||
|
app.use(bodyParser.json());
|
||||||
|
app.use(bodyParser.urlencoded({ extended: false }));
|
||||||
|
|
||||||
|
// environment variables
|
||||||
|
const port = process.env.PORT || 3000;
|
||||||
|
const ipfsUrl = process.env.IPFS_URL || 'http://localhost:5001';
|
||||||
|
if (!process.env.API_KEY) throw new Error('API_KEY was missing in env');
|
||||||
|
|
||||||
|
|
||||||
|
// greetz https://stackoverflow.com/a/51302466/1004931
|
||||||
|
async function downloadFile(url, filePath, sse) {
|
||||||
|
console.log(`downloading url=${url} to filePath=${filePath}`);
|
||||||
|
const res = await fetch(url);
|
||||||
|
const fileSize = res.headers.get('content-length');
|
||||||
|
const fileStream = fs.createWriteStream(filePath, { flags: 'wx' });
|
||||||
|
|
||||||
|
let downloadedBytes = 0;
|
||||||
|
const logInterval = 1 * 1024 * 1024; // 1MB in bytes
|
||||||
|
|
||||||
|
const progressLogger = new Transform({
|
||||||
|
transform(chunk, encoding, callback) {
|
||||||
|
downloadedBytes += chunk.length;
|
||||||
|
|
||||||
|
if (downloadedBytes % logInterval < chunk.length) {
|
||||||
|
console.log(`${downloadedBytes / (1024 * 1024)} MB processed`);
|
||||||
|
const progress = (downloadedBytes / fileSize) * 100;
|
||||||
|
console.log(`Download Progress: ${progress.toFixed(2)}%`);
|
||||||
|
sse.write({
|
||||||
|
event: 'dlProgress',
|
||||||
|
data: `${Math.floor(progress)}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.push(chunk);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
await pipeline(
|
||||||
|
res.body,
|
||||||
|
progressLogger,
|
||||||
|
fileStream
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log('download finished');
|
||||||
|
|
||||||
|
// verify the file
|
||||||
|
// If we don't, we get text error messages sent to kubo which gets added and it's a bad time.
|
||||||
|
console.log(`fileSize=${fileSize}. downloadedBytes=${downloadedBytes}`);
|
||||||
|
if (fileSize != downloadedBytes) throw new Error('downloadedBytes did not match fileSize');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function healthRes(_, res) {
|
||||||
|
const version = await getPackageVersion();
|
||||||
|
res.json({ error: false, message: `*link2cid ${version} pisses on the floor*` });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getPackageVersion() {
|
||||||
|
const packageJsonFile = await fsp.readFile(path.join(__dirname, 'package.json'), { encoding: 'utf-8' });
|
||||||
|
const json = JSON.parse(packageJsonFile);
|
||||||
|
return json.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* We use this to upload files and get progress notifications
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
async function streamingPostFetch(
|
||||||
|
url,
|
||||||
|
formData,
|
||||||
|
basename,
|
||||||
|
sse,
|
||||||
|
filesize
|
||||||
|
) {
|
||||||
|
console.log(`streamingPostFetch with url=${url}, formData=${formData.get('file')}, basename=${basename}, sse=${sse}, filesize=${filesize}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`HTTP error! Status-- ${res.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = res.body?.getReader();
|
||||||
|
if (!reader) {
|
||||||
|
throw new Error('Failed to get reader from response body');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
|
||||||
|
const chunk = new TextDecoder().decode(value);
|
||||||
|
const lines = chunk.split('\n');
|
||||||
|
for (const line of lines) {
|
||||||
|
const trimmedLine = line.trim()
|
||||||
|
if (!!trimmedLine) {
|
||||||
|
console.log(trimmedLine);
|
||||||
|
const json = JSON.parse(trimmedLine);
|
||||||
|
// console.log(`comparing json.Name=${json.Name} with basename=${basename}`);
|
||||||
|
sse.write({
|
||||||
|
event: 'addProgress',
|
||||||
|
data: `${Math.floor(json?.Size / filesize * 100)}`
|
||||||
|
})
|
||||||
|
if (json.Name === basename && json.Hash && json.Size) {
|
||||||
|
// this is the last chunk
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (done) {
|
||||||
|
throw new Error('Response reader finished before receiving a CID which indicates a failiure.');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('An error occurred:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function authenticate(req, res, next) {
|
||||||
|
const apiKey = req.query?.token;
|
||||||
|
if (!apiKey) {
|
||||||
|
const msg = `authorization 'token' was missing from query`;
|
||||||
|
console.error(msg);
|
||||||
|
return res.status(401).json({ error: true, message: msg });
|
||||||
|
}
|
||||||
|
if (apiKey !== process.env.API_KEY) {
|
||||||
|
const msg = 'INCORRECT API_KEY (wrong token)';
|
||||||
|
console.error(msg);
|
||||||
|
return res.status(403).json({ error: true, message: msg });
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function getFormStuff(filePath) {
|
||||||
|
const url = `${ipfsUrl}/api/v0/add?progress=false&cid-version=1&pin=true`;
|
||||||
|
const blob = await openAsBlob(filePath);
|
||||||
|
const basename = path.basename(filePath);
|
||||||
|
const filesize = (await stat(filePath)).size;
|
||||||
|
const formData = new FormData();
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
blob,
|
||||||
|
basename,
|
||||||
|
filesize,
|
||||||
|
formData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a file from URL to IPFS.
|
||||||
|
*
|
||||||
|
* uses SSE to send progress reports as the script
|
||||||
|
* downloads the file to disk and then does `ipfs add`
|
||||||
|
* finally returning a CID
|
||||||
|
*
|
||||||
|
* events:
|
||||||
|
* - heartbeat
|
||||||
|
* - dlProgress
|
||||||
|
* - addProgress
|
||||||
|
* - end
|
||||||
|
*/
|
||||||
|
async function addHandler(req, res) {
|
||||||
|
console.log(`/add`)
|
||||||
|
let url;
|
||||||
|
const urlStr = req.query.url;
|
||||||
|
if (!urlStr) return res.status(400).json({
|
||||||
|
error: 'url was missing from query'
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
url = new URL(urlStr);
|
||||||
|
} catch (e) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: e?.message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const timestamp = new Date().valueOf();
|
||||||
|
const fileName = `${timestamp}-${url.pathname.split('/').at(-1)}`;
|
||||||
|
const destinationFilePath = path.join(os.tmpdir(), fileName);
|
||||||
|
|
||||||
|
console.log(`fileName=${fileName}, destinationFilePath=${destinationFilePath}`);
|
||||||
|
|
||||||
|
const sse = new SseStream(req);
|
||||||
|
sse.pipe(res);
|
||||||
|
|
||||||
|
let hbStartTime = new Date();
|
||||||
|
const heartbeat = setInterval(() => {
|
||||||
|
sse.write({
|
||||||
|
event: 'heartbeat',
|
||||||
|
data: `${differenceInSeconds(new Date(), hbStartTime)}`
|
||||||
|
});
|
||||||
|
}, 15000);
|
||||||
|
|
||||||
|
res.on('close', () => {
|
||||||
|
console.log('Connection closed.');
|
||||||
|
clearTimeout(heartbeat);
|
||||||
|
sse.unpipe(res);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Downloading '${urlStr}' to destinationFilePath=${destinationFilePath}`);
|
||||||
|
await downloadFile(urlStr, destinationFilePath, sse);
|
||||||
|
|
||||||
|
|
||||||
|
sse.write({
|
||||||
|
event: 'dlProgress',
|
||||||
|
data: '100'
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`'ipfs add' the file ${destinationFilePath}`);
|
||||||
|
const { url: kuboUrl, blob, basename, filesize, formData } = await getFormStuff(destinationFilePath);
|
||||||
|
|
||||||
|
formData.append('file', blob, basename);
|
||||||
|
|
||||||
|
let cid;
|
||||||
|
try {
|
||||||
|
const output = await streamingPostFetch(kuboUrl, formData, basename, sse, filesize);
|
||||||
|
console.log(`streamingPostFetch output as follows.`);
|
||||||
|
console.log(output);
|
||||||
|
if (!output?.Hash) throw new Error('No CID was received from remote IPFS node.');
|
||||||
|
if (!output?.Size) throw new Error(`'ipfs add' was missing Size in its output.`);
|
||||||
|
// if (output.Size !== filesize) throw new Error(`input and output sizes did not match. Expected output.Size ${output.Size} to equal ${filesize}.`);
|
||||||
|
// console.log(`filesize=${filesize} output.Size=${output.Size}`);
|
||||||
|
cid = output.Hash;
|
||||||
|
|
||||||
|
console.log('cleanup');
|
||||||
|
await rm(destinationFilePath);
|
||||||
|
|
||||||
|
console.log('end SSE');
|
||||||
|
clearTimeout(heartbeat);
|
||||||
|
} catch (e) {
|
||||||
|
return sse.end({
|
||||||
|
event: 'end',
|
||||||
|
error: true,
|
||||||
|
message: e
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return sse.end({
|
||||||
|
event: 'end',
|
||||||
|
data: cid
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get('/', authenticate, healthRes);
|
||||||
|
app.get('/health', healthRes);
|
||||||
|
app.get('/add', authenticate, addHandler);
|
||||||
|
|
||||||
|
app.listen(port, async () => {
|
||||||
|
const version = await getPackageVersion();
|
||||||
|
console.log(`link2cid ${version} listening on port ${port}`);
|
||||||
|
});
|
30
packages/link2cid/package.json
Normal file
30
packages/link2cid/package.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "link2cid",
|
||||||
|
"version": "3.2.0",
|
||||||
|
"description": "REST API for adding files to IPFS",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"dev": "pnpm nodemon ./index.js",
|
||||||
|
"start": "node index.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "Unlicense",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/express": "^4.17.21",
|
||||||
|
"body-parser": "^1.20.2",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"date-fns": "^3.0.5",
|
||||||
|
"dotenv": "^16.3.1",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"ssestream": "^1.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^3.0.3"
|
||||||
|
}
|
||||||
|
}
|
691
packages/link2cid/pnpm-lock.yaml
generated
Normal file
691
packages/link2cid/pnpm-lock.yaml
generated
Normal file
@ -0,0 +1,691 @@
|
|||||||
|
lockfileVersion: '6.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
'@types/express':
|
||||||
|
specifier: ^4.17.21
|
||||||
|
version: 4.17.21
|
||||||
|
body-parser:
|
||||||
|
specifier: ^1.20.2
|
||||||
|
version: 1.20.2
|
||||||
|
cors:
|
||||||
|
specifier: ^2.8.5
|
||||||
|
version: 2.8.5
|
||||||
|
date-fns:
|
||||||
|
specifier: ^3.0.5
|
||||||
|
version: 3.0.5
|
||||||
|
dotenv:
|
||||||
|
specifier: ^16.3.1
|
||||||
|
version: 16.3.1
|
||||||
|
express:
|
||||||
|
specifier: ^4.18.2
|
||||||
|
version: 4.18.2
|
||||||
|
jsonwebtoken:
|
||||||
|
specifier: ^9.0.2
|
||||||
|
version: 9.0.2
|
||||||
|
ssestream:
|
||||||
|
specifier: ^1.1.0
|
||||||
|
version: 1.1.0
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
/@types/body-parser@1.19.5:
|
||||||
|
resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
|
||||||
|
dependencies:
|
||||||
|
'@types/connect': 3.4.38
|
||||||
|
'@types/node': 20.10.5
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@types/connect@3.4.38:
|
||||||
|
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 20.10.5
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@types/express-serve-static-core@4.17.41:
|
||||||
|
resolution: {integrity: sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==}
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 20.10.5
|
||||||
|
'@types/qs': 6.9.10
|
||||||
|
'@types/range-parser': 1.2.7
|
||||||
|
'@types/send': 0.17.4
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@types/express@4.17.21:
|
||||||
|
resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==}
|
||||||
|
dependencies:
|
||||||
|
'@types/body-parser': 1.19.5
|
||||||
|
'@types/express-serve-static-core': 4.17.41
|
||||||
|
'@types/qs': 6.9.10
|
||||||
|
'@types/serve-static': 1.15.5
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@types/http-errors@2.0.4:
|
||||||
|
resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@types/mime@1.3.5:
|
||||||
|
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@types/mime@3.0.4:
|
||||||
|
resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@types/node@20.10.5:
|
||||||
|
resolution: {integrity: sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==}
|
||||||
|
dependencies:
|
||||||
|
undici-types: 5.26.5
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@types/qs@6.9.10:
|
||||||
|
resolution: {integrity: sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@types/range-parser@1.2.7:
|
||||||
|
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@types/send@0.17.4:
|
||||||
|
resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==}
|
||||||
|
dependencies:
|
||||||
|
'@types/mime': 1.3.5
|
||||||
|
'@types/node': 20.10.5
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@types/serve-static@1.15.5:
|
||||||
|
resolution: {integrity: sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==}
|
||||||
|
dependencies:
|
||||||
|
'@types/http-errors': 2.0.4
|
||||||
|
'@types/mime': 3.0.4
|
||||||
|
'@types/node': 20.10.5
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/accepts@1.3.8:
|
||||||
|
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dependencies:
|
||||||
|
mime-types: 2.1.35
|
||||||
|
negotiator: 0.6.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/array-flatten@1.1.1:
|
||||||
|
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/body-parser@1.20.1:
|
||||||
|
resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==}
|
||||||
|
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
||||||
|
dependencies:
|
||||||
|
bytes: 3.1.2
|
||||||
|
content-type: 1.0.5
|
||||||
|
debug: 2.6.9
|
||||||
|
depd: 2.0.0
|
||||||
|
destroy: 1.2.0
|
||||||
|
http-errors: 2.0.0
|
||||||
|
iconv-lite: 0.4.24
|
||||||
|
on-finished: 2.4.1
|
||||||
|
qs: 6.11.0
|
||||||
|
raw-body: 2.5.1
|
||||||
|
type-is: 1.6.18
|
||||||
|
unpipe: 1.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/body-parser@1.20.2:
|
||||||
|
resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==}
|
||||||
|
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
||||||
|
dependencies:
|
||||||
|
bytes: 3.1.2
|
||||||
|
content-type: 1.0.5
|
||||||
|
debug: 2.6.9
|
||||||
|
depd: 2.0.0
|
||||||
|
destroy: 1.2.0
|
||||||
|
http-errors: 2.0.0
|
||||||
|
iconv-lite: 0.4.24
|
||||||
|
on-finished: 2.4.1
|
||||||
|
qs: 6.11.0
|
||||||
|
raw-body: 2.5.2
|
||||||
|
type-is: 1.6.18
|
||||||
|
unpipe: 1.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/buffer-equal-constant-time@1.0.1:
|
||||||
|
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/bytes@3.1.2:
|
||||||
|
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/call-bind@1.0.5:
|
||||||
|
resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==}
|
||||||
|
dependencies:
|
||||||
|
function-bind: 1.1.2
|
||||||
|
get-intrinsic: 1.2.2
|
||||||
|
set-function-length: 1.1.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/content-disposition@0.5.4:
|
||||||
|
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dependencies:
|
||||||
|
safe-buffer: 5.2.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/content-type@1.0.5:
|
||||||
|
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/cookie-signature@1.0.6:
|
||||||
|
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/cookie@0.5.0:
|
||||||
|
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/cors@2.8.5:
|
||||||
|
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
|
||||||
|
engines: {node: '>= 0.10'}
|
||||||
|
dependencies:
|
||||||
|
object-assign: 4.1.1
|
||||||
|
vary: 1.1.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/date-fns@3.0.5:
|
||||||
|
resolution: {integrity: sha512-Q4Tq5c5s/Zl/zbgdWf6pejn9ru7UwdIlLfvEEg1hVsQNQ7LKt76qIduagIT9OPK7+JCv1mAKherdU6bOqGYDnw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/debug@2.6.9:
|
||||||
|
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
|
||||||
|
peerDependencies:
|
||||||
|
supports-color: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
supports-color:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
ms: 2.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/define-data-property@1.1.1:
|
||||||
|
resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
dependencies:
|
||||||
|
get-intrinsic: 1.2.2
|
||||||
|
gopd: 1.0.1
|
||||||
|
has-property-descriptors: 1.0.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/depd@2.0.0:
|
||||||
|
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/destroy@1.2.0:
|
||||||
|
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
|
||||||
|
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/dotenv@16.3.1:
|
||||||
|
resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/ecdsa-sig-formatter@1.0.11:
|
||||||
|
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
|
||||||
|
dependencies:
|
||||||
|
safe-buffer: 5.2.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/ee-first@1.1.1:
|
||||||
|
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/encodeurl@1.0.2:
|
||||||
|
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/escape-html@1.0.3:
|
||||||
|
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/etag@1.8.1:
|
||||||
|
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/express@4.18.2:
|
||||||
|
resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==}
|
||||||
|
engines: {node: '>= 0.10.0'}
|
||||||
|
dependencies:
|
||||||
|
accepts: 1.3.8
|
||||||
|
array-flatten: 1.1.1
|
||||||
|
body-parser: 1.20.1
|
||||||
|
content-disposition: 0.5.4
|
||||||
|
content-type: 1.0.5
|
||||||
|
cookie: 0.5.0
|
||||||
|
cookie-signature: 1.0.6
|
||||||
|
debug: 2.6.9
|
||||||
|
depd: 2.0.0
|
||||||
|
encodeurl: 1.0.2
|
||||||
|
escape-html: 1.0.3
|
||||||
|
etag: 1.8.1
|
||||||
|
finalhandler: 1.2.0
|
||||||
|
fresh: 0.5.2
|
||||||
|
http-errors: 2.0.0
|
||||||
|
merge-descriptors: 1.0.1
|
||||||
|
methods: 1.1.2
|
||||||
|
on-finished: 2.4.1
|
||||||
|
parseurl: 1.3.3
|
||||||
|
path-to-regexp: 0.1.7
|
||||||
|
proxy-addr: 2.0.7
|
||||||
|
qs: 6.11.0
|
||||||
|
range-parser: 1.2.1
|
||||||
|
safe-buffer: 5.2.1
|
||||||
|
send: 0.18.0
|
||||||
|
serve-static: 1.15.0
|
||||||
|
setprototypeof: 1.2.0
|
||||||
|
statuses: 2.0.1
|
||||||
|
type-is: 1.6.18
|
||||||
|
utils-merge: 1.0.1
|
||||||
|
vary: 1.1.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/finalhandler@1.2.0:
|
||||||
|
resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
dependencies:
|
||||||
|
debug: 2.6.9
|
||||||
|
encodeurl: 1.0.2
|
||||||
|
escape-html: 1.0.3
|
||||||
|
on-finished: 2.4.1
|
||||||
|
parseurl: 1.3.3
|
||||||
|
statuses: 2.0.1
|
||||||
|
unpipe: 1.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/forwarded@0.2.0:
|
||||||
|
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/fresh@0.5.2:
|
||||||
|
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/function-bind@1.1.2:
|
||||||
|
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/get-intrinsic@1.2.2:
|
||||||
|
resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==}
|
||||||
|
dependencies:
|
||||||
|
function-bind: 1.1.2
|
||||||
|
has-proto: 1.0.1
|
||||||
|
has-symbols: 1.0.3
|
||||||
|
hasown: 2.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/gopd@1.0.1:
|
||||||
|
resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
|
||||||
|
dependencies:
|
||||||
|
get-intrinsic: 1.2.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/has-property-descriptors@1.0.1:
|
||||||
|
resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==}
|
||||||
|
dependencies:
|
||||||
|
get-intrinsic: 1.2.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/has-proto@1.0.1:
|
||||||
|
resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/has-symbols@1.0.3:
|
||||||
|
resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/hasown@2.0.0:
|
||||||
|
resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
dependencies:
|
||||||
|
function-bind: 1.1.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/http-errors@2.0.0:
|
||||||
|
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
dependencies:
|
||||||
|
depd: 2.0.0
|
||||||
|
inherits: 2.0.4
|
||||||
|
setprototypeof: 1.2.0
|
||||||
|
statuses: 2.0.1
|
||||||
|
toidentifier: 1.0.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/iconv-lite@0.4.24:
|
||||||
|
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
dependencies:
|
||||||
|
safer-buffer: 2.1.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/inherits@2.0.4:
|
||||||
|
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/ipaddr.js@1.9.1:
|
||||||
|
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
||||||
|
engines: {node: '>= 0.10'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/jsonwebtoken@9.0.2:
|
||||||
|
resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==}
|
||||||
|
engines: {node: '>=12', npm: '>=6'}
|
||||||
|
dependencies:
|
||||||
|
jws: 3.2.2
|
||||||
|
lodash.includes: 4.3.0
|
||||||
|
lodash.isboolean: 3.0.3
|
||||||
|
lodash.isinteger: 4.0.4
|
||||||
|
lodash.isnumber: 3.0.3
|
||||||
|
lodash.isplainobject: 4.0.6
|
||||||
|
lodash.isstring: 4.0.1
|
||||||
|
lodash.once: 4.1.1
|
||||||
|
ms: 2.1.3
|
||||||
|
semver: 7.5.4
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/jwa@1.4.1:
|
||||||
|
resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==}
|
||||||
|
dependencies:
|
||||||
|
buffer-equal-constant-time: 1.0.1
|
||||||
|
ecdsa-sig-formatter: 1.0.11
|
||||||
|
safe-buffer: 5.2.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/jws@3.2.2:
|
||||||
|
resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==}
|
||||||
|
dependencies:
|
||||||
|
jwa: 1.4.1
|
||||||
|
safe-buffer: 5.2.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/lodash.includes@4.3.0:
|
||||||
|
resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/lodash.isboolean@3.0.3:
|
||||||
|
resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/lodash.isinteger@4.0.4:
|
||||||
|
resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/lodash.isnumber@3.0.3:
|
||||||
|
resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/lodash.isplainobject@4.0.6:
|
||||||
|
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/lodash.isstring@4.0.1:
|
||||||
|
resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/lodash.once@4.1.1:
|
||||||
|
resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/lru-cache@6.0.0:
|
||||||
|
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
dependencies:
|
||||||
|
yallist: 4.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/media-typer@0.3.0:
|
||||||
|
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/merge-descriptors@1.0.1:
|
||||||
|
resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/methods@1.1.2:
|
||||||
|
resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/mime-db@1.52.0:
|
||||||
|
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/mime-types@2.1.35:
|
||||||
|
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dependencies:
|
||||||
|
mime-db: 1.52.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/mime@1.6.0:
|
||||||
|
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/ms@2.0.0:
|
||||||
|
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/ms@2.1.3:
|
||||||
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/negotiator@0.6.3:
|
||||||
|
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/object-assign@4.1.1:
|
||||||
|
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/object-inspect@1.13.1:
|
||||||
|
resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/on-finished@2.4.1:
|
||||||
|
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
dependencies:
|
||||||
|
ee-first: 1.1.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/parseurl@1.3.3:
|
||||||
|
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/path-to-regexp@0.1.7:
|
||||||
|
resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/proxy-addr@2.0.7:
|
||||||
|
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||||
|
engines: {node: '>= 0.10'}
|
||||||
|
dependencies:
|
||||||
|
forwarded: 0.2.0
|
||||||
|
ipaddr.js: 1.9.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/qs@6.11.0:
|
||||||
|
resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
|
||||||
|
engines: {node: '>=0.6'}
|
||||||
|
dependencies:
|
||||||
|
side-channel: 1.0.4
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/range-parser@1.2.1:
|
||||||
|
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/raw-body@2.5.1:
|
||||||
|
resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
dependencies:
|
||||||
|
bytes: 3.1.2
|
||||||
|
http-errors: 2.0.0
|
||||||
|
iconv-lite: 0.4.24
|
||||||
|
unpipe: 1.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/raw-body@2.5.2:
|
||||||
|
resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
dependencies:
|
||||||
|
bytes: 3.1.2
|
||||||
|
http-errors: 2.0.0
|
||||||
|
iconv-lite: 0.4.24
|
||||||
|
unpipe: 1.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/safe-buffer@5.2.1:
|
||||||
|
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/safer-buffer@2.1.2:
|
||||||
|
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/semver@7.5.4:
|
||||||
|
resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
lru-cache: 6.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/send@0.18.0:
|
||||||
|
resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
|
||||||
|
engines: {node: '>= 0.8.0'}
|
||||||
|
dependencies:
|
||||||
|
debug: 2.6.9
|
||||||
|
depd: 2.0.0
|
||||||
|
destroy: 1.2.0
|
||||||
|
encodeurl: 1.0.2
|
||||||
|
escape-html: 1.0.3
|
||||||
|
etag: 1.8.1
|
||||||
|
fresh: 0.5.2
|
||||||
|
http-errors: 2.0.0
|
||||||
|
mime: 1.6.0
|
||||||
|
ms: 2.1.3
|
||||||
|
on-finished: 2.4.1
|
||||||
|
range-parser: 1.2.1
|
||||||
|
statuses: 2.0.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/serve-static@1.15.0:
|
||||||
|
resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==}
|
||||||
|
engines: {node: '>= 0.8.0'}
|
||||||
|
dependencies:
|
||||||
|
encodeurl: 1.0.2
|
||||||
|
escape-html: 1.0.3
|
||||||
|
parseurl: 1.3.3
|
||||||
|
send: 0.18.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/set-function-length@1.1.1:
|
||||||
|
resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
dependencies:
|
||||||
|
define-data-property: 1.1.1
|
||||||
|
get-intrinsic: 1.2.2
|
||||||
|
gopd: 1.0.1
|
||||||
|
has-property-descriptors: 1.0.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/setprototypeof@1.2.0:
|
||||||
|
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/side-channel@1.0.4:
|
||||||
|
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
|
||||||
|
dependencies:
|
||||||
|
call-bind: 1.0.5
|
||||||
|
get-intrinsic: 1.2.2
|
||||||
|
object-inspect: 1.13.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/ssestream@1.1.0:
|
||||||
|
resolution: {integrity: sha512-UOS3JTuGqGEOH89mfHFwVOJNH2+JX9ebIWuw6WBQXpkVOxbdoY3RMliSHzshL4XVYJJrcul5NkuvDFCzgYu1Lw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/statuses@2.0.1:
|
||||||
|
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/toidentifier@1.0.1:
|
||||||
|
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
||||||
|
engines: {node: '>=0.6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/type-is@1.6.18:
|
||||||
|
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dependencies:
|
||||||
|
media-typer: 0.3.0
|
||||||
|
mime-types: 2.1.35
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/undici-types@5.26.5:
|
||||||
|
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/unpipe@1.0.0:
|
||||||
|
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/utils-merge@1.0.1:
|
||||||
|
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
|
||||||
|
engines: {node: '>= 0.4.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/vary@1.1.2:
|
||||||
|
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/yallist@4.0.0:
|
||||||
|
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||||
|
dev: false
|
@ -1,54 +0,0 @@
|
|||||||
|
|
||||||
import Link from "next/link"
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
|
||||||
import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons"
|
|
||||||
|
|
||||||
export default async function Page() {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="content">
|
|
||||||
<div className="box">
|
|
||||||
|
|
||||||
<div className="block">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h1>The Story of Futureporn</h1>
|
|
||||||
|
|
||||||
<p>2020 was a busy time for me. I started a small business, attended lots of support group meetings, and rode my bicycle more than ever before. I often found myself away from home during times when Melody was streaming on Chaturbate.</p>
|
|
||||||
|
|
||||||
<p>You probably know that unlike other video streaming platforms, Chaturbate doesn’t store any VODs. When I missed a stream, I felt sad. I felt like I had missed out and there’s no way I’d ever find out what happened.</p>
|
|
||||||
|
|
||||||
<p>I’m pretty handy with computer software. Creating programs and websites has been my biggest passion for my entire life. In order to never miss a ProjektMelody livestream again, I resolved to create some software that would automatically record Melody’s Chaturbate streams.</p>
|
|
||||||
|
|
||||||
<p>I put the project on hold for a few months, because I didn’t think I could make a website that could handle the traffic that the Science Team would generate.</p>
|
|
||||||
|
|
||||||
<p>I couldn’t shake the idea, though. I wanted Futureporn to exist no matter what!</p>
|
|
||||||
|
|
||||||
<p>I’ve been working on this project off and on for about a year and a half. It’s gone through several iterations, and each iteration has taught me something new. Right now, the website is usable for finding and downloading ProjektMelody Chaturbate VODs. Every VOD has a link to Melody’s tweet which originally announced the stream, and a title/description derived from said tweet. I have archived all of her known Chaturbate streams.</p>
|
|
||||||
|
|
||||||
<p>The project has evolved over time. Originally, I wanted to have a place to go when I missed one of Melody’s livestreams. Now, the project is becoming a sort of a time capsule. We’ve all seen how Melody has been de-platformed a half dozen times, and I’ve taken this to heart. Platforms are a problem for data preservation! This is one of the reasons for why I chose to use the Inter-Planetary File System (<Link target="_blank" href="https://ipfs.io/">IPFS<FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></Link>.)</p>
|
|
||||||
|
|
||||||
<p>IPFS can end 404s through “pinning,” a way of mirroring a file across several different computers. It’s a way for computers to work together to serve content instead of working independently, thus gaining redundancy and performance benefits. I see a future where pinning files on IPFS becomes as easy as pinning a photo on Pinterest. Fans of ProjektMelody can pin the VODs on Futureporn, increasing that VOD’s replication and servability to future viewers.</p>
|
|
||||||
|
|
||||||
<p>But wait, there’s more! I have been thinking about a bunch of other stuff that could be done with past VODs. I think the most exciting thing would be to use computer vision to parse Melody’s vibrator activity from the video, and export to a data file. This data file could be used to send good vibes to a viewer’s vibrator in-sync with VOD playback. Feel what Melody feels! Very exciting, very sexy! This is a long-term goal for Futureporn.</p>
|
|
||||||
|
|
||||||
<p>I have several goals for Futureporn, as listed on the <Link href="/goals">Goals page</Link>. A bunch of them have to do with increasing video playback performance, user interface design, but there’s a few that are pretty eccentric… Serving ProjektMelody VODs to Mars, for example!</p>
|
|
||||||
|
|
||||||
<p>I hope this site is useful to all the Science Team!</p>
|
|
||||||
|
|
||||||
<article className="mt-5 message is-success">
|
|
||||||
<div className="message-body">
|
|
||||||
<p>Futureporn needs financial support to continue improving. If you enjoy this website, please consider <Link target="_blank" href="https://patreon.com/CJ_Clippy">becoming a patron<FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></Link>.</p>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
import Link from 'next/link';
|
|
||||||
import { siteUrl } from '@/lib/constants';
|
|
||||||
import { IBlogPost } from '@/lib/blog';
|
|
||||||
|
|
||||||
|
|
||||||
export default async function PostsPage() {
|
|
||||||
const res = await fetch(`${siteUrl}/api/blogs`);
|
|
||||||
const posts: IBlogPost[] = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
slug: '2021-10-29-the-story-of-futureporn',
|
|
||||||
title: 'The Story Of Futureporn'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="container mb-5">
|
|
||||||
<div className="content mb-5">
|
|
||||||
|
|
||||||
<h1>All Blog Posts</h1>
|
|
||||||
<hr style={{ width: '220px' }} />
|
|
||||||
|
|
||||||
<div style={{ paddingTop: '40px' }}>
|
|
||||||
{posts.map((post: IBlogPost) => (
|
|
||||||
<article key={post.slug}>
|
|
||||||
<Link href={`/blog/${post.slug}`}>
|
|
||||||
<h2>> {post.title}</h2>
|
|
||||||
</Link>
|
|
||||||
</article>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -6,18 +6,19 @@ export interface IArchiveProgressProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function ArchiveProgress ({ vtuber }: IArchiveProgressProps) {
|
export default async function ArchiveProgress ({ vtuber }: IArchiveProgressProps) {
|
||||||
const streams = await getAllStreamsForVtuber(vtuber.id);
|
// const streams = await getAllStreamsForVtuber(vtuber.id);
|
||||||
const goodStreams = await getAllStreamsForVtuber(vtuber.id, ['good']);
|
// const goodStreams = await getAllStreamsForVtuber(vtuber.id, ['good']);
|
||||||
const issueStreams = await getAllStreamsForVtuber(vtuber.id, ['issue']);
|
// const issueStreams = await getAllStreamsForVtuber(vtuber.id, ['issue']);
|
||||||
const totalStreams = streams.length;
|
// const totalStreams = streams.length;
|
||||||
const eligibleStreams = issueStreams.length+goodStreams.length;
|
// const eligibleStreams = issueStreams.length+goodStreams.length;
|
||||||
|
|
||||||
// Check if totalStreams is not zero before calculating completedPercentage
|
// // Check if totalStreams is not zero before calculating completedPercentage
|
||||||
const completedPercentage = (totalStreams !== 0) ? Math.round(eligibleStreams / totalStreams * 100) : 0;
|
// const completedPercentage = (totalStreams !== 0) ? Math.round(eligibleStreams / totalStreams * 100) : 0;
|
||||||
return (
|
// return (
|
||||||
<div>
|
// <div>
|
||||||
<p className="heading">{eligibleStreams}/{totalStreams} Streams Archived ({completedPercentage}%)</p>
|
// <p className="heading">{eligibleStreams}/{totalStreams} Streams Archived ({completedPercentage}%)</p>
|
||||||
<progress className="progress is-success" value={eligibleStreams} max={totalStreams}>{completedPercentage}%</progress>
|
// <progress className="progress is-success" value={eligibleStreams} max={totalStreams}>{completedPercentage}%</progress>
|
||||||
</div>
|
// </div>
|
||||||
)
|
// )
|
||||||
|
return "@todo"
|
||||||
}
|
}
|
@ -1,125 +0,0 @@
|
|||||||
'use client';
|
|
||||||
// greets https://github.com/wa0x6e/cal-heatmap-react-starter/blob/main/src/components/cal-heatmap.tsx
|
|
||||||
|
|
||||||
import CalHeatmap from 'cal-heatmap';
|
|
||||||
// @ts-ignore cal-heatmap is jenk
|
|
||||||
import Legend from 'cal-heatmap/plugins/Legend';
|
|
||||||
// @ts-ignore cal-heatmap is jenk
|
|
||||||
import Tooltip from 'cal-heatmap/plugins/Tooltip';
|
|
||||||
import { DataRecord } from 'cal-heatmap/src/options/Options';
|
|
||||||
import 'cal-heatmap/cal-heatmap.css';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import { useEffect, useState, useRef } from 'react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import { getSafeDate } from '@/lib/dates';
|
|
||||||
|
|
||||||
export interface ICalProps {
|
|
||||||
data: DataRecord[];
|
|
||||||
slug: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function Cal({ data, slug }: ICalProps) {
|
|
||||||
const router = useRouter();
|
|
||||||
const [cellSize, setCellSize] = useState(13);
|
|
||||||
const [targetElementId, setTargetElementId] = useState('');
|
|
||||||
|
|
||||||
const generateUniqueId = () => {
|
|
||||||
return `cal-${Math.random().toString(36).substring(2, 9)}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const updateCellSize = () => {
|
|
||||||
const windowWidth = window.innerWidth;
|
|
||||||
if (windowWidth > 1400) {
|
|
||||||
setCellSize(15); // Adjust the cell size for width > 1400px
|
|
||||||
} else if (windowWidth > 730) {
|
|
||||||
setCellSize(10); // Adjust the cell size for width > 730px
|
|
||||||
} else {
|
|
||||||
setCellSize(3); // Adjust the cell size for width <= 730px
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateCellSize();
|
|
||||||
// Event listener to update cell size on window resize
|
|
||||||
window.addEventListener('resize', updateCellSize);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('resize', updateCellSize);
|
|
||||||
};
|
|
||||||
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTargetElementId(generateUniqueId());
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!targetElementId) return;
|
|
||||||
const cal = new CalHeatmap();
|
|
||||||
// @ts-ignore
|
|
||||||
cal.on('click', (
|
|
||||||
event: string,
|
|
||||||
timestamp: number,
|
|
||||||
value: number
|
|
||||||
) => {
|
|
||||||
router.push(`/vt/${slug}/stream/${getSafeDate(new Date(timestamp))}`);
|
|
||||||
// console.log(`slug=${slug} safeDate=${getSafeDate(new Date(timestamp))}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
cal.paint(
|
|
||||||
{
|
|
||||||
itemSelector: `#${targetElementId}`,
|
|
||||||
scale: {
|
|
||||||
color: {
|
|
||||||
// @ts-ignore this shit is straight from the example website
|
|
||||||
domain: ['missing', 'issue', 'good'],
|
|
||||||
type: 'ordinal',
|
|
||||||
range: ['red', 'yellow', 'green']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
theme: 'dark',
|
|
||||||
verticalOrientation: false,
|
|
||||||
data: {
|
|
||||||
source: data,
|
|
||||||
x: 'date',
|
|
||||||
y: 'value',
|
|
||||||
// @ts-ignore this shit is straight from the example website
|
|
||||||
groupY: d => d[0]
|
|
||||||
},
|
|
||||||
range: 12,
|
|
||||||
date: { start: data[0].date },
|
|
||||||
domain: {
|
|
||||||
type: 'month',
|
|
||||||
gutter: 4,
|
|
||||||
label: { text: 'MMM', textAlign: 'start', position: 'top' }
|
|
||||||
},
|
|
||||||
subDomain: {
|
|
||||||
type: 'ghDay',
|
|
||||||
radius: 2,
|
|
||||||
width: cellSize,
|
|
||||||
height: cellSize,
|
|
||||||
gutter: 4,
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
[
|
|
||||||
Tooltip,
|
|
||||||
{
|
|
||||||
text: ((ts: number, value: string, dayjsDate: dayjs.Dayjs) => {
|
|
||||||
return `${!!value ? value+' - '+dayjsDate.toString() : dayjsDate.toString() }`;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
}, [targetElementId, data, cellSize, router, slug]);
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div id={targetElementId}></div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
@ -5,6 +5,7 @@ import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons";
|
|||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
export default async function Contributors() {
|
export default async function Contributors() {
|
||||||
|
try {
|
||||||
const contributors = await getContributors();
|
const contributors = await getContributors();
|
||||||
if (!contributors || contributors.length < 1) return (
|
if (!contributors || contributors.length < 1) return (
|
||||||
<SkeletonTheme baseColor="#000" highlightColor="#000" width="25%">
|
<SkeletonTheme baseColor="#000" highlightColor="#000" width="25%">
|
||||||
@ -30,4 +31,10 @@ export default async function Contributors() {
|
|||||||
return (
|
return (
|
||||||
<>{contributorList}</>
|
<>{contributorList}</>
|
||||||
)
|
)
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
return <p>Failed to fetch contributor list</p>
|
||||||
|
}
|
||||||
}
|
}
|
@ -25,7 +25,6 @@ export default function Footer() {
|
|||||||
<li><Link href="/tags">Tags</Link></li>
|
<li><Link href="/tags">Tags</Link></li>
|
||||||
<li><Link href="/feed">RSS Feed</Link></li>
|
<li><Link href="/feed">RSS Feed</Link></li>
|
||||||
<li><Link href="/api">API</Link></li>
|
<li><Link href="/api">API</Link></li>
|
||||||
<li><Link href="/blog">Blog</Link></li>
|
|
||||||
<li><Link href="https://status.futureporn.net/" target="_blank"><span className="mr-1">Status</span><FontAwesomeIcon icon={faExternalLinkAlt} className="fab fa-external-link-alt"></FontAwesomeIcon></Link></li>
|
<li><Link href="https://status.futureporn.net/" target="_blank"><span className="mr-1">Status</span><FontAwesomeIcon icon={faExternalLinkAlt} className="fab fa-external-link-alt"></FontAwesomeIcon></Link></li>
|
||||||
<li><Link href="/upload">Upload</Link></li>
|
<li><Link href="/upload">Upload</Link></li>
|
||||||
<li><Link href="/profile">Profile</Link></li>
|
<li><Link href="/profile">Profile</Link></li>
|
||||||
|
@ -11,7 +11,7 @@ export default function Pager({ baseUrl, page, pageCount }: IPagerProps): React.
|
|||||||
|
|
||||||
const getPagePath = (page: any): string => {
|
const getPagePath = (page: any): string => {
|
||||||
const pageNumber = parseInt(page);
|
const pageNumber = parseInt(page);
|
||||||
console.log(`pageNumber=${pageNumber}`)
|
console.log(`pageNumber=${pageNumber}`);
|
||||||
return `${baseUrl}/${pageNumber}`;
|
return `${baseUrl}/${pageNumber}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ export function Tagger({ vod, setTimestamps }: ITaggerProps): React.JSX.Element
|
|||||||
if (authData?.accessToken) {
|
if (authData?.accessToken) {
|
||||||
setIsAuthed(true);
|
setIsAuthed(true);
|
||||||
}
|
}
|
||||||
}, [isAuthed]);
|
}, [isAuthed, authData]);
|
||||||
|
|
||||||
|
|
||||||
async function getRandomSuggestions() {
|
async function getRandomSuggestions() {
|
||||||
|
@ -253,7 +253,7 @@ export default function UploadForm({ vtubers }: IUploadFormProps) {
|
|||||||
{...register('vtuber')}
|
{...register('vtuber')}
|
||||||
>
|
>
|
||||||
{vtubers.map((vtuber: IVtuber) => (
|
{vtubers.map((vtuber: IVtuber) => (
|
||||||
<option value={vtuber.id}>{vtuber.attributes.displayName}</option>
|
<option key={vtuber.id} value={vtuber.id}>{vtuber.attributes.displayName}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,8 +10,8 @@ import LinkableHeading from './linkable-heading';
|
|||||||
|
|
||||||
|
|
||||||
export function getVodTitle(vod: IVod): string {
|
export function getVodTitle(vod: IVod): string {
|
||||||
console.log('lets getVodTitle, ey?')
|
// console.log('lets getVodTitle, ey?')
|
||||||
console.log(JSON.stringify(vod, null, 2))
|
// console.log(JSON.stringify(vod, null, 2))
|
||||||
return vod.attributes.title || vod.attributes.announceTitle || (vod.attributes?.date2 && vod.attributes?.vtuber?.data?.attributes?.displayName) ? `${vod.attributes.vtuber.data.attributes.displayName} ${vod.attributes.date2}` : `VOD ${vod.id}`;
|
return vod.attributes.title || vod.attributes.announceTitle || (vod.attributes?.date2 && vod.attributes?.vtuber?.data?.attributes?.displayName) ? `${vod.attributes.vtuber.data.attributes.displayName} ${vod.attributes.date2}` : `VOD ${vod.id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import Tes from '@/assets/svg/tes';
|
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
return (
|
return (
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<div className="box">
|
<div className="box">
|
||||||
<h2>Healthy!</h2>
|
<h2>Healthy!</h2>
|
||||||
<Tes></Tes>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
export interface IBlogPost {
|
|
||||||
slug: string;
|
|
||||||
title: string;
|
|
||||||
id: number;
|
|
||||||
}
|
|
@ -1,7 +1,10 @@
|
|||||||
// export const strapiUrl = (process.env.NODE_ENV === 'production') ? 'https://portal.futureporn.net' : 'https://chisel.sbtp:1337'
|
if (!process.env.NEXT_PUBLIC_SITE_URL) throw new Error('NEXT_PUBLIC_SITE_URL was missing in env');
|
||||||
// export const siteUrl = (process.env.NODE_ENV === 'production') ? 'https://futureporn.net' : 'http://localhost:3000'
|
if (!process.env.NEXT_PUBLIC_STRAPI_URL) throw new Error('NEXT_PUBLIC_STRAPI_URL was missing in env');
|
||||||
export const siteUrl = process.env.NEXT_PUBLIC_SITE_URL
|
if (!process.env.NEXT_PUBLIC_UPPY_COMPANION_URL) throw new Error('NEXT_PUBLIC_UPPY_COMPANION_URL undefined in env');
|
||||||
export const strapiUrl = process.env.NEXT_PUBLIC_STRAPI_URL
|
|
||||||
|
export const companionUrl = ''+process.env.NEXT_PUBLIC_UPPY_COMPANION_URL
|
||||||
|
export const siteUrl = ''+process.env.NEXT_PUBLIC_SITE_URL
|
||||||
|
export const strapiUrl = ''+process.env.NEXT_PUBLIC_STRAPI_URL
|
||||||
export const patreonSupporterBenefitId: string = '4760169'
|
export const patreonSupporterBenefitId: string = '4760169'
|
||||||
export const patreonQuantumSupporterId: string = '10663202'
|
export const patreonQuantumSupporterId: string = '10663202'
|
||||||
export const patreonVideoAccessBenefitId: string = '13462019'
|
export const patreonVideoAccessBenefitId: string = '13462019'
|
||||||
|
@ -80,8 +80,8 @@ export async function getGoals(pledgeSum: number): Promise<IGoals | null> {
|
|||||||
const funded = filterAndSortGoals(openData.concat(closedData), true);
|
const funded = filterAndSortGoals(openData.concat(closedData), true);
|
||||||
const unfunded = filterAndSortGoals(openData.concat(closedData), false);
|
const unfunded = filterAndSortGoals(openData.concat(closedData), false);
|
||||||
|
|
||||||
console.log('the following are unfunded goals')
|
// console.log('the following are unfunded goals')
|
||||||
console.log(unfunded)
|
// console.log(unfunded)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
complete: closedData,
|
complete: closedData,
|
||||||
|
@ -275,7 +275,7 @@ export async function getAllStreamsForVtuber(vtuberId: number, archiveStatuses =
|
|||||||
});
|
});
|
||||||
|
|
||||||
console.log(`strapiUrl=${strapiUrl}`)
|
console.log(`strapiUrl=${strapiUrl}`)
|
||||||
const response = await fetch(`${strapiUrl}/api/streams?${query}`, fetchStreamsOptions);
|
const response = await fetch(`${strapiUrl}/api/streams?${query}`, fetchStreamsOptions)
|
||||||
|
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
// If the response status is not 200 (OK), consider it a network failure
|
// If the response status is not 200 (OK), consider it a network failure
|
||||||
|
@ -120,7 +120,7 @@ export async function getAllVtubers(): Promise<IVtuber[] | null> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`Getting /api/vtubers page=${currentPage}`);
|
console.log(`Getting ${strapiUrl}/api/vtubers page=${currentPage}`);
|
||||||
const response = await fetch(`${strapiUrl}/api/vtubers?${query}`, fetchVtubersOptions);
|
const response = await fetch(`${strapiUrl}/api/vtubers?${query}`, fetchVtubersOptions);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
@ -5,8 +5,8 @@ import Uppy from '@uppy/core';
|
|||||||
import AwsS3 from '@uppy/aws-s3';
|
import AwsS3 from '@uppy/aws-s3';
|
||||||
import RemoteSources from '@uppy/remote-sources';
|
import RemoteSources from '@uppy/remote-sources';
|
||||||
import { useAuth } from './components/auth';
|
import { useAuth } from './components/auth';
|
||||||
|
import { companionUrl } from '@/lib/constants';
|
||||||
|
|
||||||
const companionUrl = process.env.NEXT_PUBLIC_UPPY_COMPANION_URL!
|
|
||||||
|
|
||||||
export const UppyContext = createContext(new Uppy());
|
export const UppyContext = createContext(new Uppy());
|
||||||
|
|
||||||
|
@ -1,70 +0,0 @@
|
|||||||
|
|
||||||
import { getVtuberBySlug } from '@/lib/vtubers';
|
|
||||||
import { getAllStreamsForVtuber } from '@/lib/streams';
|
|
||||||
import NotFound from '../not-found';
|
|
||||||
import { DataRecord } from 'cal-heatmap/src/options/Options';
|
|
||||||
import { Cal } from '@/components/cal';
|
|
||||||
|
|
||||||
interface IPageProps {
|
|
||||||
params: {
|
|
||||||
slug: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getArchiveStatusValue(archiveStatus: string): number {
|
|
||||||
if (archiveStatus === 'good') return 2;
|
|
||||||
if (archiveStatus === 'issue') return 1;
|
|
||||||
else return 0 // missing
|
|
||||||
}
|
|
||||||
|
|
||||||
function sortDataRecordsByDate(records: DataRecord[]) {
|
|
||||||
return records.sort((a, b) => {
|
|
||||||
if (typeof a.date === 'string' && typeof b.date === 'string') {
|
|
||||||
return a.date.localeCompare(b.date);
|
|
||||||
} else {
|
|
||||||
// Handle comparison when date is not a string (e.g., when it's a number)
|
|
||||||
// For instance, you might want to convert numbers to strings or use a different comparison logic.
|
|
||||||
// Example assuming number to string conversion:
|
|
||||||
return String(a.date).localeCompare(String(b.date));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default async function Page({ params: { slug } }: IPageProps) {
|
|
||||||
const vtuber = await getVtuberBySlug(slug);
|
|
||||||
if (!vtuber) return <NotFound></NotFound>
|
|
||||||
const streams = await getAllStreamsForVtuber(vtuber.id);
|
|
||||||
const streamsByYear: { [year: string]: DataRecord[] } = {};
|
|
||||||
streams.forEach((stream) => {
|
|
||||||
const date = new Date(stream.attributes.date);
|
|
||||||
const year = date.getFullYear();
|
|
||||||
if (!streamsByYear[year]) {
|
|
||||||
streamsByYear[year] = [];
|
|
||||||
}
|
|
||||||
streamsByYear[year].push({
|
|
||||||
date: new Date(stream.attributes.date).toISOString(),
|
|
||||||
value: stream.attributes.archiveStatus,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// Sort the data records within each year's array
|
|
||||||
for (const year in streamsByYear) {
|
|
||||||
streamsByYear[year] = sortDataRecordsByDate(streamsByYear[year]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{Object.keys(streamsByYear).map((year) => {
|
|
||||||
return (
|
|
||||||
<div key={year} className='section'>
|
|
||||||
<h2 className='title'>{year}</h2>
|
|
||||||
{/* <pre><code>{JSON.stringify(streamsByYear[year], null, 2)}</code></pre> */}
|
|
||||||
<Cal slug={slug} data={streamsByYear[year]} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
@ -30,217 +30,218 @@ export default async function Page({ params }: { params: { slug: string } }) {
|
|||||||
const vods = await getVodsForVtuber(vtuber.id, 1, 9);
|
const vods = await getVodsForVtuber(vtuber.id, 1, 9);
|
||||||
if (!vods) notFound();
|
if (!vods) notFound();
|
||||||
|
|
||||||
const missingStreams = await getAllStreamsForVtuber(vtuber.id, ['missing']);
|
return '@todo'
|
||||||
const issueStreams = await getAllStreamsForVtuber(vtuber.id, ['issue']);
|
// const missingStreams = await getAllStreamsForVtuber(vtuber.id, ['missing']);
|
||||||
const goodStreams = await getAllStreamsForVtuber(vtuber.id, ['good']);
|
// const issueStreams = await getAllStreamsForVtuber(vtuber.id, ['issue']);
|
||||||
|
// const goodStreams = await getAllStreamsForVtuber(vtuber.id, ['good']);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// // return (
|
||||||
|
// // <>
|
||||||
|
// // <p>hi mom!</p>
|
||||||
|
// // <pre>
|
||||||
|
// // <code>
|
||||||
|
// // {JSON.stringify(missingStreams, null, 2)}
|
||||||
|
// // </code>
|
||||||
|
// // </pre>
|
||||||
|
// // </>
|
||||||
|
// // )
|
||||||
|
|
||||||
// return (
|
// return (
|
||||||
// <>
|
// <>
|
||||||
// <p>hi mom!</p>
|
// {vtuber && (
|
||||||
// <pre>
|
// <>
|
||||||
// <code>
|
// <div className="box">
|
||||||
// {JSON.stringify(missingStreams, null, 2)}
|
|
||||||
// </code>
|
// <div className="columns is-multiline">
|
||||||
// </pre>
|
// <div className="column is-full">
|
||||||
|
// <h1 className="title is-2">{vtuber.attributes.displayName}</h1>
|
||||||
|
// </div>
|
||||||
|
// <div className="column is-one-quarter">
|
||||||
|
// <figure className="image is-rounded is-1by1">
|
||||||
|
// <Image
|
||||||
|
// className="is-rounded"
|
||||||
|
// alt={vtuber.attributes.displayName}
|
||||||
|
// src={vtuber.attributes.image}
|
||||||
|
// fill={true}
|
||||||
|
// placeholder='blur'
|
||||||
|
// blurDataURL={vtuber.attributes.imageBlur}
|
||||||
|
// />
|
||||||
|
// </figure>
|
||||||
|
// </div>
|
||||||
|
// <div className="column is-three-quarters">
|
||||||
|
// <p className="is-size-5 mb-3">{vtuber.attributes.description1}</p>
|
||||||
|
// <p className="is-size-5">{vtuber.attributes.description2}</p>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// <h2 id="socials" className="title is-3">
|
||||||
|
// <Link href="#socials">Socials</Link>
|
||||||
|
// </h2>
|
||||||
|
|
||||||
|
|
||||||
|
// <div className="column is-full mb-5">
|
||||||
|
// <div className="columns has-text-centered is-multiline is-centered">
|
||||||
|
// {vtuber.attributes.patreon && (
|
||||||
|
// <div className="column is-3 is-narrow">
|
||||||
|
// <Link href={vtuber.attributes.patreon} className="subtitle is-5">
|
||||||
|
// <span className="mr-2"><FontAwesomeIcon icon={faPatreon} className="fab fa-patreon" /></span><span className="mr-2">Patreon</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
||||||
|
// </Link>
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
// {vtuber.attributes.twitter && (
|
||||||
|
// <div className="column is-3 is-narrow">
|
||||||
|
// <Link target="_blank" href={vtuber.attributes.twitter} className="subtitle is-5">
|
||||||
|
// <span className="mr-2"><FontAwesomeIcon icon={faXTwitter} className="fab fa-x-twitter" /></span><span className="mr-2">Twitter</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
||||||
|
// </Link>
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
// {vtuber.attributes.youtube && (
|
||||||
|
// <div className="column is-3 is-narrow">
|
||||||
|
// <Link target="_blank" href={vtuber.attributes.youtube} className="subtitle is-5">
|
||||||
|
// <span className="mr-2"><FontAwesomeIcon icon={faYoutube} className="fab fa-youtube" /></span><span className="mr-2">YouTube</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
||||||
|
// </Link>
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
// {vtuber.attributes.twitch && (
|
||||||
|
// <div className="column is-3 is-narrow">
|
||||||
|
// <Link target="_blank" href={vtuber.attributes.twitch} className="subtitle is-5">
|
||||||
|
// <span className="mr-2"><FontAwesomeIcon icon={faTwitch} className="fab fa-twitch" /></span><span className="mr-2">Twitch</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
||||||
|
// </Link>
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
// {vtuber.attributes.tiktok && (
|
||||||
|
// <div className="column is-3 is-narrow">
|
||||||
|
// <Link target="_blank" href={vtuber.attributes.tiktok} className="subtitle is-5">
|
||||||
|
// <span className="mr-2"><FontAwesomeIcon icon={faTiktok} className="fab fa-tiktok" /></span><span className="mr-2">TikTok</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
||||||
|
// </Link>
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
// {vtuber.attributes.fansly && (
|
||||||
|
// <div className="column is-3 is-narrow">
|
||||||
|
// <Link target="_blank" href={vtuber.attributes.fansly} className='subtitle is-5'>
|
||||||
|
// <span className='mr-2'><FanslyIcon width={20} height={20} className={styles.icon}/></span><span className="mr-2">Fansly</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
||||||
|
// </Link>
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
// {vtuber.attributes.onlyfans && (
|
||||||
|
// <div className="column is-3 is-narrow">
|
||||||
|
// <Link target="_blank" href={vtuber.attributes.onlyfans} className='subtitle is-5'>
|
||||||
|
// <span className='mr-2'>
|
||||||
|
// <OnlyfansIcon width={20} height={20} className={styles.icon}></OnlyfansIcon>
|
||||||
|
// </span><span className="mr-2">OnlyFans</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
||||||
|
// </Link>
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
// {vtuber.attributes.pornhub && (
|
||||||
|
// <div className="column is-3 is-narrow">
|
||||||
|
// <Link target="_blank" href={vtuber.attributes.pornhub} className='subtitle is-5'>
|
||||||
|
// <span className='mr-2'><PornhubIcon width={20} height={20} className={styles.icon}/></span><span className="mr-2">Pornhub</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
||||||
|
// </Link>
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
// {vtuber.attributes.reddit && (
|
||||||
|
// <div className="column is-3 is-narrow">
|
||||||
|
// <Link target="_blank" href={vtuber.attributes.reddit} className="subtitle is-5">
|
||||||
|
// <span className="mr-2"><FontAwesomeIcon icon={faReddit} className="fab fa-reddit" /></span><span className="mr-2">Reddit</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
||||||
|
// </Link>
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
// {vtuber.attributes.discord && (
|
||||||
|
// <div className="column is-3 is-narrow">
|
||||||
|
// <Link target="_blank" href={vtuber.attributes.discord} className="subtitle is-5">
|
||||||
|
// <span className="mr-2"><FontAwesomeIcon icon={faDiscord} className="fab fa-discord" /></span><span className="mr-2">Discord</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
||||||
|
// </Link>
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
// {vtuber.attributes.instagram && (
|
||||||
|
// <div className="column is-3 is-narrow">
|
||||||
|
// <Link target="_blank" href={vtuber.attributes.instagram} className="subtitle is-5">
|
||||||
|
// <span className="mr-2"><FontAwesomeIcon icon={faInstagram} className="fab fa-instagram" /></span><span className="mr-2">Instagram</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
||||||
|
// </Link>
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
// {vtuber.attributes.facebook && (
|
||||||
|
// <div className="column is-3 is-narrow">
|
||||||
|
// <Link target="_blank" href={vtuber.attributes.facebook} className="subtitle is-5">
|
||||||
|
// <span className="mr-2"><FontAwesomeIcon icon={faFacebook} className="fab fa-facebook" /></span><span className="mr-2">Facebook</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
||||||
|
// </Link>
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
// {vtuber.attributes.merch && (
|
||||||
|
// <div className="column is-3 is-narrow">
|
||||||
|
// <Link target="_blank" href={vtuber.attributes.merch} className="subtitle is-5">
|
||||||
|
// <span className="mr-2"><FontAwesomeIcon icon={faBagShopping} className="fas fa-bag-shopping" /></span><span className="mr-2">Merch</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
||||||
|
// </Link>
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
// {vtuber.attributes.chaturbate && (
|
||||||
|
// <div className="column is-3 is-narrow">
|
||||||
|
// <Link target="_blank" href={vtuber.attributes.chaturbate} className='subtitle is-5'>
|
||||||
|
// <span className='mr-2'><ChaturbateIcon width={20} className={styles.icon}/></span><span className="mr-2">Chaturbate</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
||||||
|
// </Link>
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
// {vtuber.attributes.throne && (
|
||||||
|
// <div className="column is-3 is-narrow">
|
||||||
|
// <Link target="_blank" href={vtuber.attributes.throne} className='subtitle is-5'>
|
||||||
|
// <span className='mr-2'><ThroneIcon width={20} height={20} className={styles.icon}/></span><span className="mr-2">Throne</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
||||||
|
// </Link>
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
// {vtuber.attributes.linktree && (
|
||||||
|
// <div className="column is-3 is-narrow">
|
||||||
|
// <Link target="_blank" href={vtuber.attributes.linktree} className='subtitle is-5'>
|
||||||
|
// <span className='mr-2'><LinktreeIcon width={20} height={20} className={styles.icon}/></span><span className="mr-2">Linktree</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
||||||
|
// </Link>
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
// {vtuber.attributes.carrd && (
|
||||||
|
// <div className="column is-3 is-narrow">
|
||||||
|
// <Link target="_blank" href={vtuber.attributes.carrd} className='subtitle is-5'>
|
||||||
|
// <span className='mr-2'><CarrdIcon width={20} height={20} className={styles.icon}/></span><span className="mr-2">Carrd</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
||||||
|
// </Link>
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
|
||||||
|
// {/* <h2 id="toys" className="title is-3">
|
||||||
|
// <Link href="#toys">Toys</Link>
|
||||||
|
// </h2>
|
||||||
|
|
||||||
|
// <>
|
||||||
|
// <ToysList vtuber={vtuber} toys={toys} page={1} pageSize={toySampleCount} />
|
||||||
|
// {(toys.pagination.total > toySampleCount) && <Link href={`/vt/${vtuber.slug}/toys/1`} className='button mb-5'>See all of {vtuber.displayName}'s toys</Link>}
|
||||||
|
// </> */}
|
||||||
|
|
||||||
|
// <h2 id="vods" className="title is-3">
|
||||||
|
// <Link href="#vods">Vods</Link>
|
||||||
|
// </h2>
|
||||||
|
|
||||||
|
// <VodsList vtuber={vtuber} vods={vods.data} page={1} pageSize={9} />
|
||||||
|
// {
|
||||||
|
// (vtuber.attributes.vods) ? (
|
||||||
|
// <Link className='button mb-5' href={`/vt/${vtuber.attributes.slug}/vods/1`}>See all {vtuber.attributes.displayName} vods</Link>
|
||||||
|
// ) : (<p className='section'><i>No VODs have been added, yet.</i></p>)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// <h2 id="streams" className='title is-3'>
|
||||||
|
// <Link href="#streams">Streams</Link>
|
||||||
|
// </h2>
|
||||||
|
// <StreamsCalendar missingStreams={missingStreams} issueStreams={issueStreams} goodStreams={goodStreams} />
|
||||||
|
// {/*
|
||||||
|
// <h2 id="progress" className='title is-3'>
|
||||||
|
// <Link href="#progress">Archive Progress</Link>
|
||||||
|
// </h2>
|
||||||
|
// <ArchiveProgress vtuber={vtuber} /> */}
|
||||||
|
|
||||||
|
|
||||||
|
// </div>
|
||||||
// </>
|
// </>
|
||||||
// )
|
// )}
|
||||||
|
// </>
|
||||||
return (
|
// );
|
||||||
<>
|
|
||||||
{vtuber && (
|
|
||||||
<>
|
|
||||||
<div className="box">
|
|
||||||
|
|
||||||
<div className="columns is-multiline">
|
|
||||||
<div className="column is-full">
|
|
||||||
<h1 className="title is-2">{vtuber.attributes.displayName}</h1>
|
|
||||||
</div>
|
|
||||||
<div className="column is-one-quarter">
|
|
||||||
<figure className="image is-rounded is-1by1">
|
|
||||||
<Image
|
|
||||||
className="is-rounded"
|
|
||||||
alt={vtuber.attributes.displayName}
|
|
||||||
src={vtuber.attributes.image}
|
|
||||||
fill={true}
|
|
||||||
placeholder='blur'
|
|
||||||
blurDataURL={vtuber.attributes.imageBlur}
|
|
||||||
/>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div className="column is-three-quarters">
|
|
||||||
<p className="is-size-5 mb-3">{vtuber.attributes.description1}</p>
|
|
||||||
<p className="is-size-5">{vtuber.attributes.description2}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2 id="socials" className="title is-3">
|
|
||||||
<Link href="#socials">Socials</Link>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
|
|
||||||
<div className="column is-full mb-5">
|
|
||||||
<div className="columns has-text-centered is-multiline is-centered">
|
|
||||||
{vtuber.attributes.patreon && (
|
|
||||||
<div className="column is-3 is-narrow">
|
|
||||||
<Link href={vtuber.attributes.patreon} className="subtitle is-5">
|
|
||||||
<span className="mr-2"><FontAwesomeIcon icon={faPatreon} className="fab fa-patreon" /></span><span className="mr-2">Patreon</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{vtuber.attributes.twitter && (
|
|
||||||
<div className="column is-3 is-narrow">
|
|
||||||
<Link target="_blank" href={vtuber.attributes.twitter} className="subtitle is-5">
|
|
||||||
<span className="mr-2"><FontAwesomeIcon icon={faXTwitter} className="fab fa-x-twitter" /></span><span className="mr-2">Twitter</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{vtuber.attributes.youtube && (
|
|
||||||
<div className="column is-3 is-narrow">
|
|
||||||
<Link target="_blank" href={vtuber.attributes.youtube} className="subtitle is-5">
|
|
||||||
<span className="mr-2"><FontAwesomeIcon icon={faYoutube} className="fab fa-youtube" /></span><span className="mr-2">YouTube</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{vtuber.attributes.twitch && (
|
|
||||||
<div className="column is-3 is-narrow">
|
|
||||||
<Link target="_blank" href={vtuber.attributes.twitch} className="subtitle is-5">
|
|
||||||
<span className="mr-2"><FontAwesomeIcon icon={faTwitch} className="fab fa-twitch" /></span><span className="mr-2">Twitch</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{vtuber.attributes.tiktok && (
|
|
||||||
<div className="column is-3 is-narrow">
|
|
||||||
<Link target="_blank" href={vtuber.attributes.tiktok} className="subtitle is-5">
|
|
||||||
<span className="mr-2"><FontAwesomeIcon icon={faTiktok} className="fab fa-tiktok" /></span><span className="mr-2">TikTok</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{vtuber.attributes.fansly && (
|
|
||||||
<div className="column is-3 is-narrow">
|
|
||||||
<Link target="_blank" href={vtuber.attributes.fansly} className='subtitle is-5'>
|
|
||||||
<span className='mr-2'><FanslyIcon width={20} height={20} className={styles.icon}/></span><span className="mr-2">Fansly</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{vtuber.attributes.onlyfans && (
|
|
||||||
<div className="column is-3 is-narrow">
|
|
||||||
<Link target="_blank" href={vtuber.attributes.onlyfans} className='subtitle is-5'>
|
|
||||||
<span className='mr-2'>
|
|
||||||
<OnlyfansIcon width={20} height={20} className={styles.icon}></OnlyfansIcon>
|
|
||||||
</span><span className="mr-2">OnlyFans</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{vtuber.attributes.pornhub && (
|
|
||||||
<div className="column is-3 is-narrow">
|
|
||||||
<Link target="_blank" href={vtuber.attributes.pornhub} className='subtitle is-5'>
|
|
||||||
<span className='mr-2'><PornhubIcon width={20} height={20} className={styles.icon}/></span><span className="mr-2">Pornhub</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{vtuber.attributes.reddit && (
|
|
||||||
<div className="column is-3 is-narrow">
|
|
||||||
<Link target="_blank" href={vtuber.attributes.reddit} className="subtitle is-5">
|
|
||||||
<span className="mr-2"><FontAwesomeIcon icon={faReddit} className="fab fa-reddit" /></span><span className="mr-2">Reddit</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{vtuber.attributes.discord && (
|
|
||||||
<div className="column is-3 is-narrow">
|
|
||||||
<Link target="_blank" href={vtuber.attributes.discord} className="subtitle is-5">
|
|
||||||
<span className="mr-2"><FontAwesomeIcon icon={faDiscord} className="fab fa-discord" /></span><span className="mr-2">Discord</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{vtuber.attributes.instagram && (
|
|
||||||
<div className="column is-3 is-narrow">
|
|
||||||
<Link target="_blank" href={vtuber.attributes.instagram} className="subtitle is-5">
|
|
||||||
<span className="mr-2"><FontAwesomeIcon icon={faInstagram} className="fab fa-instagram" /></span><span className="mr-2">Instagram</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{vtuber.attributes.facebook && (
|
|
||||||
<div className="column is-3 is-narrow">
|
|
||||||
<Link target="_blank" href={vtuber.attributes.facebook} className="subtitle is-5">
|
|
||||||
<span className="mr-2"><FontAwesomeIcon icon={faFacebook} className="fab fa-facebook" /></span><span className="mr-2">Facebook</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{vtuber.attributes.merch && (
|
|
||||||
<div className="column is-3 is-narrow">
|
|
||||||
<Link target="_blank" href={vtuber.attributes.merch} className="subtitle is-5">
|
|
||||||
<span className="mr-2"><FontAwesomeIcon icon={faBagShopping} className="fas fa-bag-shopping" /></span><span className="mr-2">Merch</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{vtuber.attributes.chaturbate && (
|
|
||||||
<div className="column is-3 is-narrow">
|
|
||||||
<Link target="_blank" href={vtuber.attributes.chaturbate} className='subtitle is-5'>
|
|
||||||
<span className='mr-2'><ChaturbateIcon width={20} className={styles.icon}/></span><span className="mr-2">Chaturbate</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{vtuber.attributes.throne && (
|
|
||||||
<div className="column is-3 is-narrow">
|
|
||||||
<Link target="_blank" href={vtuber.attributes.throne} className='subtitle is-5'>
|
|
||||||
<span className='mr-2'><ThroneIcon width={20} height={20} className={styles.icon}/></span><span className="mr-2">Throne</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{vtuber.attributes.linktree && (
|
|
||||||
<div className="column is-3 is-narrow">
|
|
||||||
<Link target="_blank" href={vtuber.attributes.linktree} className='subtitle is-5'>
|
|
||||||
<span className='mr-2'><LinktreeIcon width={20} height={20} className={styles.icon}/></span><span className="mr-2">Linktree</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{vtuber.attributes.carrd && (
|
|
||||||
<div className="column is-3 is-narrow">
|
|
||||||
<Link target="_blank" href={vtuber.attributes.carrd} className='subtitle is-5'>
|
|
||||||
<span className='mr-2'><CarrdIcon width={20} height={20} className={styles.icon}/></span><span className="mr-2">Carrd</span><span><FontAwesomeIcon icon={faExternalLinkAlt} className="fas fa-external-link-alt" /></span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{/* <h2 id="toys" className="title is-3">
|
|
||||||
<Link href="#toys">Toys</Link>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<>
|
|
||||||
<ToysList vtuber={vtuber} toys={toys} page={1} pageSize={toySampleCount} />
|
|
||||||
{(toys.pagination.total > toySampleCount) && <Link href={`/vt/${vtuber.slug}/toys/1`} className='button mb-5'>See all of {vtuber.displayName}'s toys</Link>}
|
|
||||||
</> */}
|
|
||||||
|
|
||||||
<h2 id="vods" className="title is-3">
|
|
||||||
<Link href="#vods">Vods</Link>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<VodsList vtuber={vtuber} vods={vods.data} page={1} pageSize={9} />
|
|
||||||
{
|
|
||||||
(vtuber.attributes.vods) ? (
|
|
||||||
<Link className='button mb-5' href={`/vt/${vtuber.attributes.slug}/vods/1`}>See all {vtuber.attributes.displayName} vods</Link>
|
|
||||||
) : (<p className='section'><i>No VODs have been added, yet.</i></p>)
|
|
||||||
}
|
|
||||||
|
|
||||||
<h2 id="streams" className='title is-3'>
|
|
||||||
<Link href="#streams">Streams</Link>
|
|
||||||
</h2>
|
|
||||||
<StreamsCalendar missingStreams={missingStreams} issueStreams={issueStreams} goodStreams={goodStreams} />
|
|
||||||
{/*
|
|
||||||
<h2 id="progress" className='title is-3'>
|
|
||||||
<Link href="#progress">Archive Progress</Link>
|
|
||||||
</h2>
|
|
||||||
<ArchiveProgress vtuber={vtuber} /> */}
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
const SvgComponent = (props) => (
|
const SvgComponent = (props: any) => (
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
xmlSpace="preserve"
|
xmlSpace="preserve"
|
||||||
|
@ -43,7 +43,6 @@
|
|||||||
"@uppy/remote-sources": "^1.1.0",
|
"@uppy/remote-sources": "^1.1.0",
|
||||||
"bulma": "^0.9.4",
|
"bulma": "^0.9.4",
|
||||||
"bulma-prefers-dark": "0.1.0-beta.1",
|
"bulma-prefers-dark": "0.1.0-beta.1",
|
||||||
"cal-heatmap": "^4.2.4",
|
|
||||||
"date-fns": "^2.0.0",
|
"date-fns": "^2.0.0",
|
||||||
"date-fns-tz": "^2.0.0",
|
"date-fns-tz": "^2.0.0",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
|
@ -41,8 +41,7 @@
|
|||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
"dist/types/**/*.ts",
|
"dist/types/**/*.ts",
|
||||||
".next/types/**/*.ts",
|
".next/types/**/*.ts"
|
||||||
"assets/svg/tes.jsx"
|
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
|
373
pnpm-lock.yaml
generated
373
pnpm-lock.yaml
generated
@ -28,28 +28,6 @@ importers:
|
|||||||
specifier: ^4.7.0
|
specifier: ^4.7.0
|
||||||
version: 4.7.0
|
version: 4.7.0
|
||||||
|
|
||||||
packages/futurebot:
|
|
||||||
dependencies:
|
|
||||||
'@types/express':
|
|
||||||
specifier: ^4.17.21
|
|
||||||
version: 4.17.21
|
|
||||||
'@types/node':
|
|
||||||
specifier: ^20.11.0
|
|
||||||
version: 20.11.5
|
|
||||||
discordeno:
|
|
||||||
specifier: ^18.0.1
|
|
||||||
version: 18.0.1
|
|
||||||
dotenv:
|
|
||||||
specifier: ^16.3.1
|
|
||||||
version: 16.3.1
|
|
||||||
express:
|
|
||||||
specifier: ^4.18.2
|
|
||||||
version: 4.18.2
|
|
||||||
devDependencies:
|
|
||||||
tsx:
|
|
||||||
specifier: ^4.7.0
|
|
||||||
version: 4.7.0
|
|
||||||
|
|
||||||
packages/next:
|
packages/next:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@fortawesome/fontawesome-free':
|
'@fortawesome/fontawesome-free':
|
||||||
@ -151,9 +129,6 @@ importers:
|
|||||||
bulma-prefers-dark:
|
bulma-prefers-dark:
|
||||||
specifier: 0.1.0-beta.1
|
specifier: 0.1.0-beta.1
|
||||||
version: 0.1.0-beta.1
|
version: 0.1.0-beta.1
|
||||||
cal-heatmap:
|
|
||||||
specifier: ^4.2.4
|
|
||||||
version: 4.2.4
|
|
||||||
date-fns:
|
date-fns:
|
||||||
specifier: ^2.0.0
|
specifier: ^2.0.0
|
||||||
version: 2.30.0
|
version: 2.30.0
|
||||||
@ -1211,7 +1186,7 @@ packages:
|
|||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv: 6.12.6
|
ajv: 6.12.6
|
||||||
debug: 4.3.4(supports-color@5.5.0)
|
debug: 4.3.4
|
||||||
espree: 9.6.1
|
espree: 9.6.1
|
||||||
globals: 13.24.0
|
globals: 13.24.0
|
||||||
ignore: 5.3.0
|
ignore: 5.3.0
|
||||||
@ -1348,7 +1323,7 @@ packages:
|
|||||||
engines: {node: '>=10.10.0'}
|
engines: {node: '>=10.10.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@humanwhocodes/object-schema': 2.0.2
|
'@humanwhocodes/object-schema': 2.0.2
|
||||||
debug: 4.3.4(supports-color@5.5.0)
|
debug: 4.3.4
|
||||||
minimatch: 3.1.2
|
minimatch: 3.1.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@ -1721,25 +1696,12 @@ packages:
|
|||||||
fastq: 1.16.0
|
fastq: 1.16.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@observablehq/plot@0.6.13:
|
|
||||||
resolution: {integrity: sha512-ebQS4ENodOy+O3WUjhqv9jNPZENAZRQMIdO3ziOlAKfUzSf69+gaFAqqc04SGrQA6JwJjPYnbfeN3YIpNsCF/A==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
d3: 7.8.5
|
|
||||||
interval-tree-1d: 1.0.4
|
|
||||||
isoformat: 0.2.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@paralleldrive/cuid2@2.2.2:
|
/@paralleldrive/cuid2@2.2.2:
|
||||||
resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==}
|
resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@noble/hashes': 1.3.3
|
'@noble/hashes': 1.3.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@popperjs/core@2.11.8:
|
|
||||||
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@react-hookz/deep-equal@1.0.4:
|
/@react-hookz/deep-equal@1.0.4:
|
||||||
resolution: {integrity: sha512-N56fTrAPUDz/R423pag+n6TXWbvlBZDtTehaGFjK0InmN+V2OFWLE/WmORhmn6Ce7dlwH5+tQN1LJFw3ngTJVg==}
|
resolution: {integrity: sha512-N56fTrAPUDz/R423pag+n6TXWbvlBZDtTehaGFjK0InmN+V2OFWLE/WmORhmn6Ce7dlwH5+tQN1LJFw3ngTJVg==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -2478,7 +2440,7 @@ packages:
|
|||||||
'@typescript-eslint/types': 6.18.1
|
'@typescript-eslint/types': 6.18.1
|
||||||
'@typescript-eslint/typescript-estree': 6.18.1(typescript@5.3.3)
|
'@typescript-eslint/typescript-estree': 6.18.1(typescript@5.3.3)
|
||||||
'@typescript-eslint/visitor-keys': 6.18.1
|
'@typescript-eslint/visitor-keys': 6.18.1
|
||||||
debug: 4.3.4(supports-color@5.5.0)
|
debug: 4.3.4
|
||||||
eslint: 8.56.0
|
eslint: 8.56.0
|
||||||
typescript: 5.3.3
|
typescript: 5.3.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@ -2509,7 +2471,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 6.18.1
|
'@typescript-eslint/types': 6.18.1
|
||||||
'@typescript-eslint/visitor-keys': 6.18.1
|
'@typescript-eslint/visitor-keys': 6.18.1
|
||||||
debug: 4.3.4(supports-color@5.5.0)
|
debug: 4.3.4
|
||||||
globby: 11.1.0
|
globby: 11.1.0
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
minimatch: 9.0.3
|
minimatch: 9.0.3
|
||||||
@ -3159,10 +3121,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
|
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
/binary-search-bounds@2.0.5:
|
|
||||||
resolution: {integrity: sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/bintrees@1.0.2:
|
/bintrees@1.0.2:
|
||||||
resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==}
|
resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -3334,20 +3292,6 @@ packages:
|
|||||||
responselike: 2.0.1
|
responselike: 2.0.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/cal-heatmap@4.2.4:
|
|
||||||
resolution: {integrity: sha512-TTNoQTRxHXrttOEbkraKv9vy2VpfQIwVLQJTlAfcBusQK9qrBL/UBO+WloAxv2yrR+P8URA2cuXEdc5iztER9g==}
|
|
||||||
dependencies:
|
|
||||||
'@observablehq/plot': 0.6.13
|
|
||||||
'@popperjs/core': 2.11.8
|
|
||||||
d3-color: 3.1.0
|
|
||||||
d3-fetch: 3.0.1
|
|
||||||
d3-selection: 3.0.0
|
|
||||||
d3-transition: 3.0.1(d3-selection@3.0.0)
|
|
||||||
dayjs: 1.11.10
|
|
||||||
eventemitter3: 5.0.1
|
|
||||||
lodash-es: 4.17.21
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/call-bind@1.0.5:
|
/call-bind@1.0.5:
|
||||||
resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==}
|
resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -3462,11 +3406,6 @@ packages:
|
|||||||
delayed-stream: 1.0.0
|
delayed-stream: 1.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/commander@7.2.0:
|
|
||||||
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
|
|
||||||
engines: {node: '>= 10'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/common-tags@1.8.2:
|
/common-tags@1.8.2:
|
||||||
resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==}
|
resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==}
|
||||||
engines: {node: '>=4.0.0'}
|
engines: {node: '>=4.0.0'}
|
||||||
@ -3577,254 +3516,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-tI+NjVRS485QlSxHeM3JIjdEZSJMLOZVp41/vvWukDmIkZSoYG9gLYl9GFZGBpY42UbRVo1eMlF7XkI/IiDHzQ==}
|
resolution: {integrity: sha512-tI+NjVRS485QlSxHeM3JIjdEZSJMLOZVp41/vvWukDmIkZSoYG9gLYl9GFZGBpY42UbRVo1eMlF7XkI/IiDHzQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/d3-array@3.2.4:
|
|
||||||
resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
internmap: 2.0.3
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-axis@3.0.0:
|
|
||||||
resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-brush@3.0.0:
|
|
||||||
resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
d3-dispatch: 3.0.1
|
|
||||||
d3-drag: 3.0.0
|
|
||||||
d3-interpolate: 3.0.1
|
|
||||||
d3-selection: 3.0.0
|
|
||||||
d3-transition: 3.0.1(d3-selection@3.0.0)
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-chord@3.0.1:
|
|
||||||
resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
d3-path: 3.1.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-color@3.1.0:
|
|
||||||
resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-contour@4.0.2:
|
|
||||||
resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
d3-array: 3.2.4
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-delaunay@6.0.4:
|
|
||||||
resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
delaunator: 5.0.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-dispatch@3.0.1:
|
|
||||||
resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-drag@3.0.0:
|
|
||||||
resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
d3-dispatch: 3.0.1
|
|
||||||
d3-selection: 3.0.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-dsv@3.0.1:
|
|
||||||
resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
hasBin: true
|
|
||||||
dependencies:
|
|
||||||
commander: 7.2.0
|
|
||||||
iconv-lite: 0.6.3
|
|
||||||
rw: 1.3.3
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-ease@3.0.1:
|
|
||||||
resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-fetch@3.0.1:
|
|
||||||
resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
d3-dsv: 3.0.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-force@3.0.0:
|
|
||||||
resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
d3-dispatch: 3.0.1
|
|
||||||
d3-quadtree: 3.0.1
|
|
||||||
d3-timer: 3.0.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-format@3.1.0:
|
|
||||||
resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-geo@3.1.0:
|
|
||||||
resolution: {integrity: sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
d3-array: 3.2.4
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-hierarchy@3.1.2:
|
|
||||||
resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-interpolate@3.0.1:
|
|
||||||
resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
d3-color: 3.1.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-path@3.1.0:
|
|
||||||
resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-polygon@3.0.1:
|
|
||||||
resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-quadtree@3.0.1:
|
|
||||||
resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-random@3.0.1:
|
|
||||||
resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-scale-chromatic@3.0.0:
|
|
||||||
resolution: {integrity: sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
d3-color: 3.1.0
|
|
||||||
d3-interpolate: 3.0.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-scale@4.0.2:
|
|
||||||
resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
d3-array: 3.2.4
|
|
||||||
d3-format: 3.1.0
|
|
||||||
d3-interpolate: 3.0.1
|
|
||||||
d3-time: 3.1.0
|
|
||||||
d3-time-format: 4.1.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-selection@3.0.0:
|
|
||||||
resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-shape@3.2.0:
|
|
||||||
resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
d3-path: 3.1.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-time-format@4.1.0:
|
|
||||||
resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
d3-time: 3.1.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-time@3.1.0:
|
|
||||||
resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
d3-array: 3.2.4
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-timer@3.0.1:
|
|
||||||
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-transition@3.0.1(d3-selection@3.0.0):
|
|
||||||
resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
peerDependencies:
|
|
||||||
d3-selection: 2 - 3
|
|
||||||
dependencies:
|
|
||||||
d3-color: 3.1.0
|
|
||||||
d3-dispatch: 3.0.1
|
|
||||||
d3-ease: 3.0.1
|
|
||||||
d3-interpolate: 3.0.1
|
|
||||||
d3-selection: 3.0.0
|
|
||||||
d3-timer: 3.0.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-zoom@3.0.0:
|
|
||||||
resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
d3-dispatch: 3.0.1
|
|
||||||
d3-drag: 3.0.0
|
|
||||||
d3-interpolate: 3.0.1
|
|
||||||
d3-selection: 3.0.0
|
|
||||||
d3-transition: 3.0.1(d3-selection@3.0.0)
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3@7.8.5:
|
|
||||||
resolution: {integrity: sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
d3-array: 3.2.4
|
|
||||||
d3-axis: 3.0.0
|
|
||||||
d3-brush: 3.0.0
|
|
||||||
d3-chord: 3.0.1
|
|
||||||
d3-color: 3.1.0
|
|
||||||
d3-contour: 4.0.2
|
|
||||||
d3-delaunay: 6.0.4
|
|
||||||
d3-dispatch: 3.0.1
|
|
||||||
d3-drag: 3.0.0
|
|
||||||
d3-dsv: 3.0.1
|
|
||||||
d3-ease: 3.0.1
|
|
||||||
d3-fetch: 3.0.1
|
|
||||||
d3-force: 3.0.0
|
|
||||||
d3-format: 3.1.0
|
|
||||||
d3-geo: 3.1.0
|
|
||||||
d3-hierarchy: 3.1.2
|
|
||||||
d3-interpolate: 3.0.1
|
|
||||||
d3-path: 3.1.0
|
|
||||||
d3-polygon: 3.0.1
|
|
||||||
d3-quadtree: 3.0.1
|
|
||||||
d3-random: 3.0.1
|
|
||||||
d3-scale: 4.0.2
|
|
||||||
d3-scale-chromatic: 3.0.0
|
|
||||||
d3-selection: 3.0.0
|
|
||||||
d3-shape: 3.2.0
|
|
||||||
d3-time: 3.1.0
|
|
||||||
d3-time-format: 4.1.0
|
|
||||||
d3-timer: 3.0.1
|
|
||||||
d3-transition: 3.0.1(d3-selection@3.0.0)
|
|
||||||
d3-zoom: 3.0.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/damerau-levenshtein@1.0.8:
|
/damerau-levenshtein@1.0.8:
|
||||||
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
|
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -3870,6 +3561,18 @@ packages:
|
|||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/debug@4.3.4:
|
||||||
|
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||||
|
engines: {node: '>=6.0'}
|
||||||
|
peerDependencies:
|
||||||
|
supports-color: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
supports-color:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
ms: 2.1.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
/debug@4.3.4(supports-color@5.5.0):
|
/debug@4.3.4(supports-color@5.5.0):
|
||||||
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||||
engines: {node: '>=6.0'}
|
engines: {node: '>=6.0'}
|
||||||
@ -3919,12 +3622,6 @@ packages:
|
|||||||
has-property-descriptors: 1.0.1
|
has-property-descriptors: 1.0.1
|
||||||
object-keys: 1.1.1
|
object-keys: 1.1.1
|
||||||
|
|
||||||
/delaunator@5.0.0:
|
|
||||||
resolution: {integrity: sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==}
|
|
||||||
dependencies:
|
|
||||||
robust-predicates: 3.0.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/delayed-stream@1.0.0:
|
/delayed-stream@1.0.0:
|
||||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
@ -4215,7 +3912,7 @@ packages:
|
|||||||
eslint: '*'
|
eslint: '*'
|
||||||
eslint-plugin-import: '*'
|
eslint-plugin-import: '*'
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.3.4(supports-color@5.5.0)
|
debug: 4.3.4
|
||||||
enhanced-resolve: 5.15.0
|
enhanced-resolve: 5.15.0
|
||||||
eslint: 8.56.0
|
eslint: 8.56.0
|
||||||
eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.18.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0)
|
eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.18.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0)
|
||||||
@ -4384,7 +4081,7 @@ packages:
|
|||||||
ajv: 6.12.6
|
ajv: 6.12.6
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
cross-spawn: 7.0.3
|
cross-spawn: 7.0.3
|
||||||
debug: 4.3.4(supports-color@5.5.0)
|
debug: 4.3.4
|
||||||
doctrine: 3.0.0
|
doctrine: 3.0.0
|
||||||
escape-string-regexp: 4.0.0
|
escape-string-regexp: 4.0.0
|
||||||
eslint-scope: 7.2.2
|
eslint-scope: 7.2.2
|
||||||
@ -5008,13 +4705,6 @@ packages:
|
|||||||
safer-buffer: 2.1.2
|
safer-buffer: 2.1.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/iconv-lite@0.6.3:
|
|
||||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
dependencies:
|
|
||||||
safer-buffer: 2.1.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/ieee754@1.2.1:
|
/ieee754@1.2.1:
|
||||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -5068,17 +4758,6 @@ packages:
|
|||||||
side-channel: 1.0.4
|
side-channel: 1.0.4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/internmap@2.0.3:
|
|
||||||
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/interval-tree-1d@1.0.4:
|
|
||||||
resolution: {integrity: sha512-wY8QJH+6wNI0uh4pDQzMvl+478Qh7Rl4qLmqiluxALlNvl+I+o5x38Pw3/z7mDPTPS1dQalZJXsmbvxx5gclhQ==}
|
|
||||||
dependencies:
|
|
||||||
binary-search-bounds: 2.0.5
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/ipaddr.js@1.9.1:
|
/ipaddr.js@1.9.1:
|
||||||
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
||||||
engines: {node: '>= 0.10'}
|
engines: {node: '>= 0.10'}
|
||||||
@ -5284,10 +4963,6 @@ packages:
|
|||||||
/isexe@2.0.0:
|
/isexe@2.0.0:
|
||||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||||
|
|
||||||
/isoformat@0.2.1:
|
|
||||||
resolution: {integrity: sha512-tFLRAygk9NqrRPhJSnNGh7g7oaVWDwR0wKh/GM2LgmPa50Eg4UfyaCO4I8k6EqJHl1/uh2RAD6g06n5ygEnrjQ==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/iterator.prototype@1.1.2:
|
/iterator.prototype@1.1.2:
|
||||||
resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==}
|
resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -5458,10 +5133,6 @@ packages:
|
|||||||
p-locate: 5.0.0
|
p-locate: 5.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/lodash-es@4.17.21:
|
|
||||||
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/lodash._baseiteratee@4.7.0:
|
/lodash._baseiteratee@4.7.0:
|
||||||
resolution: {integrity: sha512-nqB9M+wITz0BX/Q2xg6fQ8mLkyfF7MU7eE+MNBNjTHFKeKaZAPEzEg+E8LWxKWf1DQVflNEn9N49yAuqKh2mWQ==}
|
resolution: {integrity: sha512-nqB9M+wITz0BX/Q2xg6fQ8mLkyfF7MU7eE+MNBNjTHFKeKaZAPEzEg+E8LWxKWf1DQVflNEn9N49yAuqKh2mWQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -6517,20 +6188,12 @@ packages:
|
|||||||
glob: 7.2.3
|
glob: 7.2.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/robust-predicates@3.0.2:
|
|
||||||
resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/run-parallel@1.2.0:
|
/run-parallel@1.2.0:
|
||||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
queue-microtask: 1.2.3
|
queue-microtask: 1.2.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/rw@1.3.3:
|
|
||||||
resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/safe-array-concat@1.0.1:
|
/safe-array-concat@1.0.1:
|
||||||
resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==}
|
resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==}
|
||||||
engines: {node: '>=0.4'}
|
engines: {node: '>=0.4'}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user