Compare commits
8 Commits
dev
...
edd8dc58ab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edd8dc58ab | ||
|
|
f17f0031a1 | ||
|
|
4d5dc1f8f9 | ||
|
|
8397e5acf2 | ||
|
|
5bcaade558 | ||
|
|
aee811309b | ||
|
|
48a29cd872 | ||
|
|
c95fc3101b |
@@ -62,18 +62,9 @@ jobs:
|
|||||||
CONTAINER_NAME="portfolio-app-dev"
|
CONTAINER_NAME="portfolio-app-dev"
|
||||||
HEALTH_PORT="3001"
|
HEALTH_PORT="3001"
|
||||||
IMAGE_NAME="${{ env.DOCKER_IMAGE }}:dev"
|
IMAGE_NAME="${{ env.DOCKER_IMAGE }}:dev"
|
||||||
BOT_CONTAINER="portfolio-discord-bot-dev"
|
|
||||||
BOT_IMAGE="portfolio-discord-bot:dev"
|
|
||||||
|
|
||||||
# Build discord-bot image
|
# Check for existing container
|
||||||
echo "🏗️ Building discord-bot image..."
|
|
||||||
DOCKER_BUILDKIT=1 docker build \
|
|
||||||
-t $BOT_IMAGE \
|
|
||||||
./discord-presence-bot
|
|
||||||
|
|
||||||
# Check for existing containers
|
|
||||||
EXISTING_CONTAINER=$(docker ps -aq -f name=$CONTAINER_NAME || echo "")
|
EXISTING_CONTAINER=$(docker ps -aq -f name=$CONTAINER_NAME || echo "")
|
||||||
EXISTING_BOT=$(docker ps -aq -f name=$BOT_CONTAINER || echo "")
|
|
||||||
|
|
||||||
# Ensure networks exist
|
# Ensure networks exist
|
||||||
echo "🌐 Ensuring networks exist..."
|
echo "🌐 Ensuring networks exist..."
|
||||||
@@ -87,15 +78,13 @@ jobs:
|
|||||||
echo "⚠️ Production database not reachable, app will use fallbacks"
|
echo "⚠️ Production database not reachable, app will use fallbacks"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Stop and remove existing containers
|
# Stop and remove existing container
|
||||||
for C in $EXISTING_CONTAINER $EXISTING_BOT; do
|
if [ ! -z "$EXISTING_CONTAINER" ]; then
|
||||||
if [ ! -z "$C" ]; then
|
echo "🛑 Stopping existing container..."
|
||||||
echo "🛑 Stopping existing container $C..."
|
docker stop $EXISTING_CONTAINER 2>/dev/null || true
|
||||||
docker stop $C 2>/dev/null || true
|
docker rm $EXISTING_CONTAINER 2>/dev/null || true
|
||||||
docker rm $C 2>/dev/null || true
|
sleep 3
|
||||||
sleep 3
|
fi
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Ensure port is free
|
# Ensure port is free
|
||||||
PORT_CONTAINER=$(docker ps -a --format "{{.ID}}\t{{.Ports}}" | grep -E "(:${HEALTH_PORT}->)" | awk '{print $1}' | head -1 || echo "")
|
PORT_CONTAINER=$(docker ps -a --format "{{.ID}}\t{{.Ports}}" | grep -E "(:${HEALTH_PORT}->)" | awk '{print $1}' | head -1 || echo "")
|
||||||
@@ -106,18 +95,7 @@ jobs:
|
|||||||
sleep 3
|
sleep 3
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Start discord-bot container
|
# Start new container
|
||||||
echo "🤖 Starting discord-bot container..."
|
|
||||||
docker run -d \
|
|
||||||
--name $BOT_CONTAINER \
|
|
||||||
--restart unless-stopped \
|
|
||||||
--network portfolio_net \
|
|
||||||
-e DISCORD_BOT_TOKEN="${DISCORD_BOT_TOKEN}" \
|
|
||||||
-e DISCORD_USER_ID="${DISCORD_USER_ID:-172037532370862080}" \
|
|
||||||
-e BOT_PORT=3001 \
|
|
||||||
$BOT_IMAGE
|
|
||||||
|
|
||||||
# Start new portfolio container
|
|
||||||
echo "🆕 Starting new dev container..."
|
echo "🆕 Starting new dev container..."
|
||||||
docker run -d \
|
docker run -d \
|
||||||
--name $CONTAINER_NAME \
|
--name $CONTAINER_NAME \
|
||||||
@@ -181,8 +159,6 @@ jobs:
|
|||||||
N8N_API_KEY: ${{ vars.N8N_API_KEY || '' }}
|
N8N_API_KEY: ${{ vars.N8N_API_KEY || '' }}
|
||||||
DIRECTUS_URL: ${{ vars.DIRECTUS_URL || 'https://cms.dk0.dev' }}
|
DIRECTUS_URL: ${{ vars.DIRECTUS_URL || 'https://cms.dk0.dev' }}
|
||||||
DIRECTUS_STATIC_TOKEN: ${{ secrets.DIRECTUS_STATIC_TOKEN || '' }}
|
DIRECTUS_STATIC_TOKEN: ${{ secrets.DIRECTUS_STATIC_TOKEN || '' }}
|
||||||
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN || '' }}
|
|
||||||
DISCORD_USER_ID: ${{ vars.DISCORD_USER_ID || '172037532370862080' }}
|
|
||||||
|
|
||||||
- name: Cleanup
|
- name: Cleanup
|
||||||
run: docker image prune -f
|
run: docker image prune -f
|
||||||
@@ -233,12 +209,10 @@ jobs:
|
|||||||
export ADMIN_SESSION_SECRET="${ADMIN_SESSION_SECRET}"
|
export ADMIN_SESSION_SECRET="${ADMIN_SESSION_SECRET}"
|
||||||
export DIRECTUS_URL="${DIRECTUS_URL}"
|
export DIRECTUS_URL="${DIRECTUS_URL}"
|
||||||
export DIRECTUS_STATIC_TOKEN="${DIRECTUS_STATIC_TOKEN}"
|
export DIRECTUS_STATIC_TOKEN="${DIRECTUS_STATIC_TOKEN}"
|
||||||
export DISCORD_BOT_TOKEN="${DISCORD_BOT_TOKEN}"
|
|
||||||
export DISCORD_USER_ID="${DISCORD_USER_ID:-172037532370862080}"
|
|
||||||
|
|
||||||
# Start new containers via compose
|
# Start new container via compose
|
||||||
echo "🆕 Starting new production containers..."
|
echo "🆕 Starting new production container..."
|
||||||
docker compose -f $COMPOSE_FILE up -d --build portfolio discord-bot
|
docker compose -f $COMPOSE_FILE up -d portfolio
|
||||||
|
|
||||||
# Wait for health
|
# Wait for health
|
||||||
echo "⏳ Waiting for container to be healthy..."
|
echo "⏳ Waiting for container to be healthy..."
|
||||||
@@ -300,8 +274,6 @@ jobs:
|
|||||||
N8N_API_KEY: ${{ vars.N8N_API_KEY || '' }}
|
N8N_API_KEY: ${{ vars.N8N_API_KEY || '' }}
|
||||||
DIRECTUS_URL: ${{ vars.DIRECTUS_URL || 'https://cms.dk0.dev' }}
|
DIRECTUS_URL: ${{ vars.DIRECTUS_URL || 'https://cms.dk0.dev' }}
|
||||||
DIRECTUS_STATIC_TOKEN: ${{ secrets.DIRECTUS_STATIC_TOKEN || '' }}
|
DIRECTUS_STATIC_TOKEN: ${{ secrets.DIRECTUS_STATIC_TOKEN || '' }}
|
||||||
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN || '' }}
|
|
||||||
DISCORD_USER_ID: ${{ vars.DISCORD_USER_ID || '172037532370862080' }}
|
|
||||||
|
|
||||||
- name: Cleanup
|
- name: Cleanup
|
||||||
run: docker image prune -f
|
run: docker image prune -f
|
||||||
|
|||||||
@@ -123,8 +123,6 @@ N8N_SECRET_TOKEN=...
|
|||||||
N8N_API_KEY=...
|
N8N_API_KEY=...
|
||||||
DATABASE_URL=postgresql://...
|
DATABASE_URL=postgresql://...
|
||||||
REDIS_URL=redis://... # optional
|
REDIS_URL=redis://... # optional
|
||||||
DISCORD_BOT_TOKEN=... # Discord bot token for presence bot (replaces Lanyard)
|
|
||||||
DISCORD_USER_ID=172037532370862080 # Discord user ID to track
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Adding a CMS-managed Section
|
## Adding a CMS-managed Section
|
||||||
|
|||||||
@@ -2,19 +2,6 @@ import type { Metadata } from "next";
|
|||||||
import HomePageServer from "../_ui/HomePageServer";
|
import HomePageServer from "../_ui/HomePageServer";
|
||||||
import { getLanguageAlternates, toAbsoluteUrl } from "@/lib/seo";
|
import { getLanguageAlternates, toAbsoluteUrl } from "@/lib/seo";
|
||||||
|
|
||||||
const localeMetadata: Record<string, { title: string; description: string }> = {
|
|
||||||
de: {
|
|
||||||
title: "Dennis Konkol – Webentwickler Osnabrück",
|
|
||||||
description:
|
|
||||||
"Dennis Konkol – Software Engineer & Webentwickler in Osnabrück. Webentwicklung, Fullstack-Apps, Docker, Next.js, Flutter. Projekte ansehen und Kontakt aufnehmen.",
|
|
||||||
},
|
|
||||||
en: {
|
|
||||||
title: "Dennis Konkol – Web Developer Osnabrück",
|
|
||||||
description:
|
|
||||||
"Dennis Konkol – Software Engineer & Web Developer in Osnabrück, Germany. Web development, fullstack apps, Docker, Next.js, Flutter.",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: {
|
||||||
@@ -22,10 +9,7 @@ export async function generateMetadata({
|
|||||||
}): Promise<Metadata> {
|
}): Promise<Metadata> {
|
||||||
const { locale } = await params;
|
const { locale } = await params;
|
||||||
const languages = getLanguageAlternates({ pathWithoutLocale: "" });
|
const languages = getLanguageAlternates({ pathWithoutLocale: "" });
|
||||||
const meta = localeMetadata[locale] ?? localeMetadata.en;
|
|
||||||
return {
|
return {
|
||||||
title: meta.title,
|
|
||||||
description: meta.description,
|
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: toAbsoluteUrl(`/${locale}`),
|
canonical: toAbsoluteUrl(`/${locale}`),
|
||||||
languages,
|
languages,
|
||||||
|
|||||||
@@ -13,12 +13,7 @@ export async function generateMetadata({
|
|||||||
}): Promise<Metadata> {
|
}): Promise<Metadata> {
|
||||||
const { locale } = await params;
|
const { locale } = await params;
|
||||||
const languages = getLanguageAlternates({ pathWithoutLocale: "projects" });
|
const languages = getLanguageAlternates({ pathWithoutLocale: "projects" });
|
||||||
const isDe = locale === "de";
|
|
||||||
return {
|
return {
|
||||||
title: isDe ? "Projekte – Dennis Konkol" : "Projects – Dennis Konkol",
|
|
||||||
description: isDe
|
|
||||||
? "Webentwicklung, Fullstack-Apps und Mobile-Projekte von Dennis Konkol. Next.js, Flutter, Docker und mehr – Osnabrück."
|
|
||||||
: "Web development, fullstack apps and mobile projects by Dennis Konkol. Next.js, Flutter, Docker and more – Osnabrück.",
|
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: toAbsoluteUrl(`/${locale}/projects`),
|
canonical: toAbsoluteUrl(`/${locale}/projects`),
|
||||||
languages,
|
languages,
|
||||||
|
|||||||
@@ -31,41 +31,20 @@ export default async function HomePageServer({ locale }: HomePageServerProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="min-h-screen">
|
<div className="min-h-screen">
|
||||||
<Script
|
<Script
|
||||||
id={"structured-data-person"}
|
id={"structured-data"}
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: JSON.stringify({
|
__html: JSON.stringify({
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "Person",
|
"@type": "Person",
|
||||||
name: "Dennis Konkol",
|
name: "Dennis Konkol",
|
||||||
alternateName: ["dk0", "denshooter"],
|
|
||||||
url: "https://dk0.dev",
|
url: "https://dk0.dev",
|
||||||
jobTitle: "Software Engineer",
|
jobTitle: "Software Engineer",
|
||||||
description:
|
|
||||||
locale === "de"
|
|
||||||
? "Software Engineer & Webentwickler in Osnabrück. Webentwicklung, Fullstack-Apps, Docker, Next.js, Flutter."
|
|
||||||
: "Software Engineer & Web Developer in Osnabrück, Germany. Web development, fullstack apps, Docker, Next.js, Flutter.",
|
|
||||||
address: {
|
address: {
|
||||||
"@type": "PostalAddress",
|
"@type": "PostalAddress",
|
||||||
addressLocality: "Osnabrück",
|
addressLocality: "Osnabrück",
|
||||||
addressRegion: "Niedersachsen",
|
addressCountry: "Germany",
|
||||||
addressCountry: "DE",
|
|
||||||
},
|
},
|
||||||
knowsAbout: [
|
|
||||||
"Webentwicklung",
|
|
||||||
"Web Development",
|
|
||||||
"Next.js",
|
|
||||||
"React",
|
|
||||||
"TypeScript",
|
|
||||||
"Flutter",
|
|
||||||
"Docker",
|
|
||||||
"DevOps",
|
|
||||||
"Self-Hosting",
|
|
||||||
"CI/CD",
|
|
||||||
"Fullstack Development",
|
|
||||||
"Softwareentwicklung",
|
|
||||||
"Informatik",
|
|
||||||
],
|
|
||||||
sameAs: [
|
sameAs: [
|
||||||
"https://github.com/Denshooter",
|
"https://github.com/Denshooter",
|
||||||
"https://linkedin.com/in/dkonkol",
|
"https://linkedin.com/in/dkonkol",
|
||||||
@@ -73,20 +52,6 @@ export default async function HomePageServer({ locale }: HomePageServerProps) {
|
|||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Script
|
|
||||||
id={"structured-data-website"}
|
|
||||||
type="application/ld+json"
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: JSON.stringify({
|
|
||||||
"@context": "https://schema.org",
|
|
||||||
"@type": "WebSite",
|
|
||||||
name: "Dennis Konkol",
|
|
||||||
alternateName: "dk0.dev",
|
|
||||||
url: "https://dk0.dev",
|
|
||||||
inLanguage: ["de", "en"],
|
|
||||||
}),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Header locale={locale} />
|
<Header locale={locale} />
|
||||||
{/* Spacer to prevent navbar overlap */}
|
{/* Spacer to prevent navbar overlap */}
|
||||||
<div className="h-24 md:h-32" aria-hidden="true"></div>
|
<div className="h-24 md:h-32" aria-hidden="true"></div>
|
||||||
|
|||||||
@@ -214,12 +214,7 @@ export default function ActivityFeed({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a
|
<div className="flex gap-4 relative z-10">
|
||||||
href={data.music.url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="flex gap-4 relative z-10"
|
|
||||||
>
|
|
||||||
<div className="w-16 h-16 rounded-lg overflow-hidden shrink-0 shadow-md relative group-hover:shadow-xl transition-shadow duration-500">
|
<div className="w-16 h-16 rounded-lg overflow-hidden shrink-0 shadow-md relative group-hover:shadow-xl transition-shadow duration-500">
|
||||||
<Image
|
<Image
|
||||||
src={data.music.albumArt}
|
src={data.music.albumArt}
|
||||||
@@ -230,10 +225,10 @@ export default function ActivityFeed({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex flex-col justify-center">
|
<div className="min-w-0 flex flex-col justify-center">
|
||||||
<p className="font-bold text-[#1DB954] dark:text-[#1DB954] text-base truncate leading-tight mb-1 hover:underline">{data.music.track}</p>
|
<p className="font-bold text-[#1DB954] dark:text-[#1DB954] text-base truncate leading-tight mb-1">{data.music.track}</p>
|
||||||
<p className="text-sm text-stone-600 dark:text-white/60 truncate font-medium">{data.music.artist}</p>
|
<p className="text-sm text-stone-600 dark:text-white/60 truncate font-medium">{data.music.artist}</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</div>
|
||||||
{/* Subtle Spotify branding gradient */}
|
{/* Subtle Spotify branding gradient */}
|
||||||
<div className="absolute top-0 right-0 w-32 h-32 bg-[#1DB954]/5 blur-[60px] rounded-full -mr-16 -mt-16 pointer-events-none" />
|
<div className="absolute top-0 right-0 w-32 h-32 bg-[#1DB954]/5 blur-[60px] rounded-full -mr-16 -mt-16 pointer-events-none" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@@ -49,33 +49,23 @@ export default async function RootLayout({
|
|||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
metadataBase: new URL(getBaseUrl()),
|
metadataBase: new URL(getBaseUrl()),
|
||||||
title: {
|
title: {
|
||||||
default: "Dennis Konkol",
|
default: "Dennis Konkol | Portfolio",
|
||||||
template: "%s | dk0",
|
template: "%s | Dennis Konkol",
|
||||||
},
|
},
|
||||||
description:
|
description:
|
||||||
"Dennis Konkol – Software Engineer & Webentwickler in Osnabrück. Webentwicklung, Fullstack-Apps, Docker, Next.js, Flutter. Portfolio mit Projekten und Kontakt.",
|
"Portfolio of Dennis Konkol, a student and software engineer based in Osnabrück, Germany. Passionate about technology, coding, and solving real-world problems.",
|
||||||
keywords: [
|
keywords: [
|
||||||
"Dennis Konkol",
|
"Dennis Konkol",
|
||||||
"dk0",
|
|
||||||
"denshooter",
|
|
||||||
"Webentwicklung Osnabrück",
|
|
||||||
"Webentwicklung",
|
|
||||||
"Softwareentwicklung Osnabrück",
|
|
||||||
"Website erstellen Osnabrück",
|
|
||||||
"Web Design Osnabrück",
|
|
||||||
"Informatik Osnabrück",
|
|
||||||
"Software Engineer",
|
"Software Engineer",
|
||||||
"Full Stack Developer",
|
|
||||||
"Frontend Developer Osnabrück",
|
|
||||||
"Next.js",
|
|
||||||
"React",
|
|
||||||
"TypeScript",
|
|
||||||
"Flutter",
|
|
||||||
"Docker",
|
|
||||||
"Self-Hosting",
|
|
||||||
"DevOps",
|
|
||||||
"Portfolio",
|
"Portfolio",
|
||||||
|
"Student",
|
||||||
|
"Web Development",
|
||||||
|
"Full Stack Developer",
|
||||||
"Osnabrück",
|
"Osnabrück",
|
||||||
|
"Germany",
|
||||||
|
"React",
|
||||||
|
"Next.js",
|
||||||
|
"TypeScript",
|
||||||
],
|
],
|
||||||
authors: [{ name: "Dennis Konkol", url: "https://dk0.dev" }],
|
authors: [{ name: "Dennis Konkol", url: "https://dk0.dev" }],
|
||||||
creator: "Dennis Konkol",
|
creator: "Dennis Konkol",
|
||||||
@@ -92,27 +82,26 @@ export const metadata: Metadata = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: "Dennis Konkol",
|
title: "Dennis Konkol | Portfolio",
|
||||||
description:
|
description:
|
||||||
"Software Engineer & Webentwickler in Osnabrück. Next.js, Flutter, Docker, DevOps. Projekte ansehen und Kontakt aufnehmen.",
|
"Explore my projects and contact me for collaboration opportunities!",
|
||||||
url: "https://dk0.dev",
|
url: "https://dk0.dev",
|
||||||
siteName: "Dennis Konkol",
|
siteName: "Dennis Konkol Portfolio",
|
||||||
images: [
|
images: [
|
||||||
{
|
{
|
||||||
url: "https://dk0.dev/api/og",
|
url: "https://dk0.dev/api/og",
|
||||||
width: 1200,
|
width: 1200,
|
||||||
height: 630,
|
height: 630,
|
||||||
alt: "Dennis Konkol",
|
alt: "Dennis Konkol Portfolio",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
locale: "de_DE",
|
locale: "en_US",
|
||||||
alternateLocale: ["en_US"],
|
|
||||||
type: "website",
|
type: "website",
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: "summary_large_image",
|
card: "summary_large_image",
|
||||||
title: "Dennis Konkol",
|
title: "Dennis Konkol | Portfolio",
|
||||||
description: "Software Engineer & Webentwickler in Osnabrück.",
|
description: "Student & Software Engineer based in Osnabrück, Germany.",
|
||||||
images: ["https://dk0.dev/api/og"],
|
images: ["https://dk0.dev/api/og"],
|
||||||
creator: "@denshooter",
|
creator: "@denshooter",
|
||||||
},
|
},
|
||||||
@@ -121,9 +110,5 @@ export const metadata: Metadata = {
|
|||||||
},
|
},
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: "https://dk0.dev",
|
canonical: "https://dk0.dev",
|
||||||
languages: {
|
|
||||||
de: "https://dk0.dev/de",
|
|
||||||
en: "https://dk0.dev/en",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
1
discord-presence-bot/.gitignore
vendored
1
discord-presence-bot/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
node_modules/
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
FROM node:25-alpine
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY package.json package-lock.json* ./
|
|
||||||
RUN npm ci --only=production && npm cache clean --force
|
|
||||||
|
|
||||||
COPY index.js .
|
|
||||||
|
|
||||||
USER node
|
|
||||||
|
|
||||||
EXPOSE 3001
|
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
|
||||||
CMD wget -qO- http://localhost:3001/presence || exit 1
|
|
||||||
|
|
||||||
CMD ["node", "index.js"]
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
const { Client, GatewayIntentBits, ActivityType } = require("discord.js");
|
|
||||||
const http = require("http");
|
|
||||||
|
|
||||||
const TOKEN = process.env.DISCORD_BOT_TOKEN;
|
|
||||||
const TARGET_USER_ID = process.env.DISCORD_USER_ID || "172037532370862080";
|
|
||||||
const PORT = parseInt(process.env.BOT_PORT || "3001", 10);
|
|
||||||
|
|
||||||
if (!TOKEN) {
|
|
||||||
console.error("DISCORD_BOT_TOKEN is required");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = new Client({
|
|
||||||
intents: [
|
|
||||||
GatewayIntentBits.Guilds,
|
|
||||||
GatewayIntentBits.GuildPresences,
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
let cachedData = {
|
|
||||||
discord_status: "offline",
|
|
||||||
listening_to_spotify: false,
|
|
||||||
spotify: null,
|
|
||||||
activities: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
function updatePresence(guild) {
|
|
||||||
const member = guild.members.cache.get(TARGET_USER_ID);
|
|
||||||
if (!member || !member.presence) return;
|
|
||||||
|
|
||||||
const presence = member.presence;
|
|
||||||
cachedData.discord_status = presence.status || "offline";
|
|
||||||
|
|
||||||
cachedData.activities = presence.activities
|
|
||||||
? presence.activities
|
|
||||||
.filter((a) => a.type !== ActivityType.Custom)
|
|
||||||
.map((a) => ({
|
|
||||||
name: a.name,
|
|
||||||
type: a.type,
|
|
||||||
details: a.details || null,
|
|
||||||
state: a.state || null,
|
|
||||||
assets: a.assets
|
|
||||||
? {
|
|
||||||
large_image: a.assets.largeImage || null,
|
|
||||||
large_text: a.assets.largeText || null,
|
|
||||||
small_image: a.assets.smallImage || null,
|
|
||||||
small_text: a.assets.smallText || null,
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
timestamps: a.timestamps
|
|
||||||
? {
|
|
||||||
start: a.timestamps.start?.toISOString() || null,
|
|
||||||
end: a.timestamps.end?.toISOString() || null,
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const spotifyActivity = presence.activities
|
|
||||||
? presence.activities.find((a) => a.type === ActivityType.Listening && a.name === "Spotify")
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (spotifyActivity && spotifyActivity.syncId) {
|
|
||||||
cachedData.listening_to_spotify = true;
|
|
||||||
cachedData.spotify = {
|
|
||||||
song: spotifyActivity.details || "",
|
|
||||||
artist: spotifyActivity.state ? spotifyActivity.state.replace(/; /g, "; ") : "",
|
|
||||||
album: spotifyActivity.assets?.largeText || "",
|
|
||||||
album_art_url: spotifyActivity.assets?.largeImage
|
|
||||||
? `https://i.scdn.co/image/${spotifyActivity.assets.largeImage.replace("spotify:", "")}`
|
|
||||||
: null,
|
|
||||||
track_id: spotifyActivity.syncId || null,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
cachedData.listening_to_spotify = false;
|
|
||||||
cachedData.spotify = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateAll() {
|
|
||||||
for (const guild of client.guilds.cache.values()) {
|
|
||||||
updatePresence(guild);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.on("ready", () => {
|
|
||||||
console.log(`Bot online as ${client.user.tag}`);
|
|
||||||
client.user.setActivity("Watching Presence", { type: ActivityType.Watching });
|
|
||||||
updateAll();
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("presenceUpdate", () => {
|
|
||||||
updateAll();
|
|
||||||
});
|
|
||||||
|
|
||||||
const server = http.createServer((req, res) => {
|
|
||||||
if (req.method === "GET" && req.url === "/presence") {
|
|
||||||
res.writeHead(200, { "Content-Type": "application/json" });
|
|
||||||
res.end(JSON.stringify({ data: cachedData }));
|
|
||||||
} else {
|
|
||||||
res.writeHead(404);
|
|
||||||
res.end("Not found");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.listen(PORT, () => {
|
|
||||||
console.log(`HTTP endpoint listening on port ${PORT}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
client.login(TOKEN);
|
|
||||||
324
discord-presence-bot/package-lock.json
generated
324
discord-presence-bot/package-lock.json
generated
@@ -1,324 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "discord-presence-bot",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"name": "discord-presence-bot",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"dependencies": {
|
|
||||||
"discord.js": "^14.18.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@discordjs/builders": {
|
|
||||||
"version": "1.14.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.14.1.tgz",
|
|
||||||
"integrity": "sha512-gSKkhXLqs96TCzk66VZuHHl8z2bQMJFGwrXC0f33ngK+FLNau4hU1PYny3DNJfNdSH+gVMzE85/d5FQ2BpcNwQ==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@discordjs/formatters": "^0.6.2",
|
|
||||||
"@discordjs/util": "^1.2.0",
|
|
||||||
"@sapphire/shapeshift": "^4.0.0",
|
|
||||||
"discord-api-types": "^0.38.40",
|
|
||||||
"fast-deep-equal": "^3.1.3",
|
|
||||||
"ts-mixer": "^6.0.4",
|
|
||||||
"tslib": "^2.6.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16.11.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@discordjs/collection": {
|
|
||||||
"version": "1.5.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz",
|
|
||||||
"integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16.11.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@discordjs/formatters": {
|
|
||||||
"version": "0.6.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz",
|
|
||||||
"integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"discord-api-types": "^0.38.33"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16.11.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@discordjs/rest": {
|
|
||||||
"version": "2.6.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.1.tgz",
|
|
||||||
"integrity": "sha512-wwQdgjeaoYFiaG+atbqx6aJDpqW7JHAo0HrQkBTbYzM3/PJ3GweQIpgElNcGZ26DCUOXMyawYd0YF7vtr+fZXg==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@discordjs/collection": "^2.1.1",
|
|
||||||
"@discordjs/util": "^1.2.0",
|
|
||||||
"@sapphire/async-queue": "^1.5.3",
|
|
||||||
"@sapphire/snowflake": "^3.5.5",
|
|
||||||
"@vladfrangu/async_event_emitter": "^2.4.6",
|
|
||||||
"discord-api-types": "^0.38.40",
|
|
||||||
"magic-bytes.js": "^1.13.0",
|
|
||||||
"tslib": "^2.6.3",
|
|
||||||
"undici": "6.24.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@discordjs/rest/node_modules/@discordjs/collection": {
|
|
||||||
"version": "2.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
|
|
||||||
"integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@discordjs/rest/node_modules/@sapphire/snowflake": {
|
|
||||||
"version": "3.5.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.5.tgz",
|
|
||||||
"integrity": "sha512-xzvBr1Q1c4lCe7i6sRnrofxeO1QTP/LKQ6A6qy0iB4x5yfiSfARMEQEghojzTNALDTcv8En04qYNIco9/K9eZQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=v14.0.0",
|
|
||||||
"npm": ">=7.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@discordjs/util": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz",
|
|
||||||
"integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"discord-api-types": "^0.38.33"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@discordjs/ws": {
|
|
||||||
"version": "1.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz",
|
|
||||||
"integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@discordjs/collection": "^2.1.0",
|
|
||||||
"@discordjs/rest": "^2.5.1",
|
|
||||||
"@discordjs/util": "^1.1.0",
|
|
||||||
"@sapphire/async-queue": "^1.5.2",
|
|
||||||
"@types/ws": "^8.5.10",
|
|
||||||
"@vladfrangu/async_event_emitter": "^2.2.4",
|
|
||||||
"discord-api-types": "^0.38.1",
|
|
||||||
"tslib": "^2.6.2",
|
|
||||||
"ws": "^8.17.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16.11.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@discordjs/ws/node_modules/@discordjs/collection": {
|
|
||||||
"version": "2.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
|
|
||||||
"integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sapphire/async-queue": {
|
|
||||||
"version": "1.5.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz",
|
|
||||||
"integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=v14.0.0",
|
|
||||||
"npm": ">=7.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sapphire/shapeshift": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"fast-deep-equal": "^3.1.3",
|
|
||||||
"lodash": "^4.17.21"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=v16"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sapphire/snowflake": {
|
|
||||||
"version": "3.5.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz",
|
|
||||||
"integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=v14.0.0",
|
|
||||||
"npm": ">=7.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/node": {
|
|
||||||
"version": "25.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
|
|
||||||
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"undici-types": "~7.19.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/ws": {
|
|
||||||
"version": "8.18.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
|
||||||
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@vladfrangu/async_event_emitter": {
|
|
||||||
"version": "2.4.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz",
|
|
||||||
"integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=v14.0.0",
|
|
||||||
"npm": ">=7.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/discord-api-types": {
|
|
||||||
"version": "0.38.47",
|
|
||||||
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.47.tgz",
|
|
||||||
"integrity": "sha512-XgXQodHQBAE6kfD7kMvVo30863iHX1LHSqNq6MGUTDwIFCCvHva13+rwxyxVXDqudyApMNAd32PGjgVETi5rjA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"workspaces": [
|
|
||||||
"scripts/actions/documentation"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/discord.js": {
|
|
||||||
"version": "14.26.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.26.3.tgz",
|
|
||||||
"integrity": "sha512-XEKtYn28YFsiJ5l4fLRyikdbo6RD5oFyqfVHQlvXz2104JhH/E8slN28dbky05w3DCrJcNVWvhVvcJCTSl/KIg==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@discordjs/builders": "^1.14.1",
|
|
||||||
"@discordjs/collection": "1.5.3",
|
|
||||||
"@discordjs/formatters": "^0.6.2",
|
|
||||||
"@discordjs/rest": "^2.6.1",
|
|
||||||
"@discordjs/util": "^1.2.0",
|
|
||||||
"@discordjs/ws": "^1.2.3",
|
|
||||||
"@sapphire/snowflake": "3.5.3",
|
|
||||||
"discord-api-types": "^0.38.40",
|
|
||||||
"fast-deep-equal": "3.1.3",
|
|
||||||
"lodash.snakecase": "4.1.1",
|
|
||||||
"magic-bytes.js": "^1.13.0",
|
|
||||||
"tslib": "^2.6.3",
|
|
||||||
"undici": "6.24.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fast-deep-equal": {
|
|
||||||
"version": "3.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/lodash": {
|
|
||||||
"version": "4.18.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
|
|
||||||
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/lodash.snakecase": {
|
|
||||||
"version": "4.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
|
|
||||||
"integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/magic-bytes.js": {
|
|
||||||
"version": "1.13.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.13.0.tgz",
|
|
||||||
"integrity": "sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/ts-mixer": {
|
|
||||||
"version": "6.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz",
|
|
||||||
"integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/tslib": {
|
|
||||||
"version": "2.8.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
|
||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
|
||||||
"license": "0BSD"
|
|
||||||
},
|
|
||||||
"node_modules/undici": {
|
|
||||||
"version": "6.24.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz",
|
|
||||||
"integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.17"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/undici-types": {
|
|
||||||
"version": "7.19.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
|
|
||||||
"integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/ws": {
|
|
||||||
"version": "8.20.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
|
|
||||||
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"bufferutil": "^4.0.1",
|
|
||||||
"utf-8-validate": ">=5.0.2"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"bufferutil": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"utf-8-validate": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "discord-presence-bot",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"private": true,
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"start": "node index.js"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"discord.js": "^14.18.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -103,33 +103,6 @@ services:
|
|||||||
memory: 128M
|
memory: 128M
|
||||||
cpus: '0.1'
|
cpus: '0.1'
|
||||||
|
|
||||||
discord-bot:
|
|
||||||
build:
|
|
||||||
context: ./discord-presence-bot
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
container_name: portfolio-discord-bot
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
- DISCORD_BOT_TOKEN=${DISCORD_BOT_TOKEN}
|
|
||||||
- DISCORD_USER_ID=${DISCORD_USER_ID:-172037532370862080}
|
|
||||||
- BOT_PORT=3001
|
|
||||||
networks:
|
|
||||||
- portfolio_net
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "wget", "-qO-", "http://localhost:3001/presence"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 3
|
|
||||||
start_period: 15s
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 128M
|
|
||||||
cpus: '0.25'
|
|
||||||
reservations:
|
|
||||||
memory: 64M
|
|
||||||
cpus: '0.1'
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
portfolio_data:
|
portfolio_data:
|
||||||
driver: local
|
driver: local
|
||||||
|
|||||||
@@ -87,33 +87,6 @@ services:
|
|||||||
retries: 5
|
retries: 5
|
||||||
start_period: 30s
|
start_period: 30s
|
||||||
|
|
||||||
discord-bot:
|
|
||||||
build:
|
|
||||||
context: ./discord-presence-bot
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
container_name: portfolio-discord-bot
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
- DISCORD_BOT_TOKEN=${DISCORD_BOT_TOKEN}
|
|
||||||
- DISCORD_USER_ID=${DISCORD_USER_ID:-172037532370862080}
|
|
||||||
- BOT_PORT=3001
|
|
||||||
networks:
|
|
||||||
- portfolio_net
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "wget", "-qO-", "http://localhost:3001/presence"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 3
|
|
||||||
start_period: 15s
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 128M
|
|
||||||
cpus: '0.25'
|
|
||||||
reservations:
|
|
||||||
memory: 64M
|
|
||||||
cpus: '0.1'
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
portfolio_data:
|
portfolio_data:
|
||||||
driver: local
|
driver: local
|
||||||
|
|||||||
@@ -30,10 +30,6 @@ N8N_WEBHOOK_URL=https://n8n.dk0.dev
|
|||||||
N8N_SECRET_TOKEN=your-n8n-secret-token
|
N8N_SECRET_TOKEN=your-n8n-secret-token
|
||||||
N8N_API_KEY=your-n8n-api-key
|
N8N_API_KEY=your-n8n-api-key
|
||||||
|
|
||||||
# Discord Presence Bot (replaces Lanyard)
|
|
||||||
DISCORD_BOT_TOKEN=your-discord-bot-token
|
|
||||||
DISCORD_USER_ID=172037532370862080
|
|
||||||
|
|
||||||
# Directus CMS (for i18n messages & content pages)
|
# Directus CMS (for i18n messages & content pages)
|
||||||
DIRECTUS_URL=https://cms.dk0.dev
|
DIRECTUS_URL=https://cms.dk0.dev
|
||||||
DIRECTUS_STATIC_TOKEN=your-static-token-here
|
DIRECTUS_STATIC_TOKEN=your-static-token-here
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ const eslintConfig = [
|
|||||||
"coverage/**",
|
"coverage/**",
|
||||||
"scripts/**",
|
"scripts/**",
|
||||||
"next-env.d.ts",
|
"next-env.d.ts",
|
||||||
"discord-presence-bot/**",
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
...compat.extends("next/core-web-vitals", "next/typescript"),
|
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
"f2": "Docker Swarm & CI/CD",
|
"f2": "Docker Swarm & CI/CD",
|
||||||
"f3": "Self-Hosted Infrastruktur"
|
"f3": "Self-Hosted Infrastruktur"
|
||||||
},
|
},
|
||||||
"description": "Ich bin Dennis Konkol, Informatik-Student und Webentwickler aus Osnabrück. Ich entwickle Fullstack-Apps mit Next.js und Flutter und betreibe meine eigene Infrastruktur mit Docker und CI/CD.",
|
"description": "Ich bin Dennis, Student aus Osnabrück und leidenschaftlicher Selfhoster. Ich entwickle Fullstack Apps und sorge am liebsten selbst dafür, dass sie auf meiner eigenen Infrastruktur perfekt laufen.",
|
||||||
"ctaWork": "Meine Projekte",
|
"ctaWork": "Meine Projekte",
|
||||||
"ctaContact": "Kontakt"
|
"ctaContact": "Kontakt"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
"f2": "Docker Swarm & CI/CD",
|
"f2": "Docker Swarm & CI/CD",
|
||||||
"f3": "Self-Hosted Infrastructure"
|
"f3": "Self-Hosted Infrastructure"
|
||||||
},
|
},
|
||||||
"description": "I'm Dennis Konkol, a computer science student and web developer from Osnabrück, Germany. I build fullstack apps with Next.js and Flutter and love running my own infrastructure with Docker and CI/CD.",
|
"description": "I'm Dennis, a student from Germany and a passionate selfhoster. I build fullstack applications and love the challenge of managing the infrastructure they run on.",
|
||||||
"ctaWork": "View Projects",
|
"ctaWork": "View Projects",
|
||||||
"ctaContact": "Get in touch"
|
"ctaContact": "Get in touch"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -93,7 +93,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"url": "http://discord-bot:3001/presence",
|
"url": "https://api.lanyard.rest/v1/users/172037532370862080",
|
||||||
"options": {}
|
"options": {}
|
||||||
},
|
},
|
||||||
"type": "n8n-nodes-base.httpRequest",
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
|||||||
Reference in New Issue
Block a user