✨ added embeds for posts :3
This commit is contained in:
parent
114c9f93d4
commit
12f5b1eb68
16 changed files with 523 additions and 6 deletions
43
.onedev-buildspec.yml
Normal file
43
.onedev-buildspec.yml
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
version: 31
|
||||||
|
jobs:
|
||||||
|
- name: Build
|
||||||
|
steps:
|
||||||
|
- !CheckoutStep
|
||||||
|
name: checkout
|
||||||
|
cloneCredential: !DefaultCredential {}
|
||||||
|
withLfs: false
|
||||||
|
withSubmodules: false
|
||||||
|
condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL
|
||||||
|
- !CommandStep
|
||||||
|
name: lint
|
||||||
|
runInContainer: true
|
||||||
|
image: node:21-alpine
|
||||||
|
interpreter: !DefaultInterpreter
|
||||||
|
commands: |
|
||||||
|
apk add --no-cache libc6-compat
|
||||||
|
apk update
|
||||||
|
|
||||||
|
yarn set version canary
|
||||||
|
yarn config set nodeLinker node-modules
|
||||||
|
|
||||||
|
yarn install
|
||||||
|
yarn run lint
|
||||||
|
useTTY: true
|
||||||
|
condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL
|
||||||
|
- !BuildImageStep
|
||||||
|
name: '[build]: docker'
|
||||||
|
dockerfile: ./Dockerfile
|
||||||
|
tags: '@server@/rose/FixBluesky:devel'
|
||||||
|
publish: true
|
||||||
|
builtInRegistryAccessTokenSecret: DOCKER_REGISTRY_TOKEN
|
||||||
|
removeDanglingImages: true
|
||||||
|
condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL
|
||||||
|
triggers:
|
||||||
|
- !BranchUpdateTrigger
|
||||||
|
branches: devel
|
||||||
|
paths: src/**
|
||||||
|
projects: rose/FixBluesky
|
||||||
|
retryCondition: never
|
||||||
|
maxRetries: 3
|
||||||
|
retryDelay: 30
|
||||||
|
timeout: 3600
|
||||||
29
Dockerfile
Normal file
29
Dockerfile
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
FROM node:21-alpine AS base
|
||||||
|
|
||||||
|
FROM base AS builder
|
||||||
|
RUN apk add --no-cache libc6-compat
|
||||||
|
RUN apk update
|
||||||
|
RUN yarn set version canary
|
||||||
|
RUN yarn config set nodeLinker node-modules
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY .gitignore .gitignore
|
||||||
|
COPY package.json ./
|
||||||
|
COPY yarn.lock ./
|
||||||
|
RUN yarn install
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN yarn build
|
||||||
|
|
||||||
|
FROM base AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Don't run production as root
|
||||||
|
RUN addgroup --system --gid 1001 fixbluesky
|
||||||
|
RUN adduser --system --uid 1001 fixbluesky
|
||||||
|
USER fixbluesky
|
||||||
|
COPY --from=builder /app .
|
||||||
|
|
||||||
|
CMD node ./dist/main.js
|
||||||
28
docker-compose.yml
Normal file
28
docker-compose.yml
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
services:
|
||||||
|
keydb:
|
||||||
|
container_name: keydb
|
||||||
|
image: eqalpha/keydb
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- fixbluesky
|
||||||
|
fixbluesky:
|
||||||
|
container_name: fixbluesky
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./Dockerfile
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 8787:8787
|
||||||
|
environment:
|
||||||
|
- REDIS_HOSTNAME=${REDIS_HOSTNAME:-keydb}
|
||||||
|
- BSKY_SERVICE_URL=${BSKY_SERVICE_URL}
|
||||||
|
- BSKY_AUTH_USERNAME=${BSKY_AUTH_USERNAME}
|
||||||
|
- BSKY_AUTH_PASSWORD=${BSKY_AUTH_PASSWORD}
|
||||||
|
- FIXBLUESKY_APP_DOMAIN=${FIXBLUESKY_APP_DOMAIN}
|
||||||
|
networks:
|
||||||
|
- fixbluesky
|
||||||
|
depends_on:
|
||||||
|
- keydb
|
||||||
|
networks:
|
||||||
|
fixbluesky:
|
||||||
|
name: fixbluesky
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
{
|
{
|
||||||
"name": "fixbluesky",
|
"name": "fixbluesky",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|
||||||
"packageManager": "yarn@4.1.1",
|
"packageManager": "yarn@4.1.1",
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "21.7.3",
|
"node": "21.7.3",
|
||||||
"yarn": "4.1.1"
|
"yarn": "4.1.1"
|
||||||
},
|
},
|
||||||
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/main.js",
|
"main": "./dist/main.js",
|
||||||
"module": "./dist/main.js",
|
"module": "./dist/main.js",
|
||||||
|
|
@ -17,13 +15,11 @@
|
||||||
"types": "./dist/main.d.ts"
|
"types": "./dist/main.d.ts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsx ./src/main.ts",
|
"dev": "tsx ./src/main.ts",
|
||||||
"lint": "biome ci ./src/**/*",
|
"lint": "biome ci ./src/**/*",
|
||||||
"build": "pkgroll"
|
"build": "pkgroll"
|
||||||
},
|
},
|
||||||
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "1.7.1",
|
"@biomejs/biome": "1.7.1",
|
||||||
"@types/node": "20.12.7",
|
"@types/node": "20.12.7",
|
||||||
|
|
@ -34,6 +30,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atproto/api": "0.12.5",
|
"@atproto/api": "0.12.5",
|
||||||
"@hono/node-server": "1.11.0",
|
"@hono/node-server": "1.11.0",
|
||||||
"hono": "4.2.7"
|
"hono": "4.2.7",
|
||||||
|
"ioredis": "5.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
27
src/components/Layout.tsx
Normal file
27
src/components/Layout.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { html } from "hono/html";
|
||||||
|
|
||||||
|
export interface LayoutProps {
|
||||||
|
url: string;
|
||||||
|
children: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Layout = ({ url, children }: LayoutProps) => {
|
||||||
|
const removeLeadingSlash = url.substring(1);
|
||||||
|
const redirectUrl = removeLeadingSlash.startsWith("https://")
|
||||||
|
? removeLeadingSlash
|
||||||
|
: `https://bsky.app/${removeLeadingSlash}`;
|
||||||
|
return html`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="canonical" href="${url.substring(1)}" />
|
||||||
|
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
|
||||||
|
<meta content="#0085ff" name="theme-color" />
|
||||||
|
<meta property="og:site_name" content="FixBluesky" />
|
||||||
|
|
||||||
|
${children}
|
||||||
|
<meta http-equiv="refresh" content="0;url=${redirectUrl}" />
|
||||||
|
</head>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
};
|
||||||
52
src/components/Post.tsx
Normal file
52
src/components/Post.tsx
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
import type { AppBskyFeedDefs } from "@atproto/api";
|
||||||
|
|
||||||
|
import { parseEmbedDescription } from "../lib/parseEmbedDescription.ts";
|
||||||
|
import { parseEmbedImage } from "../lib/parseEmbedImage.ts";
|
||||||
|
import { OEmbedTypes } from "../routes/getOEmbed.ts";
|
||||||
|
import { Layout } from "./Layout.tsx";
|
||||||
|
|
||||||
|
interface PostProps {
|
||||||
|
post: AppBskyFeedDefs.PostView;
|
||||||
|
url: string;
|
||||||
|
appDomain: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Post = ({ post, url, appDomain }: PostProps) => {
|
||||||
|
const image = parseEmbedImage(post);
|
||||||
|
return (
|
||||||
|
<Layout url={url}>
|
||||||
|
<meta name="twitter:creator" content={`@${post.author.handle}`} />
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content={parseEmbedDescription(post)}
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:title"
|
||||||
|
content={`${post.author.displayName} (@${post.author.handle})`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{!(image === post.author.avatar) && (
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{Array.isArray(image) ? (
|
||||||
|
image.map((i) => (
|
||||||
|
<meta property="og:image" content={i} key={i} />
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<meta property="og:image" content={image} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<link
|
||||||
|
type="application/json+oembed"
|
||||||
|
href={`https:/${appDomain}/oembed?type=${
|
||||||
|
OEmbedTypes.Post
|
||||||
|
}&replies=${post.replyCount}&reposts=${
|
||||||
|
post.repostCount
|
||||||
|
}&likes=${post.likeCount}&avatar=${encodeURIComponent(
|
||||||
|
post.author.avatar ?? "",
|
||||||
|
)}`}
|
||||||
|
/>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
};
|
||||||
9
src/globals.d.ts
vendored
Normal file
9
src/globals.d.ts
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import type { BskyAgent } from "@atproto/api";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HonoEnv {
|
||||||
|
Variables: {
|
||||||
|
Agent: BskyAgent;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/lib/fetchPostData.ts
Normal file
18
src/lib/fetchPostData.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import type { BskyAgent } from "@atproto/api";
|
||||||
|
|
||||||
|
export interface fetchPostOptions {
|
||||||
|
user: string;
|
||||||
|
post: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchPost(
|
||||||
|
agent: BskyAgent,
|
||||||
|
{ user, post }: fetchPostOptions,
|
||||||
|
) {
|
||||||
|
const { data: userData } = await agent.getProfile({
|
||||||
|
actor: user,
|
||||||
|
});
|
||||||
|
return agent.getPosts({
|
||||||
|
uris: [`at://${userData.did}/app.bsky.feed.post/${post}`],
|
||||||
|
});
|
||||||
|
}
|
||||||
46
src/lib/parseEmbedDescription.ts
Normal file
46
src/lib/parseEmbedDescription.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import {
|
||||||
|
AppBskyEmbedRecord,
|
||||||
|
AppBskyEmbedRecordWithMedia,
|
||||||
|
AppBskyFeedPost,
|
||||||
|
type AppBskyFeedDefs,
|
||||||
|
} from "@atproto/api";
|
||||||
|
|
||||||
|
export function parseEmbedDescription(post: AppBskyFeedDefs.PostView) {
|
||||||
|
if (AppBskyFeedPost.isRecord(post.record)) {
|
||||||
|
if (AppBskyEmbedRecord.isView(post.embed)) {
|
||||||
|
const { success: isView } = AppBskyEmbedRecord.validateView(
|
||||||
|
post.embed,
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
isView &&
|
||||||
|
AppBskyEmbedRecord.isViewRecord(post.embed.record)
|
||||||
|
) {
|
||||||
|
const { success: isViewRecord } =
|
||||||
|
AppBskyEmbedRecord.validateViewRecord(post.embed.record);
|
||||||
|
if (isViewRecord) {
|
||||||
|
// @ts-expect-error For some reason the original post value is typed as {}
|
||||||
|
return `${post.record.text}\n\nQuoting @${post.embed.record.author.handle}\n➥ ${post.embed.record.value.text}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (AppBskyEmbedRecordWithMedia.isView(post.embed)) {
|
||||||
|
const { success: isView } =
|
||||||
|
AppBskyEmbedRecordWithMedia.validateView(post.embed);
|
||||||
|
if (
|
||||||
|
isView &&
|
||||||
|
AppBskyEmbedRecord.isViewRecord(post.embed.record.record)
|
||||||
|
) {
|
||||||
|
const { success: isViewRecord } =
|
||||||
|
AppBskyEmbedRecord.validateViewRecord(
|
||||||
|
post.embed.record.record,
|
||||||
|
);
|
||||||
|
if (isViewRecord) {
|
||||||
|
// @ts-expect-error For some reason the original post value is typed as {}
|
||||||
|
return `${post.record.text}\n\nQuoting @${post.embed.record.record.author.handle}\n➥ ${post.embed.record.record.value.text}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return post.record.text;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
58
src/lib/parseEmbedImage.ts
Normal file
58
src/lib/parseEmbedImage.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import {
|
||||||
|
AppBskyEmbedImages,
|
||||||
|
AppBskyEmbedRecord,
|
||||||
|
AppBskyEmbedRecordWithMedia,
|
||||||
|
type AppBskyFeedDefs,
|
||||||
|
} from "@atproto/api";
|
||||||
|
|
||||||
|
export function parseEmbedImage(post: AppBskyFeedDefs.PostView) {
|
||||||
|
if (AppBskyEmbedRecord.isView(post.embed)) {
|
||||||
|
const { success: isView } = AppBskyEmbedRecord.validateView(
|
||||||
|
post.embed,
|
||||||
|
);
|
||||||
|
if (isView && AppBskyEmbedRecord.isViewRecord(post.embed.record)) {
|
||||||
|
const { success: isViewRecord } =
|
||||||
|
AppBskyEmbedRecord.validateViewRecord(post.embed.record);
|
||||||
|
if (
|
||||||
|
isViewRecord &&
|
||||||
|
post.embed.record.embeds &&
|
||||||
|
post.embed.record.embeds.every((v) =>
|
||||||
|
AppBskyEmbedImages.isView(v),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const validImageView = post.embed.record.embeds.every((v) =>
|
||||||
|
AppBskyEmbedImages.validateView(v),
|
||||||
|
);
|
||||||
|
if (validImageView) {
|
||||||
|
// return post.embed.record.embeds[0].images[0].fullsize;
|
||||||
|
const embeds = post.embed.record
|
||||||
|
.embeds as AppBskyEmbedImages.View[];
|
||||||
|
return embeds.flatMap((e) => e.images.map((i) => i.fullsize));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (AppBskyEmbedRecordWithMedia.isView(post.embed)) {
|
||||||
|
const { success: isView } =
|
||||||
|
AppBskyEmbedRecordWithMedia.validateView(post.embed);
|
||||||
|
if (isView && AppBskyEmbedImages.isView(post.embed.media)) {
|
||||||
|
const { success: isImageView } = AppBskyEmbedImages.validateView(
|
||||||
|
post.embed.media,
|
||||||
|
);
|
||||||
|
if (isImageView) {
|
||||||
|
// return post.embed.media.images[0].fullsize;
|
||||||
|
return post.embed.media.images.map((i) => i.fullsize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (AppBskyEmbedImages.isView(post.embed)) {
|
||||||
|
const { success: isImageView } = AppBskyEmbedImages.validateView(
|
||||||
|
post.embed,
|
||||||
|
);
|
||||||
|
if (isImageView) {
|
||||||
|
// return post.embed.images[0].fullsize;
|
||||||
|
return post.embed.images.map((i) => i.fullsize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return post.author.avatar ?? "";
|
||||||
|
}
|
||||||
57
src/main.ts
57
src/main.ts
|
|
@ -1,7 +1,62 @@
|
||||||
|
import "./util/checkEnv.ts";
|
||||||
|
|
||||||
|
import { BskyAgent } from "@atproto/api";
|
||||||
import { serve } from "@hono/node-server";
|
import { serve } from "@hono/node-server";
|
||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
|
import { HTTPException } from "hono/http-exception";
|
||||||
|
import { Redis } from "ioredis";
|
||||||
|
import { getPost } from "./routes/getPost.tsx";
|
||||||
|
|
||||||
const app = new Hono();
|
// biome-ignore lint/style/noNonNullAssertion: check is ran at app start
|
||||||
|
const redis = new Redis(6379, process.env.REDIS_HOSTNAME!);
|
||||||
|
|
||||||
|
const agent = new BskyAgent({
|
||||||
|
// biome-ignore lint/style/noNonNullAssertion: check is ran at app start
|
||||||
|
service: process.env.BSKY_SERVICE_URL!,
|
||||||
|
persistSession: async (_evt, session) => {
|
||||||
|
if (session) {
|
||||||
|
await redis.set("session", JSON.stringify(session));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const app = new Hono<HonoEnv>();
|
||||||
|
|
||||||
|
app.use("*", async (c, next) => {
|
||||||
|
const session = await redis.get("session");
|
||||||
|
try {
|
||||||
|
if (session) {
|
||||||
|
const login = await agent.resumeSession(JSON.parse(session));
|
||||||
|
if (!login.success) {
|
||||||
|
await agent.login({
|
||||||
|
// biome-ignore lint/style/noNonNullAssertion: check is ran at app start
|
||||||
|
identifier: process.env.BSKY_AUTH_USERNAME!,
|
||||||
|
// biome-ignore lint/style/noNonNullAssertion: check is ran at app start
|
||||||
|
password: process.env.BSKY_AUTH_PASSWORD!,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await agent.login({
|
||||||
|
// biome-ignore lint/style/noNonNullAssertion: check is ran at app start
|
||||||
|
identifier: process.env.BSKY_AUTH_USERNAME!,
|
||||||
|
// biome-ignore lint/style/noNonNullAssertion: check is ran at app start
|
||||||
|
password: process.env.BSKY_AUTH_PASSWORD!,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
c.set("Agent", agent);
|
||||||
|
} catch (error) {
|
||||||
|
const err = new Error("Failed to login to Bluesky!", {
|
||||||
|
cause: error,
|
||||||
|
});
|
||||||
|
throw new HTTPException(500, {
|
||||||
|
message: `${err.message} \n\n ${err.cause} \n\n ${err.stack}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/profile/:user/post/:post", getPost);
|
||||||
|
app.get("/https://bsky.app/profile/:user/post/:post", getPost);
|
||||||
|
|
||||||
serve(
|
serve(
|
||||||
{
|
{
|
||||||
|
|
|
||||||
35
src/routes/getOEmbed.ts
Normal file
35
src/routes/getOEmbed.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import type { Handler } from "hono";
|
||||||
|
|
||||||
|
export enum OEmbedTypes {
|
||||||
|
Post = 1,
|
||||||
|
Profile = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getOEmbed: Handler<HonoEnv, "/oembed"> = async (c) => {
|
||||||
|
const type = +(c.req.query("type") ?? 0);
|
||||||
|
const avatar = c.req.query("avatar");
|
||||||
|
|
||||||
|
const defaults = {
|
||||||
|
provider_name: "FixBluesky",
|
||||||
|
provider_url: "https://bsyy.app/",
|
||||||
|
thumbnail_url: avatar,
|
||||||
|
thumbnail_width: 1000,
|
||||||
|
thumbnail_height: 1000,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type === OEmbedTypes.Post) {
|
||||||
|
const { replies, reposts, likes } = c.req.query();
|
||||||
|
return c.json({
|
||||||
|
author_name: `🗨️ ${replies} ♻️ ${reposts} 💙 ${likes}`,
|
||||||
|
...defaults,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (type === OEmbedTypes.Profile) {
|
||||||
|
const { follows, posts } = c.req.query();
|
||||||
|
return c.json({
|
||||||
|
author_name: `👤 ${follows} followers\n🗨️ ${posts} skeets`,
|
||||||
|
...defaults,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return c.json(defaults, 400);
|
||||||
|
};
|
||||||
27
src/routes/getPost.tsx
Normal file
27
src/routes/getPost.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import type { Handler } from "hono";
|
||||||
|
import { HTTPException } from "hono/http-exception";
|
||||||
|
import { Post } from "../components/Post.tsx";
|
||||||
|
import { fetchPost } from "../lib/fetchPostData.ts";
|
||||||
|
|
||||||
|
export const getPost: Handler<
|
||||||
|
HonoEnv,
|
||||||
|
| "/profile/:user/post/:post"
|
||||||
|
| "/https://bsky.app/profile/:user/post/:post"
|
||||||
|
> = async (c) => {
|
||||||
|
const { user, post } = c.req.param();
|
||||||
|
const agent = c.get("Agent");
|
||||||
|
const { data, success } = await fetchPost(agent, { user, post });
|
||||||
|
if (!success) {
|
||||||
|
throw new HTTPException(500, {
|
||||||
|
message: "Failed to fetch the post!",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.html(
|
||||||
|
<Post
|
||||||
|
post={data.posts[0]}
|
||||||
|
url={c.req.path}
|
||||||
|
appDomain={process.env.FIXBLUESKY_APP_DOMAIN ?? "bsyy.app"}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
};
|
||||||
15
src/util/checkEnv.ts
Normal file
15
src/util/checkEnv.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
if (!process.env.REDIS_HOSTNAME) {
|
||||||
|
throw new Error("REDIS_HOSTNAME is not defined");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!process.env.BSKY_SERVICE_URL) {
|
||||||
|
throw new Error("BSKY_SERVICE_URL is not defined");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!process.env.BSKY_AUTH_USERNAME) {
|
||||||
|
throw new Error("BSKY_AUTH_USERNAME is not defined");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!process.env.BSKY_AUTH_PASSWORD) {
|
||||||
|
throw new Error("BSKY_AUTH_PASSWORD is not defined");
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"importHelpers": false,
|
"importHelpers": false,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "hono/jsx",
|
||||||
"lib": ["esnext"],
|
"lib": ["esnext"],
|
||||||
"module": "NodeNext",
|
"module": "NodeNext",
|
||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "NodeNext",
|
||||||
|
|
|
||||||
76
yarn.lock
76
yarn.lock
|
|
@ -481,6 +481,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@ioredis/commands@npm:^1.1.1":
|
||||||
|
version: 1.2.0
|
||||||
|
resolution: "@ioredis/commands@npm:1.2.0"
|
||||||
|
checksum: 10c0/a5d3c29dd84d8a28b7c67a441ac1715cbd7337a7b88649c0f17c345d89aa218578d2b360760017c48149ef8a70f44b051af9ac0921a0622c2b479614c4f65b36
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@isaacs/cliui@npm:^8.0.2":
|
"@isaacs/cliui@npm:^8.0.2":
|
||||||
version: 8.0.2
|
version: 8.0.2
|
||||||
resolution: "@isaacs/cliui@npm:8.0.2"
|
resolution: "@isaacs/cliui@npm:8.0.2"
|
||||||
|
|
@ -892,6 +899,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"cluster-key-slot@npm:^1.1.0":
|
||||||
|
version: 1.1.2
|
||||||
|
resolution: "cluster-key-slot@npm:1.1.2"
|
||||||
|
checksum: 10c0/d7d39ca28a8786e9e801eeb8c770e3c3236a566625d7299a47bb71113fb2298ce1039596acb82590e598c52dbc9b1f088c8f587803e697cb58e1867a95ff94d3
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"color-convert@npm:^2.0.1":
|
"color-convert@npm:^2.0.1":
|
||||||
version: 2.0.1
|
version: 2.0.1
|
||||||
resolution: "color-convert@npm:2.0.1"
|
resolution: "color-convert@npm:2.0.1"
|
||||||
|
|
@ -945,6 +959,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"denque@npm:^2.1.0":
|
||||||
|
version: 2.1.0
|
||||||
|
resolution: "denque@npm:2.1.0"
|
||||||
|
checksum: 10c0/f9ef81aa0af9c6c614a727cb3bd13c5d7db2af1abf9e6352045b86e85873e629690f6222f4edd49d10e4ccf8f078bbeec0794fafaf61b659c0589d0c511ec363
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"eastasianwidth@npm:^0.2.0":
|
"eastasianwidth@npm:^0.2.0":
|
||||||
version: 0.2.0
|
version: 0.2.0
|
||||||
resolution: "eastasianwidth@npm:0.2.0"
|
resolution: "eastasianwidth@npm:0.2.0"
|
||||||
|
|
@ -1172,6 +1193,7 @@ __metadata:
|
||||||
"@hono/node-server": "npm:1.11.0"
|
"@hono/node-server": "npm:1.11.0"
|
||||||
"@types/node": "npm:20.12.7"
|
"@types/node": "npm:20.12.7"
|
||||||
hono: "npm:4.2.7"
|
hono: "npm:4.2.7"
|
||||||
|
ioredis: "npm:5.4.1"
|
||||||
pkgroll: "npm:2.0.2"
|
pkgroll: "npm:2.0.2"
|
||||||
tsx: "npm:4.7.2"
|
tsx: "npm:4.7.2"
|
||||||
typescript: "npm:5.4.5"
|
typescript: "npm:5.4.5"
|
||||||
|
|
@ -1373,6 +1395,23 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"ioredis@npm:5.4.1":
|
||||||
|
version: 5.4.1
|
||||||
|
resolution: "ioredis@npm:5.4.1"
|
||||||
|
dependencies:
|
||||||
|
"@ioredis/commands": "npm:^1.1.1"
|
||||||
|
cluster-key-slot: "npm:^1.1.0"
|
||||||
|
debug: "npm:^4.3.4"
|
||||||
|
denque: "npm:^2.1.0"
|
||||||
|
lodash.defaults: "npm:^4.2.0"
|
||||||
|
lodash.isarguments: "npm:^3.1.0"
|
||||||
|
redis-errors: "npm:^1.2.0"
|
||||||
|
redis-parser: "npm:^3.0.0"
|
||||||
|
standard-as-callback: "npm:^2.1.0"
|
||||||
|
checksum: 10c0/5d28b7c89a3cab5b76d75923d7d4ce79172b3a1ca9be690133f6e8e393a7a4b4ffd55513e618bbb5504fed80d9e1395c9d9531a7c5c5c84aa4c4e765cca75456
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"ip-address@npm:^9.0.5":
|
"ip-address@npm:^9.0.5":
|
||||||
version: 9.0.5
|
version: 9.0.5
|
||||||
resolution: "ip-address@npm:9.0.5"
|
resolution: "ip-address@npm:9.0.5"
|
||||||
|
|
@ -1472,6 +1511,20 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"lodash.defaults@npm:^4.2.0":
|
||||||
|
version: 4.2.0
|
||||||
|
resolution: "lodash.defaults@npm:4.2.0"
|
||||||
|
checksum: 10c0/d5b77aeb702caa69b17be1358faece33a84497bcca814897383c58b28a2f8dfc381b1d9edbec239f8b425126a3bbe4916223da2a576bb0411c2cefd67df80707
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"lodash.isarguments@npm:^3.1.0":
|
||||||
|
version: 3.1.0
|
||||||
|
resolution: "lodash.isarguments@npm:3.1.0"
|
||||||
|
checksum: 10c0/5e8f95ba10975900a3920fb039a3f89a5a79359a1b5565e4e5b4310ed6ebe64011e31d402e34f577eca983a1fc01ff86c926e3cbe602e1ddfc858fdd353e62d8
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0":
|
"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0":
|
||||||
version: 10.2.0
|
version: 10.2.0
|
||||||
resolution: "lru-cache@npm:10.2.0"
|
resolution: "lru-cache@npm:10.2.0"
|
||||||
|
|
@ -1770,6 +1823,22 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"redis-errors@npm:^1.0.0, redis-errors@npm:^1.2.0":
|
||||||
|
version: 1.2.0
|
||||||
|
resolution: "redis-errors@npm:1.2.0"
|
||||||
|
checksum: 10c0/5b316736e9f532d91a35bff631335137a4f974927bb2fb42bf8c2f18879173a211787db8ac4c3fde8f75ed6233eb0888e55d52510b5620e30d69d7d719c8b8a7
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"redis-parser@npm:^3.0.0":
|
||||||
|
version: 3.0.0
|
||||||
|
resolution: "redis-parser@npm:3.0.0"
|
||||||
|
dependencies:
|
||||||
|
redis-errors: "npm:^1.0.0"
|
||||||
|
checksum: 10c0/ee16ac4c7b2a60b1f42a2cdaee22b005bd4453eb2d0588b8a4939718997ae269da717434da5d570fe0b05030466eeb3f902a58cf2e8e1ca058bf6c9c596f632f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"resolve-pkg-maps@npm:^1.0.0":
|
"resolve-pkg-maps@npm:^1.0.0":
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
resolution: "resolve-pkg-maps@npm:1.0.0"
|
resolution: "resolve-pkg-maps@npm:1.0.0"
|
||||||
|
|
@ -1965,6 +2034,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"standard-as-callback@npm:^2.1.0":
|
||||||
|
version: 2.1.0
|
||||||
|
resolution: "standard-as-callback@npm:2.1.0"
|
||||||
|
checksum: 10c0/012677236e3d3fdc5689d29e64ea8a599331c4babe86956bf92fc5e127d53f85411c5536ee0079c52c43beb0026b5ce7aa1d834dd35dd026e82a15d1bcaead1f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0":
|
"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0":
|
||||||
version: 4.2.3
|
version: 4.2.3
|
||||||
resolution: "string-width@npm:4.2.3"
|
resolution: "string-width@npm:4.2.3"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue