Skip to content

Commit ab10c11

Browse files
authored
Merge pull request #56 from Priveetee/dev
feat: ship remote login backend
2 parents dc291c6 + 623ea8c commit ab10c11

55 files changed

Lines changed: 1752 additions & 106 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.env.example‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,10 @@ DATABASE_PASSWORD=typetype
55
DRAGONFLY_URL=redis://dragonfly:6379
66
DOWNLOADER_SERVICE_URL=http://typetype-downloader:18093
77
YOUTUBE_SESSION_ENCRYPTION_KEY=replace-with-at-least-32-random-characters
8+
YOUTUBE_REMOTE_LOGIN_SERVICE_URL=http://typetype-token:8081
9+
YOUTUBE_REMOTE_LOGIN_CALLBACK_BASE_URL=http://typetype-server:8080
10+
YOUTUBE_REMOTE_LOGIN_INTERNAL_TOKEN=replace-with-shared-internal-token
11+
YOUTUBE_REMOTE_LOGIN_TTL_MS=480000
12+
YOUTUBE_REMOTE_LOGIN_MAX_SESSIONS=2
813

914
ALLOWED_ORIGINS=http://localhost:5173

‎build.gradle.kts‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
plugins {
2-
kotlin("jvm") version "2.3.21"
3-
kotlin("plugin.serialization") version "2.3.21"
2+
kotlin("jvm") version "2.4.0"
3+
kotlin("plugin.serialization") version "2.4.0"
44
id("io.ktor.plugin") version "3.5.0"
55
id("jacoco")
66
}
77

