Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
"name": "Open Home Foundation - API",
"image": "mcr.microsoft.com/devcontainers/typescript-node:24-bookworm",
"forwardPorts": [
443
3000
],
"portsAttributes": {
"443": {
"label": "HTTPS server"
"3000": {
"label": "web-api (HTTP)"
}
},
"customizations": {
Expand All @@ -31,4 +31,4 @@
},
"postCreateCommand": "pnpm install",
"remoteUser": "node"
}
}
14 changes: 14 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# YouTube Data API v3 key (https://console.cloud.google.com/apis/credentials)
YOUTUBE_API_KEY=

# Publicly reachable base URL of this service, used as the WebSub callback
# target for YouTube push notifications (e.g. https://api.openhomefoundation.org).
# Leave blank in local dev without a public tunnel; status will still update via
# the periodic reconcile.
PUBLIC_BASE_URL=

# Shared secret for verifying WebSub push payloads (HMAC signature). Required
# for push notifications: without it the service skips subscriptions and the
# /pubsub webhook rejects all incoming notifications (403). Generate one with
# `openssl rand -hex 32`.
PUBSUB_SECRET=
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules/
dist/
*.log
.DS_Store
.env
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
},
"dependencies": {
"@nestjs/common": "^11.1.19",
"@nestjs/config": "^4.0.4",
"@nestjs/core": "^11.1.19",
"@nestjs/platform-express": "^11.1.19",
"@nestjs/platform-socket.io": "^11.1.19",
Expand Down
37 changes: 37 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AppController } from './app.controller';
import { AppGateway } from './app.gateway';
import { HealthModule } from './health';
import { getVersionInfo } from './health/version';
import { LivestreamModule } from './livestream';

@Module({
imports: [HealthModule.register({ version: getVersionInfo() })],
imports: [
ConfigModule.forRoot({ isGlobal: true }),
HealthModule.register({ version: getVersionInfo() }),
LivestreamModule,
],
controllers: [AppController],
providers: [AppGateway],
})
Expand Down
4 changes: 4 additions & 0 deletions src/livestream/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './livestream.channels';
export * from './livestream.module';
export * from './livestream.service';
export * from './pubsub.service';
36 changes: 36 additions & 0 deletions src/livestream/livestream.channels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export interface Channel {
/** URL-safe identifier used in the API path, e.g. "home-assistant". */
slug: string;
/** Human-friendly display name. */
name: string;
/** YouTube handle without the leading "@". */
handle: string;
}

/**
* Open Home Foundation project channels tracked for livestreams.
* Add a new project by appending an entry here — no other code changes needed.
*/
export const CHANNELS: readonly Channel[] = [
{ slug: 'home-assistant', name: 'Home Assistant', handle: 'home_assistant' },
{ slug: 'esphome', name: 'ESPHome', handle: 'esphomeio' },
{
slug: 'open-home-foundation',
name: 'Open Home Foundation',
handle: 'OpenHomeFndn',
},
{
slug: 'music-assistant',
name: 'Music Assistant',
handle: 'musicassistantio',
},
];

/**
* Canonical YouTube channel RSS feed URL, used both as the discovery source and
* as the WebSub (PubSubHubbub) subscription topic — keep a single form so the
* two code paths cannot drift. This is the URL documented for the hub topic:
* https://developers.google.com/youtube/v3/guides/push_notifications
*/
export const feedUrl = (channelId: string): string =>
`https://www.youtube.com/feeds/videos.xml?channel_id=${channelId}`;
18 changes: 18 additions & 0 deletions src/livestream/livestream.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Controller, Get, Param } from '@nestjs/common';

import { LivestreamInfo, LivestreamService } from './livestream.service';

@Controller('livestream')
export class LivestreamController {
constructor(private readonly livestream: LivestreamService) {}

@Get()
getAll(): LivestreamInfo[] {
return this.livestream.getAll();
}

@Get(':slug')
getStatus(@Param('slug') slug: string): LivestreamInfo {
return this.livestream.getStatus(slug);
}
}
12 changes: 12 additions & 0 deletions src/livestream/livestream.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';

import { LivestreamController } from './livestream.controller';
import { LivestreamService } from './livestream.service';
import { PubSubController } from './pubsub.controller';
import { PubSubService } from './pubsub.service';

@Module({
controllers: [LivestreamController, PubSubController],
providers: [LivestreamService, PubSubService],
})
export class LivestreamModule {}
Loading