add rssapp
This commit is contained in:
parent
640e9ee892
commit
8a7baefd76
20
services/rssapp/.dockerignore
Normal file
20
services/rssapp/.dockerignore
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# .dockerignore
|
||||||
|
# Generated by Data Craftsman Tools
|
||||||
|
# https://docs.docker.com/engine/reference/builder/#dockerignore-file
|
||||||
|
|
||||||
|
# Node.js
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.npm
|
||||||
|
.node_repl_history
|
||||||
|
.nyc_output
|
||||||
|
coverage/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
144
services/rssapp/.gitignore
vendored
Normal file
144
services/rssapp/.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
|
23
services/rssapp/Dockerfile
Normal file
23
services/rssapp/Dockerfile
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# --- Base image
|
||||||
|
FROM node:22-alpine AS base
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# --- Install dependencies
|
||||||
|
FROM base AS deps
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci --omit=dev
|
||||||
|
|
||||||
|
# --- Build / final image
|
||||||
|
FROM base AS runner
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV PORT=3000
|
||||||
|
|
||||||
|
COPY --from=deps /usr/src/app/node_modules ./node_modules
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["node", "index.js"]
|
148
services/rssapp/README.md
Normal file
148
services/rssapp/README.md
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
# rssapp
|
||||||
|
|
||||||
|
A HTTP service which dynamically generates RSS feeds for recent lewdtuber tweets (X Posts). Tweet data is cached for 30 mins.
|
||||||
|
|
||||||
|
## Example usage
|
||||||
|
|
||||||
|
GET `https://rssapp.example.com/projektmelody`
|
||||||
|
|
||||||
|
### Example response
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
|
||||||
|
<channel>
|
||||||
|
<title><![CDATA[projektmelody Tweets]]></title>
|
||||||
|
<description><![CDATA[Latest tweets from projektmelody]]></description>
|
||||||
|
<link>https://x.com/projektmelody</link>
|
||||||
|
<generator>RSS for Node</generator>
|
||||||
|
<lastBuildDate>Mon, 25 Aug 2025 14:50:15 GMT</lastBuildDate>
|
||||||
|
<atom:link href="https://rssapp.sbtp.xyz/projektmelody.xml" rel="self" type="application/rss+xml"/>
|
||||||
|
<language><![CDATA[en]]></language>
|
||||||
|
<item>
|
||||||
|
<title><![CDATA[Did I mention this exists?? 🎶]]></title>
|
||||||
|
<description><![CDATA[Did I mention this exists?? 🎶]]></description>
|
||||||
|
<link>https://x.com/ProjektMelody/status/1959854383627489458</link>
|
||||||
|
<guid isPermaLink="false">1959854383627489458</guid>
|
||||||
|
<pubDate>Mon, 25 Aug 2025 05:44:32 GMT</pubDate>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<title><![CDATA[I'M LIVE!
|
||||||
|
|
||||||
|
FANSLY: https://t.co/mh1izesG2p
|
||||||
|
OF: https://t.co/PhUjaazNrn
|
||||||
|
CB: https://t.co/cxNMAItlt2]]></title>
|
||||||
|
<description><![CDATA[I'M LIVE!
|
||||||
|
|
||||||
|
FANSLY: https://t.co/mh1izesG2p
|
||||||
|
OF: https://t.co/PhUjaazNrn
|
||||||
|
CB: https://t.co/cxNMAItlt2]]></description>
|
||||||
|
<link>https://x.com/ProjektMelody/status/1959774984387416463</link>
|
||||||
|
<guid isPermaLink="false">1959774984387416463</guid>
|
||||||
|
<pubDate>Mon, 25 Aug 2025 00:29:01 GMT</pubDate>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<title><![CDATA[runnning 10 min late sry!!!!]]></title>
|
||||||
|
<description><![CDATA[runnning 10 min late sry!!!!]]></description>
|
||||||
|
<link>https://x.com/ProjektMelody/status/1959766931898249709</link>
|
||||||
|
<guid isPermaLink="false">1959766931898249709</guid>
|
||||||
|
<pubDate>Sun, 24 Aug 2025 23:57:01 GMT</pubDate>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<title><![CDATA[BRUH I HAVE A SCHECHULE!!!!!!!! https://t.co/jXwtWlozwJ]]></title>
|
||||||
|
<description><![CDATA[BRUH I HAVE A SCHECHULE!!!!!!!! https://t.co/jXwtWlozwJ]]></description>
|
||||||
|
<link>https://x.com/ProjektMelody/status/1959428737021407540</link>
|
||||||
|
<guid isPermaLink="false">1959428737021407540</guid>
|
||||||
|
<pubDate>Sun, 24 Aug 2025 01:33:09 GMT</pubDate>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<title><![CDATA[@RemixKun i h8 it here]]></title>
|
||||||
|
<description><![CDATA[@RemixKun i h8 it here]]></description>
|
||||||
|
<link>https://x.com/ProjektMelody/status/1959185583223550227</link>
|
||||||
|
<guid isPermaLink="false">1959185583223550227</guid>
|
||||||
|
<pubDate>Sat, 23 Aug 2025 09:26:57 GMT</pubDate>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<title><![CDATA[At least it isn't inflation https://t.co/jgCU1Ouy3e]]></title>
|
||||||
|
<description><![CDATA[At least it isn't inflation https://t.co/jgCU1Ouy3e]]></description>
|
||||||
|
<link>https://x.com/ProjektMelody/status/1959184510421004782</link>
|
||||||
|
<guid isPermaLink="false">1959184510421004782</guid>
|
||||||
|
<pubDate>Sat, 23 Aug 2025 09:22:41 GMT</pubDate>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<title><![CDATA[🦋⁺. ✦ NEW COLLAB SONG DROPPED!!! ✦ . ⁺ 🦋
|
||||||
|
|
||||||
|
"SNØWCRASH N' BURN" (track 2) is so pretty! Hard not to ]]></title>
|
||||||
|
<description><![CDATA[🦋⁺. ✦ NEW COLLAB SONG DROPPED!!! ✦ . ⁺ 🦋
|
||||||
|
|
||||||
|
"SNØWCRASH N' BURN" (track 2) is so pretty! Hard not to get lost in the drum beats & synth waves. My chest is full of little virtual butterflies.
|
||||||
|
|
||||||
|
Cynthoni sure puts out bangers.
|
||||||
|
|
||||||
|
LISTEN HERE: https://t.co/VEoQgLc269 https://t.co/e9YU1vSTPi]]></description>
|
||||||
|
<link>https://x.com/ProjektMelody/status/1959100103350001770</link>
|
||||||
|
<guid isPermaLink="false">1959100103350001770</guid>
|
||||||
|
<pubDate>Sat, 23 Aug 2025 03:47:17 GMT</pubDate>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<title><![CDATA[@brittblusierra なんだ??]]></title>
|
||||||
|
<description><![CDATA[@brittblusierra なんだ??]]></description>
|
||||||
|
<link>https://x.com/ProjektMelody/status/1959069147528405036</link>
|
||||||
|
<guid isPermaLink="false">1959069147528405036</guid>
|
||||||
|
<pubDate>Sat, 23 Aug 2025 01:44:17 GMT</pubDate>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<title><![CDATA[@RemixKun yes, totally not wearing a push-up bra stuffed with cantalope]]></title>
|
||||||
|
<description><![CDATA[@RemixKun yes, totally not wearing a push-up bra stuffed with cantalope]]></description>
|
||||||
|
<link>https://x.com/ProjektMelody/status/1959066423764165007</link>
|
||||||
|
<guid isPermaLink="false">1959066423764165007</guid>
|
||||||
|
<pubDate>Sat, 23 Aug 2025 01:33:27 GMT</pubDate>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<title><![CDATA[@ArmadaJones ill make it fit >.>]]></title>
|
||||||
|
<description><![CDATA[@ArmadaJones ill make it fit >.>]]></description>
|
||||||
|
<link>https://x.com/ProjektMelody/status/1959066077096550732</link>
|
||||||
|
<guid isPermaLink="false">1959066077096550732</guid>
|
||||||
|
<pubDate>Sat, 23 Aug 2025 01:32:05 GMT</pubDate>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<title><![CDATA[STREAM TOMORROW EVENING!~
|
||||||
|
I'm gonna give so many hugs!
|
||||||
|
|
||||||
|
Wait... I'm in the computer. O.o
|
||||||
|
|
||||||
|
Ok, I'll h]]></title>
|
||||||
|
<description><![CDATA[STREAM TOMORROW EVENING!~
|
||||||
|
I'm gonna give so many hugs!
|
||||||
|
|
||||||
|
Wait... I'm in the computer. O.o
|
||||||
|
|
||||||
|
Ok, I'll have to use my mouth.
|
||||||
|
Mouth hugs for everyone! https://t.co/hidiMBkc2H]]></description>
|
||||||
|
<link>https://x.com/ProjektMelody/status/1959065532155797966</link>
|
||||||
|
<guid isPermaLink="false">1959065532155797966</guid>
|
||||||
|
<pubDate>Sat, 23 Aug 2025 01:29:55 GMT</pubDate>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<title><![CDATA[.✧˚️RAWR! I'VE RETURNED!˚️✧.
|
||||||
|
|
||||||
|
My chakras are aligned. Goals are clear.
|
||||||
|
& I'm ready for a new ch]]></title>
|
||||||
|
<description><![CDATA[.✧˚️RAWR! I'VE RETURNED!˚️✧.
|
||||||
|
|
||||||
|
My chakras are aligned. Goals are clear.
|
||||||
|
& I'm ready for a new chapter.
|
||||||
|
|
||||||
|
I think i just needed a break tbh... but im back. And missed the balls outta y'all! (TᴖT)
|
||||||
|
|
||||||
|
STREAM RESUMES➡️Aug 23 @ 7 pm CST
|
||||||
|
(Fansly/CB/OF)
|
||||||
|
|
||||||
|
See yall soon, space cowboys!]]></description>
|
||||||
|
<link>https://x.com/ProjektMelody/status/1958897664072065467</link>
|
||||||
|
<guid isPermaLink="false">1958897664072065467</guid>
|
||||||
|
<pubDate>Fri, 22 Aug 2025 14:22:52 GMT</pubDate>
|
||||||
|
</item>
|
||||||
|
</channel>
|
||||||
|
</rss>
|
||||||
|
```
|
||||||
|
|
17
services/rssapp/env.js
Normal file
17
services/rssapp/env.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import '@dotenvx/dotenvx/config';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const EnvSchema = z.object({
|
||||||
|
APIFY_TOKEN: z.string().min(1, "APIFY_TOKEN is required"),
|
||||||
|
ORIGIN: z.string().min(1, "ORIGIN is required"),
|
||||||
|
WHITELIST: z.string().min(1, "WHITELIST is required"), // raw comma-separated string
|
||||||
|
});
|
||||||
|
|
||||||
|
// Parse raw env first
|
||||||
|
const parsed = EnvSchema.parse(process.env);
|
||||||
|
|
||||||
|
// Return env object with WHITELIST converted to Set
|
||||||
|
export const env = {
|
||||||
|
...parsed,
|
||||||
|
WHITELIST: new Set(parsed.WHITELIST.split(',').map(u => u.trim().toLowerCase())),
|
||||||
|
};
|
116
services/rssapp/index.js
Normal file
116
services/rssapp/index.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// index.js
|
||||||
|
import Fastify from "fastify";
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import RSS from "rss";
|
||||||
|
import TTLCache from '@isaacs/ttlcache';
|
||||||
|
import '@dotenvx/dotenvx/config.js';
|
||||||
|
import { ApifyClient } from 'apify-client';
|
||||||
|
import { env } from './env.js';
|
||||||
|
|
||||||
|
const fastify = Fastify({ logger: true });
|
||||||
|
|
||||||
|
|
||||||
|
const client = new ApifyClient({
|
||||||
|
token: env.APIFY_TOKEN,
|
||||||
|
});
|
||||||
|
|
||||||
|
const cache = new TTLCache({
|
||||||
|
max: 5000,
|
||||||
|
ttl: 30 * 60 * 1000, // 30 minutes
|
||||||
|
});
|
||||||
|
|
||||||
|
// Format date as YYYY-MM-DD_HH:mm:ss_UTC
|
||||||
|
function formatTwitterDate(date) {
|
||||||
|
const pad = (n) => String(n).padStart(2, "0");
|
||||||
|
const year = date.getUTCFullYear();
|
||||||
|
const month = pad(date.getUTCMonth() + 1);
|
||||||
|
const day = pad(date.getUTCDate());
|
||||||
|
const hours = pad(date.getUTCHours());
|
||||||
|
const minutes = pad(date.getUTCMinutes());
|
||||||
|
const seconds = pad(date.getUTCSeconds());
|
||||||
|
return `${year}-${month}-${day}_${hours}:${minutes}:${seconds}_UTC`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getJson(username) {
|
||||||
|
// 15 days ago
|
||||||
|
const sinceDate = new Date(Date.now() - 15 * 24 * 60 * 60 * 1000);
|
||||||
|
const formatted = formatTwitterDate(sinceDate);
|
||||||
|
|
||||||
|
const { defaultDatasetId } = await client.actor('kaitoeasyapi/twitter-x-data-tweet-scraper-pay-per-result-cheapest').call({
|
||||||
|
searchTerms: [`from:${username} since:${formatted}`]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetches results from the actor's dataset.
|
||||||
|
const { items } = await client.dataset(defaultDatasetId).listItems();
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Helper to build RSS feed
|
||||||
|
function buildFeed(username, tweets) {
|
||||||
|
const feed = new RSS({
|
||||||
|
title: `${username} Tweets`,
|
||||||
|
description: `Latest tweets from ${username}`,
|
||||||
|
feed_url: `https://${env.ORIGIN}/${username}`,
|
||||||
|
site_url: `https://x.com/${username}`,
|
||||||
|
language: "en",
|
||||||
|
});
|
||||||
|
|
||||||
|
tweets.forEach((tweet) => {
|
||||||
|
if (tweet.id === -1) return; // skip mock API filler
|
||||||
|
feed.item({
|
||||||
|
title: tweet.text.substring(0, 100),
|
||||||
|
description: tweet.text,
|
||||||
|
url: tweet.url,
|
||||||
|
guid: tweet.id.toString(),
|
||||||
|
date: new Date(tweet.createdAt),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return feed.xml({ indent: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
fastify.get("/:username", async (request, reply) => {
|
||||||
|
const { username } = request.params;
|
||||||
|
|
||||||
|
if (!env.WHITELIST.has(username.toLowerCase())) {
|
||||||
|
return reply.code(403).send({ error: "Forbidden username" });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Serve from cache if exists
|
||||||
|
const cached = cache.get(username);
|
||||||
|
if (cached) {
|
||||||
|
fastify.log.info('sending cached data');
|
||||||
|
fastify.log.info(cached);
|
||||||
|
return reply
|
||||||
|
.header("Content-Type", "application/rss+xml")
|
||||||
|
.send(cached.xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await getJson(username)
|
||||||
|
|
||||||
|
const xml = buildFeed(username, data);
|
||||||
|
|
||||||
|
// Cache result
|
||||||
|
cache.set(username, { xml, timestamp: Date.now() });
|
||||||
|
|
||||||
|
return reply.header("Content-Type", "application/rss+xml").send(xml);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
const start = async () => {
|
||||||
|
try {
|
||||||
|
fastify.listen({
|
||||||
|
port: process.env.PORT || 3000,
|
||||||
|
host: "0.0.0.0"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
fastify.log.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
start();
|
1519
services/rssapp/package-lock.json
generated
Normal file
1519
services/rssapp/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
services/rssapp/package.json
Normal file
22
services/rssapp/package.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "rssapp",
|
||||||
|
"version": "69.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"start": "node index.js"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "Unlicense",
|
||||||
|
"dependencies": {
|
||||||
|
"@dotenvx/dotenvx": "^1.49.0",
|
||||||
|
"@isaacs/ttlcache": "^1.4.1",
|
||||||
|
"apify-client": "^2.15.1",
|
||||||
|
"fastify": "^5.5.0",
|
||||||
|
"lru-cache": "^11.1.0",
|
||||||
|
"rss": "^1.2.2",
|
||||||
|
"zod": "^4.1.1"
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user