Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
04029f3
fix: preserve downloader artifact response metadata
Priveetee Aug 22, 2026
7962c06
feat: define canonical portability records
Priveetee Aug 22, 2026
4ae3bae
feat: define portability adapter contracts
Priveetee Aug 22, 2026
df8bfd5
feat: add bounded portability spool schema
Priveetee Aug 22, 2026
434499d
test: cover portability archive and spool bounds
Priveetee Aug 22, 2026
9edd226
feat: add owner-scoped portability jobs
Priveetee Aug 22, 2026
6a26c47
test: cover portability job ownership
Priveetee Aug 22, 2026
ee97591
feat: report portability job progress
Priveetee Aug 22, 2026
f105ded
feat: orchestrate asynchronous portability work
Priveetee Aug 22, 2026
bc4a5b1
test: cover portability engine lifecycle
Priveetee Aug 22, 2026
6b7efbf
feat: register portability adapters
Priveetee Aug 22, 2026
5691150
feat: connect portability to TypeType storage
Priveetee Aug 22, 2026
82c82d0
feat: stream core TypeType account exports
Priveetee Aug 22, 2026
010a728
feat: import core TypeType account records
Priveetee Aug 22, 2026
a46a388
feat: transfer TypeType content filters
Priveetee Aug 22, 2026
ef3f091
feat: transfer TypeType library records
Priveetee Aug 22, 2026
ab778b3
feat: encode TypeType library and settings records
Priveetee Aug 22, 2026
3259c8b
feat: add the TypeType portability adapter
Priveetee Aug 22, 2026
66e34ba
test: cover TypeType portability round trips
Priveetee Aug 22, 2026
c167a8d
feat: add portability JSON and upload helpers
Priveetee Aug 22, 2026
e21c223
feat: add Invidious portability support
Priveetee Aug 22, 2026
d05beb0
feat: decode Piped account backups
Priveetee Aug 22, 2026
dc08fa3
feat: encode Piped account backups
Priveetee Aug 22, 2026
6138a2c
feat: decode LibreTube account backups
Priveetee Aug 22, 2026
591c997
feat: encode LibreTube account backups
Priveetee Aug 22, 2026
3f60087
feat: decode Flow account backups
Priveetee Aug 22, 2026
4f1c9e8
feat: encode Flow account backups
Priveetee Aug 22, 2026
0a79f5f
feat: decode Grayjay account backups
Priveetee Aug 22, 2026
95ea826
feat: encode and verify Grayjay account backups
Priveetee Aug 22, 2026
22d0e37
feat: add ViewTube import support
Priveetee Aug 22, 2026
feaff4a
feat: add Materialious portability support
Priveetee Aug 22, 2026
6f9a028
feat: add OPML portability adapters
Priveetee Aug 22, 2026
a1fce7f
feat: model NewPipe archive databases
Priveetee Aug 22, 2026
171872c
feat: decode NewPipe account backups
Priveetee Aug 22, 2026
49156e3
feat: encode NewPipe archive records
Priveetee Aug 22, 2026
5af9a1a
test: cover NewPipe portability formats
Priveetee Aug 22, 2026
dbee278
feat: add PipePipe portability support
Priveetee Aug 22, 2026
93d05a8
feat: detect YouTube Takeout backups
Priveetee Aug 22, 2026
d96ffc4
feat: stream YouTube Takeout CSV records
Priveetee Aug 22, 2026
8e9d7f5
refactor: share YouTube Takeout parsing
Priveetee Aug 22, 2026
303270b
feat: expose asynchronous portability routes
Priveetee Aug 22, 2026
0271e3f
test: cover portability HTTP workflows
Priveetee Aug 22, 2026
27dd93e
feat: wire the portability engine
Priveetee Aug 22, 2026
e81aa4c
docs: define the portability API contract
Priveetee Aug 22, 2026
8f3bd7d
feat: expose portability job diagnostics
Priveetee Aug 22, 2026
9f6b810
perf: avoid duplicate portability category writes
Priveetee Aug 22, 2026
3f7aa89
chore: update server dependencies
Priveetee Aug 22, 2026
5af0032
fix: stabilize portability format detection
Priveetee Aug 22, 2026
e25a1d9
fix: bound authenticated SABR preparation
Priveetee Aug 23, 2026
f3dc24f
test: cover authenticated SABR timeouts
Priveetee Aug 23, 2026
d9a5160
fix: keep SABR watchdog responsive
Priveetee Aug 23, 2026
2eefc99
feat: expose parental control capability
Priveetee Aug 24, 2026
a76441a
feat: include display context in bug reports
Priveetee Aug 24, 2026
14bb5b9
perf: avoid redundant playback extraction
Priveetee Aug 25, 2026
59ef699
feat: stream SABR media responses
Priveetee Aug 25, 2026
960ca71
perf: stream VOD segments progressively
Priveetee Aug 25, 2026
a422b9d
test: cover progressive playback windows
Priveetee Aug 25, 2026
8ff8fee
test: verify readable progressive windows
Priveetee Aug 25, 2026
09db8fd
fix: align VOD audio windows to the playhead
Priveetee Aug 25, 2026
235df60
perf: reuse SABR bootstrap preparation
Priveetee Aug 25, 2026
ed9649f
perf: preload SABR initialization concurrently
Priveetee Aug 25, 2026
2e93c96
feat: expose subscription group memberships
noreply Aug 25, 2026
a3a1530
feat: support bulk subscription group membership updates
noreply Aug 26, 2026
27a3ff3
fix: reject ambiguous subscription group deletion
noreply Aug 26, 2026
ca23ed8
fix: bound subscription group membership requests
noreply Aug 26, 2026
bdfe561
fix: keep subscription membership reads consistent
noreply Aug 26, 2026
b24b165
fix: preserve SABR rewind preparation
Priveetee Aug 27, 2026
8822936
Merge pull request #80 from kapdon/feat/subscription-group-memberships
Priveetee Aug 27, 2026
744d26d
chore: prepare server 1.7.0
Priveetee Aug 28, 2026
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
13 changes: 7 additions & 6 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ repositories {
}

dependencies {
implementation(platform("com.fasterxml.jackson:jackson-bom:2.22.1"))
implementation(platform("io.netty:netty-bom:4.2.16.Final"))
implementation(platform("com.fasterxml.jackson:jackson-bom:2.22.2"))
implementation("com.fasterxml.jackson.core:jackson-core")
implementation(platform("io.netty:netty-bom:4.2.17.Final"))
constraints {
implementation("org.jsoup:jsoup:1.23.1") {
because("CVE-2026-71497 affects PipePipeExtractor's transitive jsoup version")
Expand All @@ -42,12 +43,12 @@ dependencies {
implementation("io.ktor:ktor-server-status-pages-jvm")
implementation("io.ktor:ktor-server-call-logging-jvm")
implementation("io.ktor:ktor-server-rate-limit-jvm")
implementation("ch.qos.logback:logback-classic:1.6.1")
implementation("ch.qos.logback:logback-classic:1.6.3")
implementation("com.github.Priveetee.PipePipeExtractor:extractor:f156813dd4bbebf3b4dffe541fee6c27ae1dd294")
compileOnly("com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751")
implementation("org.json:json:20260719")
implementation("com.squareup.okhttp3:okhttp:5.4.0")
implementation("io.lettuce:lettuce-core:7.6.0.RELEASE")
implementation("org.json:json:20260814")
implementation("com.squareup.okhttp3:okhttp:5.5.0")
implementation("io.lettuce:lettuce-core:7.7.0.RELEASE")
implementation("org.jetbrains.exposed:exposed-core:1.4.0")
implementation("org.jetbrains.exposed:exposed-jdbc:1.4.0")
implementation("com.zaxxer:HikariCP:7.1.0")
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
org.gradle.jvmargs=-Xmx2g -XX:+UseG1GC
kotlin.code.style=official
appVersion=1.6.0
appVersion=1.7.0
systemProp.sun.net.client.defaultReadTimeout=180000
systemProp.sun.net.client.defaultConnectTimeout=60000
17 changes: 17 additions & 0 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ tags:
- name: youtube-session
- name: user-data
- name: rss
- name: portability
paths:
/health: { $ref: ./openapi/paths/health.yaml#/Health }
/instance: { $ref: ./openapi/paths/metadata.yaml#/Instance }
Expand Down Expand Up @@ -44,6 +45,7 @@ paths:
/saved-playlists: { $ref: ./openapi/paths/saved-playlists.yaml#/SavedPlaylists }
/saved-playlists/{id}: { $ref: ./openapi/paths/saved-playlists.yaml#/SavedPlaylist }
/subscriptions: { $ref: ./openapi/paths/subscriptions.yaml#/Subscriptions }
/subscriptions/group-memberships: { $ref: ./openapi/paths/subscriptions.yaml#/SubscriptionGroupMemberships }
/subscriptions/groups: { $ref: ./openapi/paths/subscriptions.yaml#/SubscriptionGroups }
/subscriptions/groups/{groupId}: { $ref: ./openapi/paths/subscriptions.yaml#/SubscriptionGroup }
/subscriptions/groups/{groupId}/channels: { $ref: ./openapi/paths/subscriptions.yaml#/SubscriptionGroupChannels }
Expand All @@ -56,6 +58,14 @@ paths:
/settings: { $ref: ./openapi/paths/access-control.yaml#/Settings }
/backup/typetype: { $ref: ./openapi/paths/user-backup.yaml#/TypeTypeBackup }
/restore/typetype: { $ref: ./openapi/paths/user-backup.yaml#/TypeTypeRestore }
/portability/formats: { $ref: ./openapi/paths/portability.yaml#/PortabilityFormats }
/portability/imports: { $ref: ./openapi/paths/portability.yaml#/PortabilityImports }
/portability/exports: { $ref: ./openapi/paths/portability.yaml#/PortabilityExports }
/portability/jobs/{id}: { $ref: ./openapi/paths/portability.yaml#/PortabilityJob }
/portability/jobs/{id}/apply: { $ref: ./openapi/paths/portability.yaml#/PortabilityApply }
/portability/jobs/{id}/cancel: { $ref: ./openapi/paths/portability.yaml#/PortabilityCancel }
/portability/jobs/{id}/report: { $ref: ./openapi/paths/portability.yaml#/PortabilityReport }
/portability/jobs/{id}/artifact: { $ref: ./openapi/paths/portability.yaml#/PortabilityArtifact }
/blocked/keywords: { $ref: ./openapi/paths/user-backup.yaml#/BlockedKeywords }
/blocked/keywords/{keyword}: { $ref: ./openapi/paths/user-backup.yaml#/BlockedKeyword }
/allowed/channels: { $ref: ./openapi/paths/access-control.yaml#/AllowedChannels }
Expand Down Expand Up @@ -151,9 +161,11 @@ components:
SavedPlaylistItem: { $ref: ./openapi/components/media.yaml#/SavedPlaylistItem }
SavedPlaylistRequest: { $ref: ./openapi/components/media.yaml#/SavedPlaylistRequest }
SubscriptionItem: { $ref: ./openapi/components/subscriptions.yaml#/SubscriptionItem }
SubscriptionGroupMembershipItem: { $ref: ./openapi/components/subscriptions.yaml#/SubscriptionGroupMembershipItem }
SubscriptionGroupItem: { $ref: ./openapi/components/subscriptions.yaml#/SubscriptionGroupItem }
SubscriptionGroupRequest: { $ref: ./openapi/components/subscriptions.yaml#/SubscriptionGroupRequest }
SubscriptionGroupMembershipRequest: { $ref: ./openapi/components/subscriptions.yaml#/SubscriptionGroupMembershipRequest }
SubscriptionGroupMembershipBatchRequest: { $ref: ./openapi/components/subscriptions.yaml#/SubscriptionGroupMembershipBatchRequest }
SubscriptionFeedResponse: { $ref: ./openapi/components/subscriptions.yaml#/SubscriptionFeedResponse }
SubscriptionFeedPreparingResponse: { $ref: ./openapi/components/subscriptions.yaml#/SubscriptionFeedPreparingResponse }
RssFeedRequest: { $ref: ./openapi/components/rss.yaml#/RssFeedRequest }
Expand All @@ -163,6 +175,11 @@ components:
SettingsItem: { $ref: ./openapi/components/access-control.yaml#/SettingsItem }
TypeTypeBackupItem: { $ref: ./openapi/components/user-backup.yaml#/TypeTypeBackupItem }
TypeTypeRestoreSummary: { $ref: ./openapi/components/user-backup.yaml#/TypeTypeRestoreSummary }
PortabilityAdapterDescriptor: { $ref: ./openapi/components/portability.yaml#/PortabilityAdapterDescriptor }
PortabilityJobSnapshot: { $ref: ./openapi/components/portability.yaml#/PortabilityJobSnapshot }
PortabilityJobReport: { $ref: ./openapi/components/portability.yaml#/PortabilityJobReport }
PortabilityImportRequest: { $ref: ./openapi/components/portability.yaml#/PortabilityImportRequest }
PortabilityExportRequest: { $ref: ./openapi/components/portability.yaml#/PortabilityExportRequest }
BlockedKeywordItem: { $ref: ./openapi/components/user-backup.yaml#/BlockedKeywordItem }
AdminSettingsItem: { $ref: ./openapi/components/access-control.yaml#/AdminSettingsItem }
AllowedChannelItem: { $ref: ./openapi/components/access-control.yaml#/AllowedChannelItem }
Expand Down
4 changes: 4 additions & 0 deletions openapi/components/instance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ InstanceResponse:
- oidcAutoRedirect
- youtubeRemoteLoginEnabled
- youtubeRemoteLoginReady
- parentalControlsEnabled
- rss
properties:
name: { type: string, example: TypeType }
Expand Down Expand Up @@ -50,6 +51,9 @@ InstanceResponse:
type: string
nullable: true
enum: [disabled, not_configured, token_unreachable]
parentalControlsEnabled:
type: boolean
description: True when the instance-wide allow-list policy is enabled.
rss:
$ref: '#/RssInstanceCapability'
RssInstanceCapability:
Expand Down
125 changes: 125 additions & 0 deletions openapi/components/portability.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
PortabilityFormat:
type: string
enum: [typetype, pipepipe, newpipe, invidious, piped, libretube, viewtube, materialious, youtube-local, flow, skytube, grayjay, youtube-takeout, opml]
PortabilityCategory:
type: string
enum: [subscriptions, subscriptionGroups, history, playlists, watchLater, favorites, progress, searchHistory, savedPlaylists, settings, contentFilters]
PortabilityCapability:
type: object
required: [category, directions, fidelity]
properties:
category: { $ref: '#/PortabilityCategory' }
directions:
type: array
uniqueItems: true
items: { type: string, enum: [import, export] }
fidelity: { type: string, enum: [complete, partial] }
PortabilityAdapterDescriptor:
type: object
required: [format, adapterVersion, capabilities, defaultExtension, contentType]
properties:
format: { $ref: '#/PortabilityFormat' }
adapterVersion: { type: integer, minimum: 1 }
capabilities:
type: array
uniqueItems: true
items: { $ref: '#/PortabilityCapability' }
defaultExtension: { type: string }
contentType: { type: string }
PortabilityIssue:
type: object
required: [category, code, message, count]
properties:
category:
allOf: [{ $ref: '#/PortabilityCategory' }]
nullable: true
code: { type: string }
message: { type: string }
count: { type: integer, format: int64, minimum: 1 }
PortabilityDetection:
type: object
required: [format, formatVersion, adapterVersion, confidence, evidence]
properties:
format: { $ref: '#/PortabilityFormat' }
formatVersion: { type: string, nullable: true }
adapterVersion: { type: integer, minimum: 1 }
confidence: { type: integer, minimum: 1, maximum: 100 }
evidence: { type: string }
PortabilityPreview:
type: object
required: [detection, counts, duplicates, issues]
properties:
detection: { $ref: '#/PortabilityDetection' }
counts:
type: object
additionalProperties: { type: integer, format: int64, minimum: 0 }
duplicates: { type: integer, format: int64, minimum: 0 }
issues:
type: array
items: { $ref: '#/PortabilityIssue' }
PortabilityJobSnapshot:
type: object
required: [id, kind, state, createdAt, updatedAt]
properties:
id: { type: string, format: uuid }
kind: { type: string, enum: [import, export] }
state: { type: string, enum: [queued, analyzing, ready, applying, encoding, completed, failed, cancelled] }
createdAt: { type: integer, format: int64 }
updatedAt: { type: integer, format: int64 }
requestId: { type: string, nullable: true }
preview:
allOf: [{ $ref: '#/PortabilityPreview' }]
nullable: true
result:
type: object
nullable: true
additionalProperties: { type: integer, format: int64, minimum: 0 }
progress:
allOf: [{ $ref: '#/PortabilityJobProgress' }]
nullable: true
errorCode: { type: string, nullable: true }
errorMessage: { type: string, nullable: true }
PortabilityJobProgress:
type: object
required: [phase, unit, processed, total]
properties:
phase: { type: string, enum: [analyzing, collecting, applying, encoding] }
unit: { type: string, enum: [records, categories, bytes] }
processed: { type: integer, format: int64, minimum: 0 }
total: { type: integer, format: int64, minimum: 0, nullable: true }
PortabilityJobReport:
type: object
required: [id, state]
properties:
id: { type: string, format: uuid }
state: { type: string, enum: [queued, analyzing, ready, applying, encoding, completed, failed, cancelled] }
requestId: { type: string, nullable: true }
preview:
allOf: [{ $ref: '#/PortabilityPreview' }]
nullable: true
result:
type: object
nullable: true
additionalProperties: { type: integer, format: int64, minimum: 0 }
errorCode: { type: string, nullable: true }
errorMessage: { type: string, nullable: true }
PortabilityImportRequest:
type: object
required: [categories]
properties:
categories:
type: array
minItems: 1
uniqueItems: true
items: { $ref: '#/PortabilityCategory' }
duplicatePolicy: { type: string, enum: [skip, replace], default: skip }
PortabilityExportRequest:
type: object
required: [format, categories]
properties:
format: { $ref: '#/PortabilityFormat' }
categories:
type: array
minItems: 1
uniqueItems: true
items: { $ref: '#/PortabilityCategory' }
23 changes: 22 additions & 1 deletion openapi/components/subscriptions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ SubscriptionItem:
name: { type: string }
avatarUrl: { type: string }
subscribedAt: { type: integer, format: int64 }
SubscriptionGroupMembershipItem:
type: object
required: [channelUrl, name, avatarUrl, subscribedAt, groupIds]
properties:
channelUrl: { type: string, minLength: 1 }
name: { type: string }
avatarUrl: { type: string }
subscribedAt: { type: integer, format: int64 }
groupIds:
type: array
uniqueItems: true
items: { type: string, format: uuid }
SubscriptionCreateRequest:
type: object
required: [channelUrl, name, avatarUrl]
Expand All @@ -31,7 +43,16 @@ SubscriptionGroupMembershipRequest:
type: object
required: [channelUrl]
properties:
channelUrl: { type: string, minLength: 1 }
channelUrl: { type: string, minLength: 1, maxLength: 2048 }
SubscriptionGroupMembershipBatchRequest:
type: object
required: [channelUrls]
properties:
channelUrls:
type: array
minItems: 1
maxItems: 500
items: { type: string, minLength: 1, maxLength: 2048 }
SubscriptionFeedResponse:
type: object
required: [videos, nextpage, generation, generatedAt, refreshing]
Expand Down
10 changes: 10 additions & 0 deletions openapi/paths/downloader.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,16 @@ JobArtifact:
'200': { description: Artifact bytes. }
'206': { description: Partial artifact bytes. }
'302': { description: Redirect to public artifact storage. }
'416': { description: Requested byte range is outside the artifact. }
'404': { $ref: ../components/common.yaml#/JsonError }
head:
tags: [downloader]
summary: Inspect a completed artifact without transferring its body
responses:
'200': { description: Artifact metadata. }
'206': { description: Partial artifact metadata. }
'302': { description: Redirect to public artifact storage. }
'416': { description: Requested byte range is outside the artifact. }
'404': { $ref: ../components/common.yaml#/JsonError }
JobCancel:
parameters:
Expand Down
Loading