88
apply(from = "gradle/openapi-validation.gradle.kts")
9-
109
group = "dev.typetype"
1110
version = "0.0.1"
12-
1311
application {
1412
mainClass.set("dev.typetype.server.ApplicationKt")
1513
}
@@ -27,11 +25,12 @@ dependencies {
2725
implementation("io.ktor:ktor-serialization-kotlinx-json-jvm")
2826
implementation("io.ktor:ktor-server-cors-jvm")
2927
implementation("io.ktor:ktor-server-compression-jvm")
28+
implementation("io.ktor:ktor-server-websockets-jvm")
3029
implementation("io.ktor:ktor-server-status-pages-jvm")
3130
implementation("io.ktor:ktor-server-call-logging-jvm")
3231
implementation("io.ktor:ktor-server-rate-limit-jvm")
3332
implementation("ch.qos.logback:logback-classic:1.5.34")
34-
implementation("com.github.InfinityLoop1308.PipePipeExtractor:extractor:3c8feae0548ba7e30bce92f881a1924121c3dc83")
33+
implementation("com.github.InfinityLoop1308.PipePipeExtractor:extractor:2a4a333c0e64a96343d35981fd5e75fd122526c2")
3534
implementation("org.json:json:20250517")
3635
implementation("com.squareup.okhttp3:okhttp:5.3.2")
3736
implementation("io.lettuce:lettuce-core:7.6.0.RELEASE")

‎docker-compose.dev-mirror.yml‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ services:
1818
ALLOWED_ORIGINS: http://localhost:28082,http://127.0.0.1:28082
1919
SUBTITLE_SERVICE_URL: http://typetype-token:8081
2020
DOWNLOADER_SERVICE_URL: http://typetype-downloader:18093
21+
YOUTUBE_SESSION_ENCRYPTION_KEY: beta-youtube-session-key-change-before-prod
22+
YOUTUBE_REMOTE_LOGIN_SERVICE_URL: http://typetype-token:8081
23+
YOUTUBE_REMOTE_LOGIN_CALLBACK_BASE_URL: http://typetype-server:8080
24+
YOUTUBE_REMOTE_LOGIN_INTERNAL_TOKEN: beta-remote-login-internal-token
25+
YOUTUBE_REMOTE_LOGIN_TTL_MS: "480000"
26+
YOUTUBE_REMOTE_LOGIN_MAX_SESSIONS: "2"
2127
depends_on:
2228
postgres-init: { condition: service_completed_successfully }
2329
dragonfly: { condition: service_started }
@@ -56,12 +62,19 @@ services:
5662
typetype-token: { condition: service_started }
5763
restart: unless-stopped
5864
typetype-token:
59-
image: ghcr.io/priveetee/typetype-token:latest
65+
image: ghcr.io/priveetee/typetype-token-beta:beta
6066
pull_policy: always
6167
ports: ["28081:8081"]
6268
init: true
6369
ipc: host
64-
environment: ["NODE_ENV=production"]
70+
environment:
71+
NODE_ENV: production
72+
YOUTUBE_REMOTE_LOGIN_ENABLED: "true"
73+
YOUTUBE_REMOTE_LOGIN_INTERNAL_TOKEN: beta-remote-login-internal-token
74+
YOUTUBE_REMOTE_LOGIN_CALLBACK_ORIGIN: http://typetype-server:8080
75+
YOUTUBE_REMOTE_LOGIN_MAX_SESSIONS: "2"
76+
YOUTUBE_REMOTE_LOGIN_FRAME_FPS: "10"
77+
YOUTUBE_REMOTE_LOGIN_MAX_FRAME_BYTES: "524288"
6578
restart: unless-stopped
6679
postgres:
6780
image: postgres:17

‎openapi.yaml‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ tags:
1212
- name: health
1313
- name: extraction
1414
- name: downloader
15+
- name: youtube-session
1516
paths:
1617
/health:
1718
$ref: ./openapi/paths/health.yaml#/Health
@@ -51,6 +52,14 @@ paths:
5152
$ref: ./openapi/paths/downloader.yaml#/JobArtifact
5253
/downloader/jobs/{id}/cancel:
5354
$ref: ./openapi/paths/downloader.yaml#/JobCancel
55+
/youtube-session/browser/start:
56+
$ref: ./openapi/paths/youtube-session.yaml#/BrowserStart
57+
/youtube-session/browser/{sessionId}:
58+
$ref: ./openapi/paths/youtube-session.yaml#/BrowserSession
59+
/internal/youtube-remote-login/callback:
60+
$ref: ./openapi/paths/youtube-session.yaml#/InternalBrowserComplete
61+
/internal/youtube-session/browser/complete:
62+
$ref: ./openapi/paths/youtube-session.yaml#/InternalBrowserComplete
5463
components:
5564
schemas:
5665
ErrorResponse:
@@ -87,3 +96,9 @@ components:
8796
$ref: ./openapi/components/media.yaml#/CommentsPageResponse
8897
DownloadJob:
8998
$ref: ./openapi/components/downloader.yaml#/DownloadJob
99+
YoutubeRemoteBrowserStartRequest:
100+
$ref: ./openapi/components/youtube-session.yaml#/YoutubeRemoteBrowserStartRequest
101+
YoutubeRemoteBrowserStartResponse:
102+
$ref: ./openapi/components/youtube-session.yaml#/YoutubeRemoteBrowserStartResponse
103+
YoutubeRemoteBrowserCompleteRequest:
104+
$ref: ./openapi/components/youtube-session.yaml#/YoutubeRemoteBrowserCompleteRequest
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
YoutubeRemoteBrowserStartRequest:
2+
type: object
3+
properties:
4+
returnTo:
5+
type: string
6+
nullable: true
7+
maxLength: 2048
8+
YoutubeRemoteBrowserStartResponse:
9+
type: object
10+
required: [sessionId, wsUrl, expiresAt]
11+
properties:
12+
sessionId: { type: string }
13+
wsUrl: { type: string }
14+
expiresAt: { type: integer, format: int64 }
15+
YoutubeRemoteBrowserCompleteRequest:
16+
type: object
17+
required: [sessionId, tokenSessionId, status, cookies, poToken, capturedAt]
18+
properties:
19+
sessionId: { type: string }
20+
tokenSessionId: { type: string }
21+
status:
22+
type: string
23+
enum: [completed]
24+
cookies: { type: string }
25+
poToken: { type: string }
26+
capturedAt: { type: integer, format: int64 }

‎openapi/paths/youtube-session.yaml‎

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
BrowserStart:
2+
post:
3+
tags: [youtube-session]
4+
summary: Start a temporary remote Chromium login session
5+
requestBody:
6+
required: false
7+
content:
8+
application/json:
9+
schema:
10+
type: object
11+
properties:
12+
returnTo:
13+
type: string
14+
nullable: true
15+
maxLength: 2048
16+
responses:
17+
'201':
18+
description: Remote browser session created.
19+
content:
20+
application/json:
21+
schema:
22+
type: object
23+
required: [sessionId, wsUrl, expiresAt]
24+
properties:
25+
sessionId: { type: string }
26+
wsUrl: { type: string }
27+
expiresAt: { type: integer, format: int64 }
28+
'401': { $ref: '#/components/responses/JsonError' }
29+
'403': { $ref: '#/components/responses/JsonError' }
30+
'409': { $ref: '#/components/responses/JsonError' }
31+
'429': { $ref: '#/components/responses/JsonError' }
32+
'503': { $ref: '#/components/responses/JsonError' }
33+
BrowserSession:
34+
parameters:
35+
- name: sessionId
36+
in: path
37+
required: true
38+
schema: { type: string }
39+
get:
40+
tags: [youtube-session]
41+
summary: WebSocket endpoint for remote browser frames and input
42+
parameters:
43+
- name: token
44+
in: query
45+
required: true
46+
schema: { type: string }
47+
responses:
48+
'101': { description: WebSocket upgrade accepted. }
49+
'401': { $ref: '#/components/responses/JsonError' }
50+
delete:
51+
tags: [youtube-session]
52+
summary: Cancel a temporary remote browser login session
53+
responses:
54+
'204': { description: Remote browser session cancelled. }
55+
'401': { $ref: '#/components/responses/JsonError' }
56+
'404': { $ref: '#/components/responses/JsonError' }
57+
InternalBrowserComplete:
58+
post:
59+
tags: [youtube-session]
60+
summary: Internal callback used by TypeType-Token after capture
61+
parameters:
62+
- name: X-Internal-Token
63+
in: header
64+
required: true
65+
schema: { type: string }
66+
requestBody:
67+
required: true
68+
content:
69+
application/json:
70+
schema:
71+
type: object
72+
required: [sessionId, tokenSessionId, status, cookies, poToken, capturedAt]
73+
properties:
74+
sessionId: { type: string }
75+
tokenSessionId: { type: string }
76+
status:
77+
type: string
78+
enum: [completed]
79+
cookies: { type: string }
80+
poToken: { type: string }
81+
capturedAt: { type: integer, format: int64 }
82+
responses:
83+
'204': { description: YouTube credentials stored. }
84+
'400': { $ref: '#/components/responses/JsonError' }
85+
'401': { $ref: '#/components/responses/JsonError' }
86+
'404': { $ref: '#/components/responses/JsonError' }
87+
components:
88+
schemas:
89+
ErrorResponse:
90+
type: object
91+
required: [error]
92+
properties:
93+
error: { type: string }
94+
code: { type: string, default: error }
95+
requestId: { type: string, nullable: true }
96+
headers:
97+
RequestIdHeader:
98+
description: Request correlation id returned by TypeType-Server.
99+
schema: { type: string }
100+
responses:
101+
JsonError:
102+
description: Request failed.
103+
headers:
104+
X-Request-ID:
105+
$ref: '#/components/headers/RequestIdHeader'
106+
content:
107+
application/json:
108+
schema:
109+
$ref: '#/components/schemas/ErrorResponse'

‎src/main/kotlin/dev/typetype/server/Application.kt‎

Lines changed: 34 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,6 @@ package dev.typetype.server
22
import dev.typetype.server.cache.DragonflyService
33
import dev.typetype.server.db.DatabaseFactory
44
import dev.typetype.server.downloader.OkHttpDownloader
5-
import dev.typetype.server.routes.avatarRoutes
6-
import dev.typetype.server.routes.bulletCommentRoutes
7-
import dev.typetype.server.routes.channelRoutes
8-
import dev.typetype.server.routes.commentRoutes
9-
import dev.typetype.server.routes.downloaderGatewayRoutes
10-
import dev.typetype.server.routes.internalObservabilityRoutes
11-
import dev.typetype.server.routes.manifestRoutes
12-
import dev.typetype.server.routes.nicoVideoProxyRoutes
13-
import dev.typetype.server.routes.oidcAuthRoutes
14-
import dev.typetype.server.routes.podcastRoutes
15-
import dev.typetype.server.routes.proxyRoutes
16-
import dev.typetype.server.routes.publicPlaylistRoutes
17-
import dev.typetype.server.routes.storyboardProxyRoutes
18-
import dev.typetype.server.routes.searchRoutes
19-
import dev.typetype.server.routes.streamRoutes
20-
import dev.typetype.server.routes.suggestionRoutes
21-
import dev.typetype.server.routes.adminSessionRoutes
22-
import dev.typetype.server.routes.adminRoutes
23-
import dev.typetype.server.routes.adminBugReportRoutes
24-
import dev.typetype.server.routes.authRoutes
25-
import dev.typetype.server.routes.trendingRoutes
26-
import dev.typetype.server.routes.publicMetadataRoutes
27-
import dev.typetype.server.routes.sessionActivityRoutes
28-
import dev.typetype.server.routes.userDataRoutes
295
import dev.typetype.server.services.ActiveSessionService
306
import dev.typetype.server.services.AuthService
317
import dev.typetype.server.services.AdminSettingsService
@@ -40,11 +16,12 @@ import dev.typetype.server.services.InstanceService
4016
import dev.typetype.server.services.InternalHealthService
4117
import dev.typetype.server.services.OidcAuthService
4218
import dev.typetype.server.services.OidcConfigLoader
19+
import dev.typetype.server.services.OkHttpYoutubeRemoteBrowserClient
4320
import dev.typetype.server.services.UserAdminService
21+
import dev.typetype.server.services.YoutubeRemoteBrowserConfig
22+
import dev.typetype.server.services.YoutubeRemoteBrowserService
4423
import io.ktor.server.application.Application
4524
import io.ktor.server.netty.EngineMain
46-
import io.ktor.server.plugins.ratelimit.rateLimit
47-
import io.ktor.server.routing.routing
4825
import org.schabi.newpipe.extractor.NewPipe
4926
import java.util.UUID
5027

@@ -67,53 +44,47 @@ fun Application.module() {
6744
val gitHubIssueService = GitHubIssueService()
6845
val adminSettingsService = AdminSettingsService()
6946
val activeSessionService = ActiveSessionService(adminSettingsService)
70-
val instanceService = InstanceService(authService, adminSettingsService, oidcAuthService::publicConfig)
7147
val restoreService = PipePipeBackupImporterService()
7248
val cacheUrl = System.getenv("DRAGONFLY_URL") ?: "redis://localhost:6379"
7349
val subtitleServiceUrl = System.getenv("SUBTITLE_SERVICE_URL") ?: "http://typetype-token:8081"
50+
val youtubeRemoteBrowserConfig = YoutubeRemoteBrowserConfig.fromEnvironment(subtitleServiceUrl)
51+
val instanceService = InstanceService(
52+
authService,
53+
adminSettingsService,
54+
youtubeRemoteLoginAvailable = { youtubeRemoteBrowserConfig.isConfigured },
55+
oidcConfigProvider = oidcAuthService::publicConfig,
56+
)
7457
val downloaderServiceUrl = System.getenv("DOWNLOADER_SERVICE_URL") ?: "http://typetype-downloader:18093"
7558
val youtubeSessionEncryptionKey = System.getenv("YOUTUBE_SESSION_ENCRYPTION_KEY")
7659
?: error("YOUTUBE_SESSION_ENCRYPTION_KEY is required")
7760
val cache = DragonflyService(cacheUrl)
7861
val svc = ServiceRegistry(cache, subtitleServiceUrl, youtubeSessionEncryptionKey)
62+
val youtubeRemoteBrowserService = YoutubeRemoteBrowserService(
63+
youtubeRemoteBrowserConfig,
64+
adminSettingsService,
65+
svc.youtubeSessionService,
66+
OkHttpYoutubeRemoteBrowserClient(youtubeRemoteBrowserConfig.serviceUrl),
67+
)
7968
val downloaderGatewayService = DownloaderGatewayService(downloaderServiceUrl)
8069
val openMojiProxyService = OpenMojiProxyService(cache)
8170
val internalHealthService = InternalHealthService(cache, downloaderGatewayService, subtitleServiceUrl)
8271
configurePlugins(authService)
83-
routing {
84-
internalObservabilityRoutes(internalHealthService::check)
85-
publicMetadataRoutes(instanceService::getInstance)
86-
rateLimit(STREAMS_ZONE) {
87-
streamRoutes(svc.streamService, authService, svc.youtubeSessionStreamService::getStreamInfo)
88-
manifestRoutes(svc.manifestService, svc.nativeManifestService, svc.hlsManifestService)
89-
}
90-
rateLimit(EXTRACTION_ZONE) {
91-
searchRoutes(svc.searchService)
92-
suggestionRoutes(svc.suggestionService)
93-
trendingRoutes(svc.trendingService)
94-
publicPlaylistRoutes(svc.publicPlaylistService)
95-
commentRoutes(svc.commentService)
96-
bulletCommentRoutes(svc.bulletCommentService)
97-
}
98-
rateLimit(CHANNEL_ZONE) {
99-
channelRoutes(svc.channelService)
100-
podcastRoutes(svc.podcastService)
101-
}
102-
rateLimit(PROXY_ZONE) {
103-
proxyRoutes(svc.proxyService)
104-
nicoVideoProxyRoutes(svc.nicoVideoProxyService)
105-
}
106-
rateLimit(PROXY_STORYBOARD_ZONE) {
107-
storyboardProxyRoutes(svc.proxyService)
108-
}
109-
downloaderGatewayRoutes(downloaderGatewayService)
110-
oidcAuthRoutes(oidcAuthService, adminSettingsService)
111-
authRoutes(authService, passwordResetService, profileService, adminSettingsService, svc.homeRecommendationWarmupService)
112-
adminRoutes(authService, userAdminService, passwordResetService, adminSettingsService)
113-
adminSessionRoutes(authService, activeSessionService)
114-
sessionActivityRoutes(authService, activeSessionService)
115-
adminBugReportRoutes(authService, svc.bugReportService, gitHubIssueService)
116-
avatarRoutes(avatarService, openMojiProxyService)
117-
rateLimit(USER_DATA_ZONE) { userDataRoutes(svc, authService, profileService, avatarService, svc.bugReportService, restoreService) }
118-
}
72+
installApplicationRoutes(
73+
svc = svc,
74+
authService = authService,
75+
adminSettingsService = adminSettingsService,
76+
activeSessionService = activeSessionService,
77+
downloaderGatewayService = downloaderGatewayService,
78+
gitHubIssueService = gitHubIssueService,
79+
instanceService = instanceService,
80+
oidcAuthService = oidcAuthService,
81+
passwordResetService = passwordResetService,
82+
profileService = profileService,
83+
userAdminService = userAdminService,
84+
avatarService = avatarService,
85+
openMojiProxyService = openMojiProxyService,
86+
internalHealthService = internalHealthService,
87+
restoreService = restoreService,
88+
youtubeRemoteBrowserService = youtubeRemoteBrowserService,
89+
)
11990
}

0 commit comments

Comments
 (0)