├── .editorconfig ├── .env.example ├── .git-blame-ignore-revs ├── .gitattributes ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug-report.yml │ └── feature-request.yml └── workflows │ ├── api-test.yml │ ├── auth-hurl.yml │ ├── coding-style.yml │ ├── docker.yml │ ├── helm-release-chart.yml │ ├── helm-test-chart.yml │ ├── native-build.yml │ ├── native-update.yml │ └── release.yml ├── .gitignore ├── .pg_format ├── AUTHORS.md ├── CONTRIBUTING.md ├── DIAGRAMS.md ├── INSTALLING.md ├── LICENSE ├── OIDC.md ├── PRIVACY.md ├── README.md ├── api ├── .env.example ├── .gitignore ├── Dockerfile ├── Dockerfile.dev ├── README.md ├── biome.json ├── bun.lock ├── bunfig.toml ├── drizzle.config.ts ├── drizzle │ ├── 0000_init.sql │ ├── 0001_video.sql │ ├── 0002_seasons.sql │ ├── 0003_order.sql │ ├── 0004_jointures.sql │ ├── 0005_trigram.sql │ ├── 0006_seasons.sql │ ├── 0007_entries.sql │ ├── 0008_entries.sql │ ├── 0009_collections.sql │ ├── 0010_studios.sql │ ├── 0011_join_rename.sql │ ├── 0012_available_count.sql │ ├── 0013_original.sql │ ├── 0014_staff.sql │ ├── 0015_news.sql │ ├── 0016_mqueue.sql │ ├── 0017_watchlist.sql │ ├── 0018_history.sql │ ├── 0019_nextup.sql │ ├── 0020_video_unique.sql │ └── meta │ │ ├── 0000_snapshot.json │ │ ├── 0001_snapshot.json │ │ ├── 0002_snapshot.json │ │ ├── 0003_snapshot.json │ │ ├── 0004_snapshot.json │ │ ├── 0005_snapshot.json │ │ ├── 0006_snapshot.json │ │ ├── 0007_snapshot.json │ │ ├── 0008_snapshot.json │ │ ├── 0009_snapshot.json │ │ ├── 0010_snapshot.json │ │ ├── 0011_snapshot.json │ │ ├── 0012_snapshot.json │ │ ├── 0013_snapshot.json │ │ ├── 0014_snapshot.json │ │ ├── 0015_snapshot.json │ │ ├── 0016_snapshot.json │ │ ├── 0017_snapshot.json │ │ ├── 0018_snapshot.json │ │ ├── 0019_snapshot.json │ │ ├── 0020_snapshot.json │ │ └── _journal.json ├── package.json ├── patches │ └── drizzle-orm@0.43.1.patch ├── src │ ├── auth.ts │ ├── base.ts │ ├── controllers │ │ ├── entries.ts │ │ ├── images.ts │ │ ├── profiles │ │ │ ├── history.ts │ │ │ ├── nextup.ts │ │ │ ├── profile.ts │ │ │ └── watchlist.ts │ │ ├── seasons.ts │ │ ├── seed │ │ │ ├── images.ts │ │ │ ├── index.ts │ │ │ ├── insert │ │ │ │ ├── collection.ts │ │ │ │ ├── entries.ts │ │ │ │ ├── seasons.ts │ │ │ │ ├── shows.ts │ │ │ │ ├── staff.ts │ │ │ │ └── studios.ts │ │ │ ├── movies.ts │ │ │ ├── refresh.ts │ │ │ └── series.ts │ │ ├── shows │ │ │ ├── collections.ts │ │ │ ├── logic.ts │ │ │ ├── movies.ts │ │ │ ├── series.ts │ │ │ └── shows.ts │ │ ├── staff.ts │ │ ├── studios.ts │ │ └── videos.ts │ ├── db │ │ ├── index.ts │ │ ├── schema │ │ │ ├── entries.ts │ │ │ ├── history.ts │ │ │ ├── index.ts │ │ │ ├── mqueue.ts │ │ │ ├── profiles.ts │ │ │ ├── seasons.ts │ │ │ ├── shows.ts │ │ │ ├── staff.ts │ │ │ ├── studios.ts │ │ │ ├── utils.ts │ │ │ ├── videos.ts │ │ │ └── watchlist.ts │ │ └── utils.ts │ ├── index.ts │ ├── models │ │ ├── collections.ts │ │ ├── entry │ │ │ ├── base-entry.ts │ │ │ ├── episode.ts │ │ │ ├── extra.ts │ │ │ ├── index.ts │ │ │ ├── movie-entry.ts │ │ │ └── special.ts │ │ ├── error.ts │ │ ├── examples │ │ │ ├── bubble.ts │ │ │ ├── dune-1984.ts │ │ │ ├── dune-2021.ts │ │ │ ├── dune-collection.ts │ │ │ ├── index.ts │ │ │ └── made-in-abyss.ts │ │ ├── history.ts │ │ ├── movie.ts │ │ ├── season.ts │ │ ├── serie.ts │ │ ├── show.ts │ │ ├── staff-roles.ts │ │ ├── staff.ts │ │ ├── studio.ts │ │ ├── utils │ │ │ ├── db-metadata.ts │ │ │ ├── descriptions.ts │ │ │ ├── external-id.ts │ │ │ ├── filters │ │ │ │ ├── index.ts │ │ │ │ ├── parser.ts │ │ │ │ └── to-sql.ts │ │ │ ├── genres.ts │ │ │ ├── image.ts │ │ │ ├── index.ts │ │ │ ├── keyset-paginate.ts │ │ │ ├── language.ts │ │ │ ├── original.ts │ │ │ ├── page.ts │ │ │ ├── relations.ts │ │ │ ├── resource.ts │ │ │ └── sort.ts │ │ ├── video.ts │ │ └── watchlist.ts │ └── utils.ts ├── tests │ ├── collection │ │ └── seed-collection.test.ts │ ├── helpers │ │ ├── index.ts │ │ ├── jwt.ts │ │ ├── movies-helper.ts │ │ ├── series-helper.ts │ │ ├── shows-helper.ts │ │ ├── staff-helper.ts │ │ ├── studio-helper.ts │ │ └── videos-helper.ts │ ├── manual.ts │ ├── misc │ │ ├── filter.test.ts │ │ └── images.test.ts │ ├── movies │ │ ├── get-all-movies-with-null.test.ts │ │ ├── get-all-movies.test.ts │ │ ├── get-movie.test.ts │ │ ├── seed-movies.test.ts │ │ └── watchstatus.test.ts │ ├── series │ │ ├── get-entries.test.ts │ │ ├── get-seasons.test.ts │ │ ├── get-series.test.ts │ │ ├── get-staff.test.ts │ │ ├── history.test.ts │ │ ├── nextup.test.ts │ │ ├── seed-serie.test.ts │ │ └── studios.test.ts │ ├── setup.ts │ ├── utils.ts │ └── videos │ │ ├── getdel.test.ts │ │ └── scanner.test.ts └── tsconfig.json ├── auth ├── .dockerignore ├── .env.example ├── .swaggo ├── Dockerfile ├── Dockerfile.dev ├── README.md ├── apikey.go ├── config.go ├── dbc │ ├── apikeys.sql.go │ ├── db.go │ ├── models.go │ ├── sessions.sql.go │ └── users.sql.go ├── docs │ ├── docs.go │ └── swagger.json ├── go.mod ├── go.sum ├── jwt.go ├── kerror.go ├── main.go ├── page.go ├── sessions.go ├── sql │ ├── migrations │ │ ├── 000001_users.down.sql │ │ ├── 000001_users.up.sql │ │ ├── 000002_sessions.down.sql │ │ ├── 000002_sessions.up.sql │ │ ├── 000003_apikeys.down.sql │ │ └── 000003_apikeys.up.sql │ └── queries │ │ ├── apikeys.sql │ │ ├── sessions.sql │ │ └── users.sql ├── sqlc.yaml ├── tests │ ├── apikey.hurl │ ├── basic.hurl │ ├── change-password.hurl │ ├── edit-settings.hurl │ ├── invalid-password.hurl │ └── users.hurl ├── users.go └── utils.go ├── autosync ├── .env.example ├── .gitignore ├── Dockerfile ├── autosync │ ├── __init__.py │ ├── __main__.py │ ├── consumer.py │ ├── models │ │ ├── episode.py │ │ ├── message.py │ │ ├── metadataid.py │ │ ├── movie.py │ │ ├── show.py │ │ ├── user.py │ │ └── watch_status.py │ └── services │ │ ├── aggregate.py │ │ ├── service.py │ │ └── simkl.py ├── pyproject.toml └── requirements.txt ├── back ├── .config │ └── dotnet-tools.json ├── .dockerignore ├── .editorconfig ├── .env.example ├── .gitignore ├── Dockerfile ├── Dockerfile.dev ├── Dockerfile.migrations ├── Kyoo.ruleset ├── Kyoo.sln ├── ef.rsp ├── icon.ico ├── nuget.config ├── src │ ├── Directory.Build.props │ ├── Kyoo.Abstractions │ │ ├── .gitignore │ │ ├── Controllers │ │ │ ├── IIssueRepository.cs │ │ │ ├── ILibraryManager.cs │ │ │ ├── IPermissionValidator.cs │ │ │ ├── IRepository.cs │ │ │ ├── IScanner.cs │ │ │ ├── ISearchManager.cs │ │ │ ├── IThumbnailsManager.cs │ │ │ └── IWatchStatusRepository.cs │ │ ├── Extensions.cs │ │ ├── Kyoo.Abstractions.csproj │ │ ├── Models │ │ │ ├── Attributes │ │ │ │ ├── ApiDefinitionAttribute.cs │ │ │ │ ├── ComputedAttribute.cs │ │ │ │ ├── LoadableRelationAttribute.cs │ │ │ │ ├── NotMergeableAttribute.cs │ │ │ │ ├── OneOfAttribute.cs │ │ │ │ ├── Permission │ │ │ │ │ ├── PartialPermissionAttribute.cs │ │ │ │ │ ├── PermissionAttribute.cs │ │ │ │ │ └── UserOnlyAttribute.cs │ │ │ │ └── SqlFirstColumnAttribute.cs │ │ │ ├── Exceptions │ │ │ │ ├── DuplicatedItemException.cs │ │ │ │ ├── ItemNotFoundException.cs │ │ │ │ └── UnauthorizedException.cs │ │ │ ├── Genre.cs │ │ │ ├── ILibraryItem.cs │ │ │ ├── INews.cs │ │ │ ├── IWatchlist.cs │ │ │ ├── Issues.cs │ │ │ ├── MetadataID.cs │ │ │ ├── Page.cs │ │ │ ├── Patch.cs │ │ │ ├── Resources │ │ │ │ ├── Collection.cs │ │ │ │ ├── Episode.cs │ │ │ │ ├── Interfaces │ │ │ │ │ ├── IAddedDate.cs │ │ │ │ │ ├── IMetadata.cs │ │ │ │ │ ├── IQuery.cs │ │ │ │ │ ├── IRefreshable.cs │ │ │ │ │ ├── IResource.cs │ │ │ │ │ └── IThumbnails.cs │ │ │ │ ├── JwtToken.cs │ │ │ │ ├── Movie.cs │ │ │ │ ├── Season.cs │ │ │ │ ├── Show.cs │ │ │ │ ├── Studio.cs │ │ │ │ ├── User.cs │ │ │ │ └── WatchStatus.cs │ │ │ ├── SearchPage.cs │ │ │ ├── Utils │ │ │ │ ├── Claims.cs │ │ │ │ ├── Constants.cs │ │ │ │ ├── Filter.cs │ │ │ │ ├── Identifier.cs │ │ │ │ ├── Include.cs │ │ │ │ ├── Pagination.cs │ │ │ │ ├── RequestError.cs │ │ │ │ ├── SearchPagination.cs │ │ │ │ └── Sort.cs │ │ │ └── VideoLinks.cs │ │ └── Utility │ │ │ ├── ExpressionParameterReplacer.cs │ │ │ ├── JsonKindResolver.cs │ │ │ ├── Utility.cs │ │ │ └── Wrapper.cs │ ├── Kyoo.Authentication │ │ ├── Attributes │ │ │ └── DisableOnEnvVarAttribute.cs │ │ ├── AuthenticationModule.cs │ │ ├── Controllers │ │ │ ├── ITokenController.cs │ │ │ ├── OidcController.cs │ │ │ ├── PermissionValidator.cs │ │ │ └── TokenController.cs │ │ ├── Kyoo.Authentication.csproj │ │ ├── Models │ │ │ ├── DTO │ │ │ │ ├── JwtProfile.cs │ │ │ │ ├── LoginRequest.cs │ │ │ │ ├── PasswordResetRequest.cs │ │ │ │ ├── RegisterRequest.cs │ │ │ │ └── ServerInfo.cs │ │ │ └── Options │ │ │ │ ├── AuthenticationOption.cs │ │ │ │ └── PermissionOption.cs │ │ └── Views │ │ │ └── AuthApi.cs │ ├── Kyoo.Core │ │ ├── .gitignore │ │ ├── Controllers │ │ │ ├── Base64RouteConstraint.cs │ │ │ ├── IdentifierRouteConstraint.cs │ │ │ ├── LibraryManager.cs │ │ │ ├── MiscRepository.cs │ │ │ ├── Repositories │ │ │ │ ├── CollectionRepository.cs │ │ │ │ ├── DapperHelper.cs │ │ │ │ ├── DapperRepository.cs │ │ │ │ ├── EfHelpers.cs │ │ │ │ ├── EpisodeRepository.cs │ │ │ │ ├── GenericRepository.cs │ │ │ │ ├── IssueRepository.cs │ │ │ │ ├── LibraryItemRepository.cs │ │ │ │ ├── MovieRepository.cs │ │ │ │ ├── NewsRepository.cs │ │ │ │ ├── RepositoryHelper.cs │ │ │ │ ├── SeasonRepository.cs │ │ │ │ ├── ShowRepository.cs │ │ │ │ ├── StudioRepository.cs │ │ │ │ ├── UserRepository.cs │ │ │ │ └── WatchStatusRepository.cs │ │ │ └── ThumbnailsManager.cs │ │ ├── CoreModule.cs │ │ ├── ExceptionFilter.cs │ │ ├── Extensions │ │ │ └── ServiceExtensions.cs │ │ ├── Kyoo.Core.csproj │ │ ├── Program.cs │ │ ├── Storage │ │ │ ├── FileStorage.cs │ │ │ ├── IStorage.cs │ │ │ └── S3Storage.cs │ │ └── Views │ │ │ ├── Admin │ │ │ ├── Health.cs │ │ │ └── Misc.cs │ │ │ ├── Content │ │ │ ├── ThumbnailsApi.cs │ │ │ └── VideoApi.cs │ │ │ ├── Helper │ │ │ ├── BaseApi.cs │ │ │ ├── CrudApi.cs │ │ │ ├── CrudThumbsApi.cs │ │ │ ├── FilterBinder.cs │ │ │ ├── IncludeBinder.cs │ │ │ ├── SortBinder.cs │ │ │ └── Transcoder.cs │ │ │ ├── InfoApi.cs │ │ │ ├── Metadata │ │ │ ├── IssueApi.cs │ │ │ └── StudioApi.cs │ │ │ └── Resources │ │ │ ├── CollectionApi.cs │ │ │ ├── EpisodeApi.cs │ │ │ ├── LibraryItemApi.cs │ │ │ ├── MovieApi.cs │ │ │ ├── NewsApi.cs │ │ │ ├── SearchApi.cs │ │ │ ├── SeasonApi.cs │ │ │ ├── ShowApi.cs │ │ │ ├── UserApi.cs │ │ │ └── WatchlistApi.cs │ ├── Kyoo.Meilisearch │ │ ├── FilterExtensionMethods.cs │ │ ├── Kyoo.Meilisearch.csproj │ │ ├── MeiliSync.cs │ │ ├── MeilisearchModule.cs │ │ └── SearchManager.cs │ ├── Kyoo.Postgresql │ │ ├── DatabaseContext.cs │ │ ├── DbConfigurationProvider.cs │ │ ├── Kyoo.Postgresql.csproj │ │ ├── Migrations │ │ │ ├── 20231128171554_Initial.Designer.cs │ │ │ ├── 20231128171554_Initial.cs │ │ │ ├── 20231204000849_Watchlist.Designer.cs │ │ │ ├── 20231204000849_Watchlist.cs │ │ │ ├── 20231220093441_Settings.Designer.cs │ │ │ ├── 20231220093441_Settings.cs │ │ │ ├── 20240120154137_RuntimeNullable.Designer.cs │ │ │ ├── 20240120154137_RuntimeNullable.cs │ │ │ ├── 20240204193443_RemoveUserLogo.Designer.cs │ │ │ ├── 20240204193443_RemoveUserLogo.cs │ │ │ ├── 20240217143306_AddIssues.Designer.cs │ │ │ ├── 20240217143306_AddIssues.cs │ │ │ ├── 20240219170615_AddPlayPermission.Designer.cs │ │ │ ├── 20240219170615_AddPlayPermission.cs │ │ │ ├── 20240229202049_AddUserExternalId.Designer.cs │ │ │ ├── 20240229202049_AddUserExternalId.cs │ │ │ ├── 20240302151906_MakePasswordOptional.Designer.cs │ │ │ ├── 20240302151906_MakePasswordOptional.cs │ │ │ ├── 20240324174638_UseDateOnly.Designer.cs │ │ │ ├── 20240324174638_UseDateOnly.cs │ │ │ ├── 20240401213942_AddGenres.Designer.cs │ │ │ ├── 20240401213942_AddGenres.cs │ │ │ ├── 20240414212454_AddNextRefresh.Designer.cs │ │ │ ├── 20240414212454_AddNextRefresh.cs │ │ │ ├── 20240420124608_ReworkImages.Designer.cs │ │ │ ├── 20240420124608_ReworkImages.cs │ │ │ ├── 20240423151632_AddServerOptions.Designer.cs │ │ │ ├── 20240423151632_AddServerOptions.cs │ │ │ ├── 20240506175054_FixSeasonMetadataId.Designer.cs │ │ │ ├── 20240506175054_FixSeasonMetadataId.cs │ │ │ └── PostgresContextModelSnapshot.cs │ │ ├── PostgresContext.cs │ │ ├── PostgresModule.cs │ │ └── Utils │ │ │ ├── JsonTypeHandler.cs │ │ │ └── ListTypeHandler.cs │ ├── Kyoo.RabbitMq │ │ ├── Kyoo.RabbitMq.csproj │ │ ├── Message.cs │ │ ├── RabbitMqModule.cs │ │ ├── RabbitProducer.cs │ │ └── ScannerProducer.cs │ └── Kyoo.Swagger │ │ ├── ApiSorter.cs │ │ ├── ApiTagsFilter.cs │ │ ├── GenericResponseProvider.cs │ │ ├── Kyoo.Swagger.csproj │ │ ├── Models │ │ └── TagGroups.cs │ │ ├── OperationPermissionProcessor.cs │ │ └── SwaggerModule.cs ├── stylecop.json └── tests │ ├── .gitignore │ ├── auth │ └── auth.robot │ ├── pyproject.toml │ └── rest.resource ├── biome.json ├── chart ├── .helmignore ├── Chart.yaml ├── README.md ├── templates │ ├── _common.tpl │ ├── _helpers.tpl │ ├── autosync │ │ ├── deployment.yaml │ │ └── serviceaccount.yaml │ ├── back │ │ ├── deployment.yaml │ │ ├── pvc.yaml │ │ ├── service.yaml │ │ └── serviceaccount.yaml │ ├── extra-manifests.yaml │ ├── front │ │ ├── deployment.yaml │ │ ├── service.yaml │ │ └── serviceaccount.yaml │ ├── ingress.yaml │ ├── matcher │ │ ├── deployment.yaml │ │ └── serviceaccount.yaml │ ├── scanner │ │ ├── deployment.yaml │ │ └── serviceaccount.yaml │ └── transcoder │ │ ├── deployment.yaml │ │ ├── pvc.yaml │ │ ├── service.yaml │ │ └── serviceaccount.yaml └── values.yaml ├── docker-compose.build.yml ├── docker-compose.dev-v5.yml ├── docker-compose.dev.yml ├── docker-compose.yml ├── front ├── .dockerignore ├── .gitattributes ├── .gitignore ├── .yarn │ ├── plugins │ │ └── @yarnpkg │ │ │ ├── plugin-interactive-tools.cjs │ │ │ └── plugin-workspace-tools.cjs │ ├── releases │ │ └── yarn-3.2.4.cjs │ └── sdks │ │ ├── eslint │ │ ├── bin │ │ │ └── eslint.js │ │ ├── lib │ │ │ └── api.js │ │ └── package.json │ │ ├── integrations.yml │ │ ├── prettier │ │ ├── index.js │ │ └── package.json │ │ └── typescript │ │ ├── bin │ │ ├── tsc │ │ └── tsserver │ │ ├── lib │ │ ├── tsc.js │ │ ├── tsserver.js │ │ ├── tsserverlibrary.js │ │ └── typescript.js │ │ └── package.json ├── .yarnrc.yml ├── Dockerfile ├── Dockerfile.dev ├── apps │ ├── mobile │ │ ├── app.config.ts │ │ ├── app │ │ │ ├── (app) │ │ │ │ ├── (tabs) │ │ │ │ │ ├── _layout.tsx │ │ │ │ │ ├── browse.tsx │ │ │ │ │ ├── downloads.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── _layout.tsx │ │ │ │ ├── admin │ │ │ │ │ └── index.tsx │ │ │ │ ├── collection │ │ │ │ │ └── [slug] │ │ │ │ │ │ └── index.tsx │ │ │ │ ├── movie │ │ │ │ │ └── [slug] │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── watch.tsx │ │ │ │ ├── search │ │ │ │ │ └── index.tsx │ │ │ │ ├── settings │ │ │ │ │ └── index.tsx │ │ │ │ ├── show │ │ │ │ │ └── [slug].tsx │ │ │ │ └── watch │ │ │ │ │ └── [slug].tsx │ │ │ ├── (public) │ │ │ │ ├── _layout.tsx │ │ │ │ ├── connection-error.tsx │ │ │ │ ├── login │ │ │ │ │ ├── callback.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── register │ │ │ │ │ └── index.tsx │ │ │ │ ├── server-url │ │ │ │ │ └── index.tsx │ │ │ │ └── settings │ │ │ │ │ └── index.tsx │ │ │ ├── _layout.tsx │ │ │ └── utils.tsx │ │ ├── assets │ │ │ └── icon.png │ │ ├── eas.json │ │ ├── metro.config.js │ │ ├── package.json │ │ └── tsconfig.json │ └── web │ │ ├── next.config.js │ │ ├── package.json │ │ ├── public │ │ ├── banner.png │ │ ├── icon-128x128.png │ │ ├── icon-16x16.png │ │ ├── icon-256x256.png │ │ ├── icon-32x32.png │ │ ├── icon-64x64.png │ │ └── icon.svg │ │ ├── src │ │ ├── i18n.tsx │ │ ├── pages │ │ │ ├── _app.tsx │ │ │ ├── _document.tsx │ │ │ ├── admin │ │ │ │ └── index.tsx │ │ │ ├── browse │ │ │ │ └── index.tsx │ │ │ ├── collection │ │ │ │ └── [slug] │ │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ ├── login │ │ │ │ ├── callback.tsx │ │ │ │ └── index.tsx │ │ │ ├── movie │ │ │ │ └── [slug] │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── watch.tsx │ │ │ ├── register │ │ │ │ └── index.tsx │ │ │ ├── search │ │ │ │ └── index.tsx │ │ │ ├── settings │ │ │ │ └── index.tsx │ │ │ ├── setup │ │ │ │ └── index.tsx │ │ │ ├── show │ │ │ │ └── [slug].tsx │ │ │ └── watch │ │ │ │ └── [slug].tsx │ │ ├── polyfill.ts │ │ └── router.tsx │ │ └── tsconfig.json ├── biome.json ├── package.json ├── packages │ ├── models │ │ ├── package.json │ │ ├── src │ │ │ ├── account-internal.ts │ │ │ ├── accounts.tsx │ │ │ ├── index.ts │ │ │ ├── issue.ts │ │ │ ├── kyoo-errors.ts │ │ │ ├── login.ts │ │ │ ├── page.ts │ │ │ ├── query.tsx │ │ │ ├── resources │ │ │ │ ├── collection.ts │ │ │ │ ├── episode.base.ts │ │ │ │ ├── episode.ts │ │ │ │ ├── genre.ts │ │ │ │ ├── index.ts │ │ │ │ ├── library-item.ts │ │ │ │ ├── metadata.ts │ │ │ │ ├── movie.ts │ │ │ │ ├── news.ts │ │ │ │ ├── person.ts │ │ │ │ ├── quality.ts │ │ │ │ ├── season.ts │ │ │ │ ├── server-info.ts │ │ │ │ ├── show.ts │ │ │ │ ├── studio.ts │ │ │ │ ├── user.ts │ │ │ │ ├── watch-info.ts │ │ │ │ ├── watch-status.ts │ │ │ │ └── watchlist.ts │ │ │ ├── theme.ts │ │ │ ├── traits │ │ │ │ ├── images.ts │ │ │ │ ├── index.ts │ │ │ │ └── resource.ts │ │ │ └── utils.ts │ │ └── tsconfig.json │ ├── primitives │ │ ├── package.json │ │ ├── src │ │ │ ├── alert.tsx │ │ │ ├── alert.web.tsx │ │ │ ├── avatar.tsx │ │ │ ├── button.tsx │ │ │ ├── chip.tsx │ │ │ ├── constants │ │ │ │ └── index.ts │ │ │ ├── container.tsx │ │ │ ├── divider.tsx │ │ │ ├── icons.tsx │ │ │ ├── image │ │ │ │ ├── base-image.tsx │ │ │ │ ├── blurhash.tsx │ │ │ │ ├── blurhash.web.tsx │ │ │ │ ├── image.tsx │ │ │ │ ├── image.web.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── sprite.tsx │ │ │ │ └── sprite.web.tsx │ │ │ ├── index.ts │ │ │ ├── input.tsx │ │ │ ├── links.tsx │ │ │ ├── menu.tsx │ │ │ ├── menu.web.tsx │ │ │ ├── popup.tsx │ │ │ ├── progress.tsx │ │ │ ├── select.tsx │ │ │ ├── select.web.tsx │ │ │ ├── skeleton.tsx │ │ │ ├── skeleton.web.tsx │ │ │ ├── slider.tsx │ │ │ ├── snackbar.tsx │ │ │ ├── svg.d.ts │ │ │ ├── text.tsx │ │ │ ├── themes │ │ │ │ ├── catppuccin.ts │ │ │ │ ├── index.ts │ │ │ │ └── theme.tsx │ │ │ ├── tooltip.ts │ │ │ ├── tooltip.web.tsx │ │ │ ├── types.d.ts │ │ │ └── utils │ │ │ │ ├── breakpoints.ts │ │ │ │ ├── capitalize.ts │ │ │ │ ├── head.tsx │ │ │ │ ├── head.web.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── nojs.tsx │ │ │ │ ├── page-style.tsx │ │ │ │ ├── page-style.web.tsx │ │ │ │ ├── spacing.tsx │ │ │ │ └── touchonly.tsx │ │ └── tsconfig.json │ └── ui │ │ ├── package.json │ │ ├── src │ │ ├── admin │ │ │ ├── index.tsx │ │ │ ├── scanner.tsx │ │ │ └── users.tsx │ │ ├── browse │ │ │ ├── grid.tsx │ │ │ ├── header.tsx │ │ │ ├── index.tsx │ │ │ ├── list.tsx │ │ │ └── types.ts │ │ ├── collection │ │ │ └── index.tsx │ │ ├── components │ │ │ ├── context-menus.tsx │ │ │ ├── media-info.tsx │ │ │ ├── rating.tsx │ │ │ └── watchlist-info.tsx │ │ ├── details │ │ │ ├── collection.tsx │ │ │ ├── episode.tsx │ │ │ ├── header.tsx │ │ │ ├── index.tsx │ │ │ ├── movie.tsx │ │ │ ├── person.tsx │ │ │ ├── season.tsx │ │ │ ├── show.tsx │ │ │ └── staff.tsx │ │ ├── downloads │ │ │ ├── index.tsx │ │ │ ├── index.web.tsx │ │ │ ├── page.tsx │ │ │ └── state.tsx │ │ ├── errors │ │ │ ├── connection.tsx │ │ │ ├── error.tsx │ │ │ ├── index.tsx │ │ │ ├── setup.tsx │ │ │ └── unauthorized.tsx │ │ ├── fetch-infinite.tsx │ │ ├── fetch-infinite.web.tsx │ │ ├── fetch.tsx │ │ ├── home │ │ │ ├── genre.tsx │ │ │ ├── header.tsx │ │ │ ├── index.tsx │ │ │ ├── news.tsx │ │ │ ├── recommended.tsx │ │ │ ├── vertical.tsx │ │ │ └── watchlist.tsx │ │ ├── i18n-d.d.ts │ │ ├── index.ts │ │ ├── layout.tsx │ │ ├── login │ │ │ ├── form.tsx │ │ │ ├── index.ts │ │ │ ├── login.tsx │ │ │ ├── oidc.tsx │ │ │ ├── password-input.tsx │ │ │ ├── register.tsx │ │ │ └── server-url.tsx │ │ ├── navbar │ │ │ ├── icon.tsx │ │ │ └── index.tsx │ │ ├── player │ │ │ ├── components │ │ │ │ ├── hover.tsx │ │ │ │ ├── left-buttons.tsx │ │ │ │ ├── right-buttons.tsx │ │ │ │ └── scrubber.tsx │ │ │ ├── index.tsx │ │ │ ├── keyboard.tsx │ │ │ ├── media-session.tsx │ │ │ ├── state.tsx │ │ │ ├── video.tsx │ │ │ ├── video.web.tsx │ │ │ └── watch-status-observer.tsx │ │ ├── search │ │ │ └── index.tsx │ │ ├── settings │ │ │ ├── account.tsx │ │ │ ├── base.tsx │ │ │ ├── general.tsx │ │ │ ├── index.tsx │ │ │ ├── oidc.tsx │ │ │ └── playback.tsx │ │ ├── svg.d.ts │ │ └── utils.ts │ │ └── tsconfig.json ├── translations │ ├── am.json │ ├── ar.json │ ├── de.json │ ├── en.json │ ├── es.json │ ├── fr.json │ ├── gl.json │ ├── index.js │ ├── is.json │ ├── it.json │ ├── ko.json │ ├── ml.json │ ├── nl.json │ ├── pl.json │ ├── pt.json │ ├── pt_br.json │ ├── ro.json │ ├── ru.json │ ├── ta.json │ ├── tr.json │ ├── uk.json │ └── zh.json └── yarn.lock ├── icons ├── banner.png ├── icon-128x128.png ├── icon-16x16.png ├── icon-256x256.ico ├── icon-256x256.png ├── icon-32x32.png ├── icon-64x64.png └── icon.svg ├── renovate.json5 ├── scanner ├── .dockerignore ├── .env.example ├── .gitignore ├── Dockerfile ├── README.md ├── matcher │ ├── __init__.py │ ├── __main__.py │ ├── cache.py │ ├── matcher.py │ ├── parser │ │ ├── guess.py │ │ └── rules.py │ └── subscriber.py ├── providers │ ├── implementations │ │ ├── themoviedatabase.py │ │ ├── thetvdb.py │ │ └── thexem.py │ ├── kyoo_client.py │ ├── provider.py │ ├── rabbit_base.py │ ├── types │ │ ├── collection.py │ │ ├── episode.py │ │ ├── genre.py │ │ ├── metadataid.py │ │ ├── movie.py │ │ ├── season.py │ │ ├── show.py │ │ └── studio.py │ └── utils.py ├── pyproject.toml ├── requirements.txt └── scanner │ ├── __init__.py │ ├── __main__.py │ ├── monitor.py │ ├── publisher.py │ ├── refresher.py │ ├── scanner.py │ └── subscriber.py ├── shell.nix └── transcoder ├── .dockerignore ├── .env.example ├── .gitignore ├── Dockerfile ├── Dockerfile.dev ├── README.md ├── go.mod ├── go.sum ├── main.go ├── migrations ├── 000001_init_db.down.sql ├── 000001_init_db.up.sql ├── 000002_add_hearing_impaired_column.down.sql └── 000002_add_hearing_impaired_column.up.sql ├── src ├── api │ └── pprof.go ├── audiostream.go ├── cmap.go ├── codec.go ├── extract.go ├── filestream.go ├── hwaccel.go ├── info.go ├── keyframes.go ├── metadata.go ├── quality.go ├── runlock.go ├── settings.go ├── storage │ ├── filestorage.go │ ├── s3storage.go │ └── storage.go ├── stream.go ├── subtitles.go ├── thumbnails.go ├── tracker.go ├── transcoder.go ├── utils │ └── utils.go └── videostream.go └── utils.go /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | indent_style = tab 9 | indent_size = tab 10 | smart_tab = true 11 | 12 | [{*.yaml,*.yml,*.nix}] 13 | indent_style = space 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | 7e6e56a366babe17e7891a5897180efbf93c00c5 2 | a5638203a6ecb9f372a5a61e1c8fd443bf3a17fe 3 | 18e301f26acd7f2e97eac26c5f48377fa13956f5 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.Designer.cs linguist-generated=true 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | chart/ @acelinkio 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Create a bug report to help improve Kyoo 3 | labels: [bug] 4 | body: 5 | - type: input 6 | attributes: 7 | label: "Kyoo's version" 8 | - type: textarea 9 | attributes: 10 | label: What happened? 11 | description: Also tell us, what did you expect to happen? 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest a new feature for Kyoo (don't forget to check projects first) 3 | labels: [enhancement] 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: Feature description 8 | -------------------------------------------------------------------------------- /.github/workflows/api-test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - next 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | test: 12 | name: Test api 13 | runs-on: ubuntu-latest 14 | services: 15 | postgres: 16 | image: postgres:15 17 | ports: 18 | - "5432:5432" 19 | env: 20 | POSTGRES_USER: kyoo 21 | POSTGRES_PASSWORD: password 22 | options: >- 23 | --health-cmd pg_isready 24 | --health-interval 10s 25 | --health-timeout 5s 26 | --health-retries 5 27 | steps: 28 | - uses: actions/checkout@v4 29 | - uses: oven-sh/setup-bun@v2 30 | 31 | - name: Install dependencies 32 | working-directory: ./api 33 | run: bun install --frozen-lockfile 34 | 35 | - name: Test 36 | working-directory: ./api 37 | run: bun test 38 | env: 39 | PGHOST: localhost 40 | -------------------------------------------------------------------------------- /.github/workflows/helm-release-chart.yml: -------------------------------------------------------------------------------- 1 | name: Release Helm Chart 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | workflow_dispatch: 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Set up Helm 17 | uses: azure/setup-helm@v4 18 | 19 | - name: Log in to GHCR 20 | uses: docker/login-action@v3 21 | with: 22 | registry: ghcr.io 23 | username: ${{ github.actor }} 24 | password: ${{ secrets.GITHUB_TOKEN }} 25 | 26 | - name: Update Helm Dependencies 27 | run: | 28 | helm dependency update ./chart 29 | 30 | - name: Package Helm Chart 31 | run: | 32 | export tag=$(echo ${GITHUB_REF#refs/tags/} | sed 's/^v//') 33 | helm package ./chart --version $tag --app-version $tag 34 | 35 | - name: Build Helm-safe repo name 36 | run: | 37 | REPO_NAME="$(echo "oci://ghcr.io/${GITHUB_REPOSITORY_OWNER}/helm-charts" | tr '[:upper:]' '[:lower:]')" 38 | echo "REPO_NAME=${REPO_NAME}" >> "${GITHUB_ENV}" 39 | 40 | - name: Push Helm Chart to GHCR 41 | run: | 42 | helm push kyoo-*.tgz "${REPO_NAME}" 43 | -------------------------------------------------------------------------------- /.github/workflows/helm-test-chart.yml: -------------------------------------------------------------------------------- 1 | name: Test Helm Chart 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - next 8 | pull_request: 9 | workflow_dispatch: 10 | 11 | jobs: 12 | test-helm-chart: 13 | name: Test Helm Chart 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | 20 | - name: Set up Helm 21 | uses: azure/setup-helm@v4 22 | 23 | - name: Lint the Helm chart 24 | run: | 25 | helm lint ./chart 26 | 27 | - name: Create Kind cluster 28 | uses: helm/kind-action@v1 29 | 30 | - name: Install Helm chart in Kind cluster (Dry Run) 31 | run: | 32 | helm dependency update ./chart 33 | helm install test-release ./chart --dry-run --debug 34 | 35 | - name: Deploy Helm chart to Kind cluster 36 | run: | 37 | helm install test-release ./chart 38 | 39 | - name: Verify Helm release 40 | run: | 41 | kubectl get all 42 | 43 | - name: Cleanup Kind cluster 44 | run: | 45 | kind delete cluster 46 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | 7 | jobs: 8 | update: 9 | name: Release 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v4 14 | 15 | - name: Set correct versions 16 | run: | 17 | VERSION=${{ github.ref_name }} 18 | VERSION=${VERSION:1} # Remove v prefix 19 | VERSION=${VERSION%.*} # Remove minor version 20 | sed "s/edge/$VERSION/" -i docker-compose.yml 21 | 22 | - uses: actions/upload-artifact@v4 23 | with: 24 | name: docker-compose.yml 25 | path: ./docker-compose.yml 26 | 27 | - name: Upload release artifacts 28 | uses: softprops/action-gh-release@v2 29 | with: 30 | files: ./docker-compose.yml 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /video 2 | .env 3 | .venv 4 | .idea 5 | .vscode 6 | log.html 7 | output.xml 8 | report.html 9 | chart/charts 10 | chart/Chart.lock 11 | tmp 12 | -------------------------------------------------------------------------------- /.pg_format: -------------------------------------------------------------------------------- 1 | tabs=1 2 | function-case=1 #lowercase 3 | keyword-case=1 4 | type-case=1 5 | no-space-function=1 6 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Authors 2 | Ordered by the date of the first commit. 3 | 4 | * Zoe Roux ([@zoriya](http://github.com/zoriya)) 5 | -------------------------------------------------------------------------------- /api/.env.example: -------------------------------------------------------------------------------- 1 | # vi: ft=sh 2 | # shellcheck disable=SC2034 3 | 4 | KYOO_PREFIX=/api 5 | 6 | 7 | # either an hard-coded secret to decode jwts or empty to use keibi's public secret. 8 | # this should only be used in tests 9 | JWT_SECRET= 10 | # used to verify who's making the jwt 11 | JWT_ISSUER=$PUBLIC_URL 12 | # keibi's server to retrieve the public jwt secret 13 | AUTH_SERVER=http://auth:4568 14 | 15 | IMAGES_PATH=./images 16 | 17 | # It is recommended to use the below PG environment variables when possible. 18 | # POSTGRES_URL=postgres://user:password@hostname:port/dbname?sslmode=verify-full&sslrootcert=/path/to/server.crt&sslcert=/path/to/client.crt&sslkey=/path/to/client.key 19 | # The behavior of the below variables match what is documented here: 20 | # https://www.postgresql.org/docs/current/libpq-envars.html 21 | PGUSER=kyoo 22 | PGPASSWORD=password 23 | PGDATABASE=kyooDB 24 | PGHOST=postgres 25 | PGPORT=5432 26 | # PGOPTIONS=-c search_path=kyoo,public 27 | # PGPASSFILE=/my/password # Takes precedence over PGPASSWORD. New line characters are not trimmed. 28 | # PGSSLMODE=verify-full 29 | # PGSSLROOTCERT=/my/serving.crt 30 | # PGSSLCERT=/my/client.crt 31 | # PGSSLKEY=/my/client.key 32 | -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/*.bun 3 | images 4 | -------------------------------------------------------------------------------- /api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM oven/bun AS builder 2 | WORKDIR /app 3 | 4 | COPY package.json bun.lock . 5 | COPY patches patches 6 | RUN bun install --production 7 | 8 | COPY src src 9 | COPY drizzle drizzle 10 | COPY tsconfig.json . 11 | 12 | ENV NODE_ENV=production 13 | RUN bun build \ 14 | --compile \ 15 | --minify-whitespace \ 16 | --minify-syntax \ 17 | --target bun \ 18 | --outfile server \ 19 | ./src/index.ts 20 | 21 | FROM gcr.io/distroless/base 22 | WORKDIR /app 23 | 24 | COPY --from=builder /app/server server 25 | 26 | ENV NODE_ENV=production 27 | EXPOSE 3567 28 | CMD ["./server"] 29 | -------------------------------------------------------------------------------- /api/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM oven/bun AS builder 2 | WORKDIR /app 3 | 4 | COPY package.json bun.lock . 5 | COPY patches patches 6 | RUN bun install --production 7 | 8 | EXPOSE 3567 9 | CMD ["bun", "dev"] 10 | 11 | -------------------------------------------------------------------------------- /api/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../biome.json"], 3 | "formatter": { 4 | "lineWidth": 80 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /api/bunfig.toml: -------------------------------------------------------------------------------- 1 | [test] 2 | preload = ["./tests/setup.ts"] 3 | -------------------------------------------------------------------------------- /api/drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "drizzle-kit"; 2 | 3 | export default defineConfig({ 4 | out: "./drizzle", 5 | schema: "./src/db/schema", 6 | dialect: "postgresql", 7 | casing: "snake_case", 8 | dbCredentials: { 9 | url: process.env.DATABASE_URL!, 10 | }, 11 | migrations: { 12 | schema: "kyoo", 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /api/drizzle/0001_video.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "kyoo"."videos" DROP CONSTRAINT "rendering_pos";--> statement-breakpoint 2 | ALTER TABLE "kyoo"."videos" ALTER COLUMN "rendering" SET DATA TYPE text;--> statement-breakpoint 3 | ALTER TABLE "kyoo"."videos" ALTER COLUMN "rendering" SET NOT NULL;--> statement-breakpoint 4 | ALTER TABLE "kyoo"."videos" ALTER COLUMN "version" SET DEFAULT 1;--> statement-breakpoint 5 | ALTER TABLE "kyoo"."videos" ALTER COLUMN "version" SET NOT NULL;--> statement-breakpoint 6 | ALTER TABLE "kyoo"."videos" ADD COLUMN "slug" varchar(255) NOT NULL;--> statement-breakpoint 7 | ALTER TABLE "kyoo"."videos" ADD CONSTRAINT "videos_slug_unique" UNIQUE("slug"); -------------------------------------------------------------------------------- /api/drizzle/0003_order.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "kyoo"."entries" ALTER COLUMN "order" SET DATA TYPE real;--> statement-breakpoint 2 | ALTER TABLE "kyoo"."entries_translation" ADD COLUMN "tagline" text;--> statement-breakpoint 3 | ALTER TABLE "kyoo"."season_translation" DROP COLUMN IF EXISTS "logo"; 4 | -------------------------------------------------------------------------------- /api/drizzle/0005_trigram.sql: -------------------------------------------------------------------------------- 1 | CREATE INDEX "name_trgm" ON "kyoo"."show_translations" USING gin ("name" gin_trgm_ops);--> statement-breakpoint 2 | CREATE INDEX "tags" ON "kyoo"."show_translations" USING btree ("tags");--> statement-breakpoint 3 | CREATE INDEX "kind" ON "kyoo"."shows" USING hash ("kind");--> statement-breakpoint 4 | CREATE INDEX "rating" ON "kyoo"."shows" USING btree ("rating");--> statement-breakpoint 5 | CREATE INDEX "startAir" ON "kyoo"."shows" USING btree ("start_air"); 6 | -------------------------------------------------------------------------------- /api/drizzle/0006_seasons.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "kyoo"."entries" ALTER COLUMN "created_at" SET NOT NULL;--> statement-breakpoint 2 | ALTER TABLE "kyoo"."entries" ALTER COLUMN "next_refresh" SET NOT NULL;--> statement-breakpoint 3 | ALTER TABLE "kyoo"."seasons" ALTER COLUMN "created_at" SET NOT NULL;--> statement-breakpoint 4 | ALTER TABLE "kyoo"."seasons" ALTER COLUMN "next_refresh" SET NOT NULL;--> statement-breakpoint 5 | CREATE INDEX "show_fk" ON "kyoo"."seasons" USING hash ("show_pk"); -------------------------------------------------------------------------------- /api/drizzle/0007_entries.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "kyoo"."entries" RENAME COLUMN "type" TO "kind";--> statement-breakpoint 2 | ALTER TABLE "kyoo"."entries" ALTER COLUMN "show_pk" SET NOT NULL;--> statement-breakpoint 3 | ALTER TABLE "kyoo"."entries" ADD COLUMN "extra_kind" text; -------------------------------------------------------------------------------- /api/drizzle/0009_collections.sql: -------------------------------------------------------------------------------- 1 | ALTER TYPE "kyoo"."show_kind" ADD VALUE 'collection';--> statement-breakpoint 2 | ALTER TABLE "kyoo"."shows" ADD COLUMN "collection_pk" integer;--> statement-breakpoint 3 | ALTER TABLE "kyoo"."shows" ADD CONSTRAINT "shows_collection_pk_shows_pk_fk" FOREIGN KEY ("collection_pk") REFERENCES "kyoo"."shows"("pk") ON DELETE set null ON UPDATE no action; -------------------------------------------------------------------------------- /api/drizzle/0012_available_count.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "kyoo"."shows" ADD COLUMN "entries_count" integer NOT NULL;--> statement-breakpoint 2 | ALTER TABLE "kyoo"."shows" ADD COLUMN "available_count" integer DEFAULT 0 NOT NULL; -------------------------------------------------------------------------------- /api/drizzle/0013_original.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "kyoo"."videos" ALTER COLUMN "guess" DROP DEFAULT;--> statement-breakpoint 2 | ALTER TABLE "kyoo"."shows" ADD COLUMN "original" jsonb NOT NULL;--> statement-breakpoint 3 | ALTER TABLE "kyoo"."shows" DROP COLUMN "original_language"; -------------------------------------------------------------------------------- /api/drizzle/0015_news.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "kyoo"."entries" ADD COLUMN "available_since" timestamp with time zone; -------------------------------------------------------------------------------- /api/drizzle/0016_mqueue.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "kyoo"."mqueue" ( 2 | "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, 3 | "kind" varchar(255) NOT NULL, 4 | "message" jsonb NOT NULL, 5 | "attempt" integer DEFAULT 0 NOT NULL, 6 | "created_at" timestamp with time zone DEFAULT now() NOT NULL 7 | ); 8 | --> statement-breakpoint 9 | CREATE INDEX "mqueue_created" ON "kyoo"."mqueue" USING btree ("created_at"); -------------------------------------------------------------------------------- /api/drizzle/0018_history.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "kyoo"."history" ALTER COLUMN "video_pk" DROP NOT NULL;--> statement-breakpoint 2 | ALTER TABLE "kyoo"."watchlist" ALTER COLUMN "status" SET DATA TYPE text;--> statement-breakpoint 3 | DROP TYPE "kyoo"."watchlist_status";--> statement-breakpoint 4 | CREATE TYPE "kyoo"."watchlist_status" AS ENUM('watching', 'rewatching', 'completed', 'dropped', 'planned');--> statement-breakpoint 5 | ALTER TABLE "kyoo"."watchlist" ALTER COLUMN "status" SET DATA TYPE "kyoo"."watchlist_status" USING "status"::"kyoo"."watchlist_status"; -------------------------------------------------------------------------------- /api/drizzle/0019_nextup.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "kyoo"."watchlist" ADD COLUMN "last_played_at" timestamp with time zone; -------------------------------------------------------------------------------- /api/drizzle/0020_video_unique.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "kyoo"."entries" ALTER COLUMN "kind" SET DATA TYPE text;--> statement-breakpoint 2 | DROP TYPE "kyoo"."entry_type";--> statement-breakpoint 3 | CREATE TYPE "kyoo"."entry_type" AS ENUM('episode', 'movie', 'special', 'extra');--> statement-breakpoint 4 | ALTER TABLE "kyoo"."entries" ALTER COLUMN "kind" SET DATA TYPE "kyoo"."entry_type" USING "kind"::"kyoo"."entry_type";--> statement-breakpoint 5 | ALTER TABLE "kyoo"."videos" ADD CONSTRAINT "rendering_unique" UNIQUE NULLS NOT DISTINCT("rendering","part","version"); -------------------------------------------------------------------------------- /api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "version": "5.0.0", 4 | "scripts": { 5 | "dev": "bun --watch src/index.ts", 6 | "build": "bun build src/index.ts --target bun --outdir ./dist", 7 | "start": "NODE_ENV=production bun dist/index.js", 8 | "test": "bun test", 9 | "format": "biome check --write ." 10 | }, 11 | "dependencies": { 12 | "@elysiajs/swagger": "zoriya/elysia-swagger#build", 13 | "blurhash": "^2.0.5", 14 | "drizzle-kit": "^0.31.0", 15 | "drizzle-orm": "0.43.1", 16 | "elysia": "^1.2.25", 17 | "jose": "^6.0.10", 18 | "parjs": "^1.3.9", 19 | "pg": "^8.15.6", 20 | "sharp": "^0.34.1" 21 | }, 22 | "devDependencies": { 23 | "@types/pg": "^8.11.14", 24 | "node-addon-api": "^8.3.1", 25 | "bun-types": "^1.2.11" 26 | }, 27 | "module": "src/index.js", 28 | "patchedDependencies": { 29 | "drizzle-orm@0.43.1": "patches/drizzle-orm@0.43.1.patch" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /api/src/controllers/profiles/profile.ts: -------------------------------------------------------------------------------- 1 | import { eq, sql } from "drizzle-orm"; 2 | import { db } from "~/db"; 3 | import { profiles } from "~/db/schema"; 4 | 5 | export async function getOrCreateProfile(userId: string) { 6 | let [profile] = await db 7 | .select({ pk: profiles.pk }) 8 | .from(profiles) 9 | .where(eq(profiles.id, userId)) 10 | .limit(1); 11 | if (profile) return profile.pk; 12 | 13 | [profile] = await db 14 | .insert(profiles) 15 | .values({ id: userId }) 16 | .onConflictDoUpdate({ 17 | // we can't do `onConflictDoNothing` because on race conditions 18 | // we still want the profile to be returned. 19 | target: [profiles.id], 20 | set: { id: sql`excluded.id` }, 21 | }) 22 | .returning({ pk: profiles.pk }); 23 | return profile.pk; 24 | } 25 | -------------------------------------------------------------------------------- /api/src/controllers/seed/refresh.ts: -------------------------------------------------------------------------------- 1 | // oh i hate js dates so much. 2 | export const guessNextRefresh = (airDate: Date | string) => { 3 | if (typeof airDate === "string") airDate = new Date(airDate); 4 | const diff = new Date().getTime() - airDate.getTime(); 5 | const days = diff / (24 * 60 * 60 * 1000); 6 | 7 | const ret = new Date(); 8 | if (days <= 4) ret.setDate(ret.getDate() + 4); 9 | else if (days <= 21) ret.setDate(ret.getDate() + 14); 10 | else ret.setMonth(ret.getMonth() + 2); 11 | return ret.toISOString().substring(0, 10); 12 | }; 13 | -------------------------------------------------------------------------------- /api/src/db/schema/history.ts: -------------------------------------------------------------------------------- 1 | import { sql } from "drizzle-orm"; 2 | import { check, index, integer, timestamp } from "drizzle-orm/pg-core"; 3 | import { entries } from "./entries"; 4 | import { profiles } from "./profiles"; 5 | import { schema } from "./utils"; 6 | import { videos } from "./videos"; 7 | 8 | export const history = schema.table( 9 | "history", 10 | { 11 | pk: integer().primaryKey().generatedAlwaysAsIdentity(), 12 | profilePk: integer() 13 | .notNull() 14 | .references(() => profiles.pk, { onDelete: "cascade" }), 15 | entryPk: integer() 16 | .notNull() 17 | .references(() => entries.pk, { onDelete: "cascade" }), 18 | videoPk: integer().references(() => videos.pk, { onDelete: "set null" }), 19 | percent: integer().notNull().default(0), 20 | time: integer(), 21 | playedDate: timestamp({ withTimezone: true, mode: "string" }) 22 | .notNull() 23 | .defaultNow(), 24 | }, 25 | (t) => [ 26 | index("history_play_date").on(t.playedDate.desc()), 27 | 28 | check("percent_valid", sql`${t.percent} between 0 and 100`), 29 | ], 30 | ); 31 | -------------------------------------------------------------------------------- /api/src/db/schema/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./entries"; 2 | export * from "./seasons"; 3 | export * from "./shows"; 4 | export * from "./studios"; 5 | export * from "./staff"; 6 | export * from "./videos"; 7 | export * from "./profiles"; 8 | export * from "./history"; 9 | export * from "./mqueue"; 10 | -------------------------------------------------------------------------------- /api/src/db/schema/mqueue.ts: -------------------------------------------------------------------------------- 1 | import { 2 | index, 3 | integer, 4 | jsonb, 5 | timestamp, 6 | uuid, 7 | varchar, 8 | } from "drizzle-orm/pg-core"; 9 | import { schema } from "./utils"; 10 | 11 | export const mqueue = schema.table( 12 | "mqueue", 13 | { 14 | id: uuid().notNull().primaryKey().defaultRandom(), 15 | kind: varchar({ length: 255 }).notNull(), 16 | message: jsonb().notNull(), 17 | attempt: integer().notNull().default(0), 18 | createdAt: timestamp({ withTimezone: true, mode: "string" }) 19 | .notNull() 20 | .defaultNow(), 21 | }, 22 | (t) => [index("mqueue_created").on(t.createdAt)], 23 | ); 24 | -------------------------------------------------------------------------------- /api/src/db/schema/profiles.ts: -------------------------------------------------------------------------------- 1 | import { integer, uuid } from "drizzle-orm/pg-core"; 2 | import { schema } from "./utils"; 3 | 4 | // user info is stored in keibi (the auth service). 5 | // this table is only there for relations. 6 | export const profiles = schema.table("profiles", { 7 | pk: integer().primaryKey().generatedAlwaysAsIdentity(), 8 | id: uuid().notNull().unique(), 9 | }); 10 | -------------------------------------------------------------------------------- /api/src/db/schema/utils.ts: -------------------------------------------------------------------------------- 1 | import { jsonb, pgSchema, varchar } from "drizzle-orm/pg-core"; 2 | import type { Image } from "~/models/utils"; 3 | 4 | export const schema = pgSchema("kyoo"); 5 | 6 | export const language = () => varchar({ length: 255 }); 7 | 8 | export const image = () => jsonb().$type(); 9 | 10 | export const externalid = () => 11 | jsonb() 12 | .$type< 13 | Record< 14 | string, 15 | { 16 | dataId: string; 17 | link: string | null; 18 | } 19 | > 20 | >() 21 | .notNull() 22 | .default({}); 23 | -------------------------------------------------------------------------------- /api/src/models/entry/base-entry.ts: -------------------------------------------------------------------------------- 1 | import { t } from "elysia"; 2 | import { Image } from "../utils/image"; 3 | 4 | export const BaseEntry = () => 5 | t.Object({ 6 | airDate: t.Nullable(t.String({ format: "date" })), 7 | runtime: t.Nullable( 8 | t.Number({ 9 | minimum: 0, 10 | description: "Runtime of the episode in minutes", 11 | }), 12 | ), 13 | thumbnail: t.Nullable(Image), 14 | 15 | nextRefresh: t.String({ format: "date-time" }), 16 | }); 17 | 18 | export const EntryTranslation = () => 19 | t.Object({ 20 | name: t.Nullable(t.String()), 21 | description: t.Nullable(t.String()), 22 | }); 23 | -------------------------------------------------------------------------------- /api/src/models/entry/index.ts: -------------------------------------------------------------------------------- 1 | import { t } from "elysia"; 2 | import { Episode, SeedEpisode } from "./episode"; 3 | import type { Extra } from "./extra"; 4 | import { MovieEntry, SeedMovieEntry } from "./movie-entry"; 5 | import { SeedSpecial, Special } from "./special"; 6 | 7 | export const Entry = t.Union([Episode, MovieEntry, Special]); 8 | export type Entry = Episode | MovieEntry | Special; 9 | 10 | export const SeedEntry = t.Union([SeedEpisode, SeedMovieEntry, SeedSpecial]); 11 | export type SeedEntry = SeedEpisode | SeedMovieEntry | SeedSpecial; 12 | 13 | export type EntryKind = Entry["kind"] | Extra["kind"]; 14 | 15 | export * from "./episode"; 16 | export * from "./movie-entry"; 17 | export * from "./special"; 18 | export * from "./extra"; 19 | -------------------------------------------------------------------------------- /api/src/models/error.ts: -------------------------------------------------------------------------------- 1 | import { t } from "elysia"; 2 | 3 | export const KError = t.Object( 4 | { 5 | status: t.Integer(), 6 | message: t.String(), 7 | details: t.Optional(t.Any()), 8 | }, 9 | { 10 | description: "Invalid parameters.", 11 | examples: [{ status: 404, message: "Movie not found" }], 12 | }, 13 | ); 14 | export type KError = typeof KError.static; 15 | 16 | export class KErrorT extends Error { 17 | constructor(message: string, details?: any) { 18 | super(JSON.stringify({ code: "KError", status: 422, message, details })); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /api/src/models/examples/dune-collection.ts: -------------------------------------------------------------------------------- 1 | import type { SeedCollection } from "~/models/collections"; 2 | 3 | export const duneCollection: SeedCollection = { 4 | slug: "dune-collection", 5 | translations: { 6 | en: { 7 | name: " Dune Collection", 8 | tagline: "A mythic and emotionally charged hero journey.", 9 | description: 10 | "The saga of Paul Atreides and his rise to power on the deadly planet Arrakis.", 11 | aliases: [], 12 | tags: ["sci-fi", "adventure", "drama", "action", "epic"], 13 | poster: 14 | "https://image.tmdb.org/t/p/original/wD57HqZ6fXwwDdfQLo4hXLRwGV1.jpg", 15 | thumbnail: 16 | "https://image.tmdb.org/t/p/original/k2ocXnNkmvE6rJomRkExIStFq3v.jpg", 17 | banner: null, 18 | logo: "https://image.tmdb.org/t/p/original/5nDsd3u1c6kDphbtIqkHseLg7HL.png", 19 | }, 20 | }, 21 | originalLanguage: "en", 22 | genres: ["adventure", "science-fiction"], 23 | rating: 80, 24 | externalId: { 25 | themoviedatabase: { 26 | dataId: "726871", 27 | link: "https://www.themoviedb.org/collection/726871-dune-collection", 28 | }, 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /api/src/models/examples/index.ts: -------------------------------------------------------------------------------- 1 | import { KindGuard } from "@sinclair/typebox"; 2 | import type { TSchema } from "elysia"; 3 | 4 | export const registerExamples = ( 5 | schema: T, 6 | ...examples: (Partial | undefined)[] 7 | ) => { 8 | if (KindGuard.IsUnion(schema)) { 9 | for (const union of schema.anyOf) { 10 | registerExamples(union, ...examples); 11 | } 12 | return; 13 | } 14 | if (KindGuard.IsIntersect(schema)) { 15 | for (const intersec of schema.allOf) { 16 | registerExamples(intersec, ...examples); 17 | } 18 | return; 19 | } 20 | for (const example of examples) { 21 | if (!example) continue; 22 | for (const [key, val] of Object.entries(example)) { 23 | const prop = schema.properties[ 24 | key as keyof typeof schema.properties 25 | ] as TSchema; 26 | if (!prop) continue; 27 | prop.examples ??= []; 28 | prop.examples.push(val); 29 | } 30 | } 31 | }; 32 | 33 | export * from "./bubble"; 34 | export * from "./made-in-abyss"; 35 | export * from "./dune-1984"; 36 | export * from "./dune-2021"; 37 | export * from "./dune-collection"; 38 | -------------------------------------------------------------------------------- /api/src/models/history.ts: -------------------------------------------------------------------------------- 1 | import { t } from "elysia"; 2 | import { comment } from "~/utils"; 3 | 4 | export const Progress = t.Object({ 5 | percent: t.Integer({ minimum: 0, maximum: 100 }), 6 | time: t.Nullable( 7 | t.Integer({ 8 | minimum: 0, 9 | description: comment` 10 | When this episode was stopped (in seconds since the start). 11 | This value is null if the entry was never watched or is finished. 12 | `, 13 | }), 14 | ), 15 | playedDate: t.Nullable(t.String({ format: "date-time" })), 16 | videoId: t.Nullable( 17 | t.String({ 18 | format: "uuid", 19 | description: comment` 20 | Id of the video the user watched. 21 | This can be used to resume playback in the correct video file 22 | without asking the user what video to play. 23 | 24 | This will be null if the user did not watch the entry or 25 | if the video was deleted since. 26 | `, 27 | }), 28 | ), 29 | }); 30 | export type Progress = typeof Progress.static; 31 | 32 | export const SeedHistory = t.Intersect([ 33 | t.Object({ 34 | entry: t.String({ 35 | description: "Id or slug of the entry/movie you watched", 36 | }), 37 | }), 38 | Progress, 39 | ]); 40 | export type SeedHistory = typeof SeedHistory.static; 41 | -------------------------------------------------------------------------------- /api/src/models/show.ts: -------------------------------------------------------------------------------- 1 | import { t } from "elysia"; 2 | import { Collection } from "./collections"; 3 | import { Movie } from "./movie"; 4 | import { Serie } from "./serie"; 5 | 6 | export const Show = t.Union([ 7 | t.Intersect([t.Object({ kind: t.Literal("movie") }), Movie]), 8 | t.Intersect([t.Object({ kind: t.Literal("serie") }), Serie]), 9 | t.Intersect([t.Object({ kind: t.Literal("collection") }), Collection]), 10 | ]); 11 | export type Show = typeof Show.static; 12 | -------------------------------------------------------------------------------- /api/src/models/staff-roles.ts: -------------------------------------------------------------------------------- 1 | import { t } from "elysia"; 2 | import { Show } from "./show"; 3 | import { Role, Staff } from "./staff"; 4 | 5 | export const RoleWShow = t.Intersect([ 6 | Role, 7 | t.Object({ 8 | show: Show, 9 | }), 10 | ]); 11 | export type RoleWShow = typeof RoleWShow.static; 12 | 13 | export const RoleWStaff = t.Intersect([ 14 | Role, 15 | t.Object({ 16 | staff: Staff, 17 | }), 18 | ]); 19 | export type RoleWStaff = typeof RoleWStaff.static; 20 | -------------------------------------------------------------------------------- /api/src/models/studio.ts: -------------------------------------------------------------------------------- 1 | import { t } from "elysia"; 2 | import type { Prettify } from "elysia/dist/types"; 3 | import { bubbleImages, madeInAbyss, registerExamples } from "./examples"; 4 | import { DbMetadata, ExternalId, Resource, TranslationRecord } from "./utils"; 5 | import { Image, SeedImage } from "./utils/image"; 6 | 7 | const BaseStudio = t.Object({ 8 | externalId: ExternalId(), 9 | }); 10 | 11 | export const StudioTranslation = t.Object({ 12 | name: t.String(), 13 | logo: t.Nullable(Image), 14 | }); 15 | export type StudioTranslation = typeof StudioTranslation.static; 16 | 17 | export const Studio = t.Composite([ 18 | Resource(), 19 | StudioTranslation, 20 | BaseStudio, 21 | DbMetadata, 22 | ]); 23 | export type Studio = Prettify; 24 | 25 | export const SeedStudio = t.Composite([ 26 | BaseStudio, 27 | t.Object({ 28 | slug: t.String({ format: "slug" }), 29 | translations: TranslationRecord( 30 | t.Composite([ 31 | t.Omit(StudioTranslation, ["logo"]), 32 | t.Object({ 33 | logo: t.Nullable(SeedImage), 34 | }), 35 | ]), 36 | ), 37 | }), 38 | ]); 39 | export type SeedStudio = Prettify; 40 | 41 | const ex = madeInAbyss.studios[0]; 42 | registerExamples(Studio, { ...ex, ...ex.translations.en, ...bubbleImages }); 43 | -------------------------------------------------------------------------------- /api/src/models/utils/db-metadata.ts: -------------------------------------------------------------------------------- 1 | import { t } from "elysia"; 2 | 3 | export const DbMetadata = t.Object({ 4 | createdAt: t.String({ format: "date-time" }), 5 | updatedAt: t.String({ format: "date-time" }), 6 | }); 7 | -------------------------------------------------------------------------------- /api/src/models/utils/descriptions.ts: -------------------------------------------------------------------------------- 1 | import { comment } from "~/utils"; 2 | 3 | export const desc = { 4 | preferOriginal: comment` 5 | Prefer images in the original's language. If true, will return untranslated images instead of the translated ones. 6 | 7 | If unspecified, kyoo will look at the current user's settings to decide what to do. 8 | `, 9 | 10 | after: comment` 11 | Cursor for the pagination. 12 | You can ignore this and only use the prev/next field in the response. 13 | `, 14 | 15 | query: comment` 16 | Search query. 17 | Searching automatically sort via relevance before the other sort parameters. 18 | `, 19 | }; 20 | -------------------------------------------------------------------------------- /api/src/models/utils/external-id.ts: -------------------------------------------------------------------------------- 1 | import { t } from "elysia"; 2 | import { comment } from "../../utils"; 3 | 4 | export const ExternalId = () => 5 | t.Record( 6 | t.String(), 7 | t.Object({ 8 | dataId: t.String(), 9 | link: t.Nullable(t.String({ format: "uri" })), 10 | }), 11 | ); 12 | 13 | export const EpisodeId = t.Record( 14 | t.String(), 15 | t.Object({ 16 | serieId: t.String({ 17 | descrpition: comment` 18 | Id on the external website. 19 | We store the serie's id because episode id are rarely stable. 20 | `, 21 | }), 22 | season: t.Nullable( 23 | t.Integer({ 24 | description: "Null if the external website uses absolute numbering.", 25 | }), 26 | ), 27 | episode: t.Integer(), 28 | link: t.Nullable(t.String({ format: "uri" })), 29 | }), 30 | ); 31 | export type EpisodeId = typeof EpisodeId.static; 32 | 33 | export const SeasonId = t.Record( 34 | t.String(), 35 | t.Object({ 36 | serieId: t.String({ 37 | descrpition: comment` 38 | Id on the external website. 39 | We store the serie's id because episode id are rarely stable. 40 | `, 41 | }), 42 | season: t.Integer(), 43 | link: t.Nullable(t.String({ format: "uri" })), 44 | }), 45 | ); 46 | export type SeasonId = typeof SeasonId.static; 47 | -------------------------------------------------------------------------------- /api/src/models/utils/genres.ts: -------------------------------------------------------------------------------- 1 | import { t } from "elysia"; 2 | 3 | export const Genre = t.UnionEnum([ 4 | "action", 5 | "adventure", 6 | "animation", 7 | "comedy", 8 | "crime", 9 | "documentary", 10 | "drama", 11 | "family", 12 | "fantasy", 13 | "history", 14 | "horror", 15 | "music", 16 | "mystery", 17 | "romance", 18 | "science-fiction", 19 | "thriller", 20 | "war", 21 | "western", 22 | "kids", 23 | "reality", 24 | "politics", 25 | "soap", 26 | "talk", 27 | ]); 28 | export type Genre = typeof Genre.static; 29 | -------------------------------------------------------------------------------- /api/src/models/utils/image.ts: -------------------------------------------------------------------------------- 1 | import { t } from "elysia"; 2 | 3 | export const Image = t.Object({ 4 | id: t.String(), 5 | source: t.String({ format: "uri" }), 6 | blurhash: t.String(), 7 | }); 8 | export type Image = typeof Image.static; 9 | 10 | export const SeedImage = t.String({ format: "uri" }); 11 | -------------------------------------------------------------------------------- /api/src/models/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./external-id"; 2 | export * from "./genres"; 3 | export * from "./image"; 4 | export * from "./language"; 5 | export * from "./resource"; 6 | export * from "./filters"; 7 | export * from "./page"; 8 | export * from "./sort"; 9 | export * from "./keyset-paginate"; 10 | export * from "./db-metadata"; 11 | export * from "./original"; 12 | export * from "./relations"; 13 | -------------------------------------------------------------------------------- /api/src/models/utils/original.ts: -------------------------------------------------------------------------------- 1 | import { t } from "elysia"; 2 | import { comment } from "~/utils"; 3 | import { Language } from "./language"; 4 | 5 | export const Original = t.Object({ 6 | language: Language({ 7 | description: "The language code this was made in.", 8 | examples: ["ja"], 9 | }), 10 | name: t.String({ 11 | description: "The name in the original language", 12 | examples: ["進撃の巨人"], 13 | }), 14 | latinName: t.Nullable( 15 | t.String({ 16 | description: comment` 17 | The original name but using latin scripts. 18 | This is only set if the original language is written with another 19 | alphabet (like japanase, korean, chineses...) 20 | `, 21 | examples: ["Shingeki no Kyojin"], 22 | }), 23 | ), 24 | }); 25 | export type Original = typeof Original.static; 26 | -------------------------------------------------------------------------------- /api/src/models/utils/relations.ts: -------------------------------------------------------------------------------- 1 | import { type SQL, type Subquery, sql } from "drizzle-orm"; 2 | import type { SelectResultField } from "drizzle-orm/query-builders/select.types"; 3 | 4 | export const buildRelations = < 5 | R extends string, 6 | P extends object, 7 | Rel extends Record Subquery>, 8 | >( 9 | enabled: R[], 10 | relations: Rel, 11 | params?: P, 12 | ) => { 13 | // we wrap that in a sql`` instead of using the builder because of this issue 14 | // https://github.com/drizzle-team/drizzle-orm/pull/1674 15 | return Object.fromEntries( 16 | enabled.map((x) => [x, sql`${relations[x](params!)}`]), 17 | ) as { 18 | [P in R]?: SQL< 19 | ReturnType["_"]["selectedFields"] extends { 20 | [key: string]: infer TValue; 21 | } 22 | ? SelectResultField 23 | : never 24 | >; 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /api/src/models/utils/resource.ts: -------------------------------------------------------------------------------- 1 | import { FormatRegistry } from "@sinclair/typebox"; 2 | import { TypeCompiler } from "@sinclair/typebox/compiler"; 3 | import { t } from "elysia"; 4 | 5 | export const slugPattern = "^[a-z0-9-]+$"; 6 | 7 | FormatRegistry.Set("slug", (slug) => { 8 | return /^[a-z0-9-]+$/g.test(slug); 9 | }); 10 | 11 | export const Resource = () => 12 | t.Object({ 13 | id: t.String({ format: "uuid" }), 14 | slug: t.String({ format: "slug" }), 15 | }); 16 | export type Resource = ReturnType["static"]; 17 | 18 | const checker = TypeCompiler.Compile(t.String({ format: "uuid" })); 19 | export const isUuid = (id: string) => checker.Check(id); 20 | -------------------------------------------------------------------------------- /api/src/models/watchlist.ts: -------------------------------------------------------------------------------- 1 | import { t } from "elysia"; 2 | 3 | export const WatchlistStatus = t.UnionEnum([ 4 | "completed", 5 | "watching", 6 | "rewatching", 7 | "dropped", 8 | "planned", 9 | ]); 10 | export type WatchlistStatus = typeof WatchlistStatus.static; 11 | 12 | export const SerieWatchStatus = t.Object({ 13 | status: WatchlistStatus, 14 | score: t.Nullable(t.Integer({ minimum: 0, maximum: 100 })), 15 | startedAt: t.Nullable(t.String({ format: "date-time" })), 16 | completedAt: t.Nullable(t.String({ format: "date-time" })), 17 | seenCount: t.Integer({ 18 | description: "The number of episodes you watched in this serie.", 19 | minimum: 0, 20 | }), 21 | }); 22 | export type SerieWatchStatus = typeof SerieWatchStatus.static; 23 | 24 | export const MovieWatchStatus = t.Composite([ 25 | t.Omit(SerieWatchStatus, ["startedAt", "seenCount"]), 26 | t.Object({ 27 | percent: t.Integer({ 28 | minimum: 0, 29 | maximum: 100, 30 | }), 31 | }), 32 | ]); 33 | export type MovieWatchStatus = typeof MovieWatchStatus.static; 34 | -------------------------------------------------------------------------------- /api/src/utils.ts: -------------------------------------------------------------------------------- 1 | import type { BunFile, S3File } from "bun"; 2 | 3 | // remove indent in multi-line comments 4 | export const comment = (str: TemplateStringsArray, ...values: any[]) => 5 | str 6 | .reduce((acc, str, i) => `${acc}${values[i - 1]}${str}`) 7 | .replace(/(^\s)|(\s+$)/g, "") // first & last whitespaces 8 | .replace(/^[ \t]+/gm, "") // leading spaces 9 | .replace(/([^\n])\n([^\n])/g, "$1 $2") // two lines to space separated line 10 | .replace(/\n{2}/g, "\n"); // keep newline if there's an empty line 11 | 12 | export function getYear(date: string) { 13 | return new Date(date).getUTCFullYear(); 14 | } 15 | 16 | export type Prettify = { 17 | [K in keyof T]: Prettify; 18 | } & {}; 19 | 20 | // Returns either a filesystem-backed file, or a S3-backed file, 21 | // depending on whether or not S3 environment variables are set. 22 | export function getFile(path: string): BunFile | S3File { 23 | if ("S3_BUCKET" in process.env || "AWS_BUCKET" in process.env) { 24 | // This will use a S3 client configured via environment variables. 25 | // See https://bun.sh/docs/api/s3#credentials for more details. 26 | return Bun.s3.file(path); 27 | } 28 | 29 | return Bun.file(path); 30 | } 31 | -------------------------------------------------------------------------------- /api/tests/collection/seed-collection.test.ts: -------------------------------------------------------------------------------- 1 | import { beforeAll, describe, expect, it } from "bun:test"; 2 | import { createMovie } from "tests/helpers"; 3 | import { expectStatus } from "tests/utils"; 4 | import { db } from "~/db"; 5 | import { shows } from "~/db/schema"; 6 | import { dune } from "~/models/examples/dune-2021"; 7 | import { duneCollection } from "~/models/examples/dune-collection"; 8 | 9 | beforeAll(async () => { 10 | await db.delete(shows); 11 | }); 12 | 13 | describe("Collection seeding", () => { 14 | it("Can create a movie with a collection", async () => { 15 | const [resp, body] = await createMovie({ 16 | ...dune, 17 | collection: duneCollection, 18 | }); 19 | expectStatus(resp, body).toBe(201); 20 | expect(body.id).toBeString(); 21 | expect(body.slug).toBe("dune"); 22 | expect(body.collection.slug).toBe("dune-collection"); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /api/tests/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./movies-helper"; 2 | export * from "./series-helper"; 3 | export * from "./shows-helper"; 4 | export * from "./studio-helper"; 5 | export * from "./staff-helper"; 6 | export * from "./videos-helper"; 7 | 8 | export * from "~/base"; 9 | -------------------------------------------------------------------------------- /api/tests/helpers/jwt.ts: -------------------------------------------------------------------------------- 1 | import { SignJWT } from "jose"; 2 | 3 | export async function getJwtHeaders() { 4 | const jwt = await new SignJWT({ 5 | sub: "39158be0-3f59-4c45-b00d-d25b3bc2b884", 6 | sid: "04ac7ecc-255b-481d-b0c8-537c1578e3d5", 7 | username: "test-username", 8 | permissions: ["core.read", "core.write", "users.read"], 9 | }) 10 | .setProtectedHeader({ alg: "HS256" }) 11 | .setIssuedAt() 12 | .setIssuer(process.env.JWT_ISSUER!) 13 | .setExpirationTime("2h") 14 | .sign(new TextEncoder().encode(process.env.JWT_SECRET)); 15 | 16 | return { Authorization: `Bearer ${jwt}` }; 17 | } 18 | -------------------------------------------------------------------------------- /api/tests/helpers/studio-helper.ts: -------------------------------------------------------------------------------- 1 | import { buildUrl } from "tests/utils"; 2 | import { app } from "~/base"; 3 | import { getJwtHeaders } from "./jwt"; 4 | 5 | export const getStudio = async ( 6 | id: string, 7 | { langs, ...query }: { langs?: string; preferOriginal?: boolean }, 8 | ) => { 9 | const resp = await app.handle( 10 | new Request(buildUrl(`studios/${id}`, query), { 11 | method: "GET", 12 | headers: langs 13 | ? { 14 | "Accept-Language": langs, 15 | ...(await getJwtHeaders()), 16 | } 17 | : await getJwtHeaders(), 18 | }), 19 | ); 20 | const body = await resp.json(); 21 | return [resp, body] as const; 22 | }; 23 | 24 | export const getShowsByStudio = async ( 25 | studio: string, 26 | { 27 | langs, 28 | ...opts 29 | }: { 30 | filter?: string; 31 | limit?: number; 32 | after?: string; 33 | sort?: string | string[]; 34 | query?: string; 35 | langs?: string; 36 | preferOriginal?: boolean; 37 | }, 38 | ) => { 39 | const resp = await app.handle( 40 | new Request(buildUrl(`studios/${studio}/shows`, opts), { 41 | method: "GET", 42 | headers: langs 43 | ? { 44 | "Accept-Language": langs, 45 | ...(await getJwtHeaders()), 46 | } 47 | : await getJwtHeaders(), 48 | }), 49 | ); 50 | const body = await resp.json(); 51 | return [resp, body] as const; 52 | }; 53 | -------------------------------------------------------------------------------- /api/tests/helpers/videos-helper.ts: -------------------------------------------------------------------------------- 1 | import { buildUrl } from "tests/utils"; 2 | import { app } from "~/base"; 3 | import type { SeedVideo } from "~/models/video"; 4 | import { getJwtHeaders } from "./jwt"; 5 | 6 | export const createVideo = async (video: SeedVideo | SeedVideo[]) => { 7 | const resp = await app.handle( 8 | new Request(buildUrl("videos"), { 9 | method: "POST", 10 | body: JSON.stringify(Array.isArray(video) ? video : [video]), 11 | headers: { 12 | "Content-Type": "application/json", 13 | ...(await getJwtHeaders()), 14 | }, 15 | }), 16 | ); 17 | const body = await resp.json(); 18 | return [resp, body] as const; 19 | }; 20 | 21 | export const getVideos = async () => { 22 | const resp = await app.handle( 23 | new Request(buildUrl("videos"), { 24 | method: "GET", 25 | headers: await getJwtHeaders(), 26 | }), 27 | ); 28 | const body = await resp.json(); 29 | return [resp, body] as const; 30 | }; 31 | 32 | export const deleteVideo = async (paths: string[]) => { 33 | const resp = await app.handle( 34 | new Request(buildUrl("videos"), { 35 | method: "DELETE", 36 | body: JSON.stringify(paths), 37 | headers: { 38 | "Content-Type": "application/json", 39 | ...(await getJwtHeaders()), 40 | }, 41 | }), 42 | ); 43 | const body = await resp.json(); 44 | return [resp, body] as const; 45 | }; 46 | -------------------------------------------------------------------------------- /api/tests/misc/images.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "bun:test"; 2 | import { eq } from "drizzle-orm"; 3 | import { defaultBlurhash, processImages } from "~/controllers/seed/images"; 4 | import { db } from "~/db"; 5 | import { mqueue, shows, staff, studios, videos } from "~/db/schema"; 6 | import { madeInAbyss } from "~/models/examples"; 7 | import { createSerie } from "../helpers"; 8 | 9 | describe("images", () => { 10 | it("Create a serie download images", async () => { 11 | await db.delete(shows); 12 | await db.delete(studios); 13 | await db.delete(staff); 14 | await db.delete(videos); 15 | await db.delete(mqueue); 16 | 17 | await createSerie(madeInAbyss); 18 | const release = await processImages(); 19 | // remove notifications to prevent other images to be downloaded (do not curl 20000 images for nothing) 20 | release(); 21 | 22 | const ret = await db.query.shows.findFirst({ 23 | where: eq(shows.slug, madeInAbyss.slug), 24 | }); 25 | expect(ret!.slug).toBe(madeInAbyss.slug); 26 | expect(ret!.original.poster!.blurhash).toBeString(); 27 | expect(ret!.original.poster!.blurhash).not.toBe(defaultBlurhash); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /api/tests/series/get-seasons.test.ts: -------------------------------------------------------------------------------- 1 | import { beforeAll, describe, expect, it } from "bun:test"; 2 | import { createSerie, getSeasons } from "tests/helpers"; 3 | import { expectStatus } from "tests/utils"; 4 | import { madeInAbyss } from "~/models/examples"; 5 | 6 | beforeAll(async () => { 7 | await createSerie(madeInAbyss); 8 | }); 9 | 10 | describe("Get seasons", () => { 11 | it("Invalid slug", async () => { 12 | const [resp, body] = await getSeasons("sotneuhn", { langs: "en" }); 13 | 14 | expectStatus(resp, body).toBe(404); 15 | expect(body).toMatchObject({ 16 | status: 404, 17 | message: expect.any(String), 18 | }); 19 | }); 20 | it("Default sort order", async () => { 21 | const [resp, body] = await getSeasons(madeInAbyss.slug, { langs: "en" }); 22 | 23 | expectStatus(resp, body).toBe(200); 24 | expect(body.items).toBeArrayOfSize(madeInAbyss.seasons.length); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /api/tests/setup.ts: -------------------------------------------------------------------------------- 1 | import { beforeAll } from "bun:test"; 2 | import { migrate } from "~/db"; 3 | 4 | process.env.JWT_SECRET = "this is a secret"; 5 | process.env.JWT_ISSUER = "https://kyoo.zoriya.dev"; 6 | 7 | beforeAll(async () => { 8 | await migrate(); 9 | }); 10 | -------------------------------------------------------------------------------- /api/tests/utils.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "bun:test"; 2 | 3 | export function expectStatus(resp: Response, body: object) { 4 | const matcher = expect({ ...body, status: resp.status }); 5 | return { 6 | toBe: (status: number) => { 7 | matcher.toMatchObject({ status: status }); 8 | }, 9 | }; 10 | } 11 | 12 | export const buildUrl = (route: string, query?: Record) => { 13 | const params = new URLSearchParams(); 14 | if (query) { 15 | for (const [key, value] of Object.entries(query)) { 16 | if (!Array.isArray(value)) { 17 | params.append(key, value.toString()); 18 | continue; 19 | } 20 | for (const v of value) params.append(key, v.toString()); 21 | } 22 | } 23 | return params.size 24 | ? `http://localhost/${route}?${params}` 25 | : `http://localhost/${route}`; 26 | }; 27 | -------------------------------------------------------------------------------- /api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2021", 4 | "module": "ES2022", 5 | "moduleResolution": "node", 6 | "types": ["bun-types"], 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "skipLibCheck": true, 11 | "noErrorTruncation": true, 12 | "resolveJsonModule": true, 13 | "baseUrl": ".", 14 | "paths": { 15 | "~/*": ["./src/*"], 16 | "package.json": ["package.json"] 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /auth/.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile* 2 | *.md 3 | .dockerignore 4 | .gitignore 5 | .env* 6 | 7 | # generated via sqlc 8 | dbc/ 9 | # genereated via swag 10 | docs/ 11 | 12 | # vim: ft=gitignore 13 | -------------------------------------------------------------------------------- /auth/.swaggo: -------------------------------------------------------------------------------- 1 | replace jwt.MapClaims map[string]string 2 | replace uuid.UUID string 3 | -------------------------------------------------------------------------------- /auth/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.24 AS build 2 | WORKDIR /app 3 | 4 | RUN go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest 5 | RUN go install github.com/swaggo/swag/cmd/swag@latest 6 | 7 | COPY go.mod go.sum ./ 8 | RUN go mod download 9 | 10 | COPY sqlc.yaml ./ 11 | COPY sql ./sql 12 | RUN sqlc generate 13 | 14 | COPY . . 15 | RUN swag init --parseDependency --outputTypes json,go 16 | RUN CGO_ENABLED=0 GOOS=linux go build -o /keibi 17 | 18 | FROM gcr.io/distroless/base-debian11 19 | WORKDIR /app 20 | EXPOSE 4568 21 | USER nonroot:nonroot 22 | 23 | COPY --from=build /keibi /app/keibi 24 | COPY sql ./sql 25 | 26 | HEALTHCHECK --interval=30s --retries=15 CMD curl --fail http://localhost:4568$KEIBI_PREFIX/health || exit 27 | CMD ["/app/keibi"] 28 | -------------------------------------------------------------------------------- /auth/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM golang:1.24 AS build 2 | WORKDIR /app 3 | 4 | RUN go install github.com/bokwoon95/wgo@latest 5 | 6 | COPY go.mod go.sum ./ 7 | RUN go mod download 8 | 9 | EXPOSE 4568 10 | HEALTHCHECK --interval=30s --retries=15 CMD curl --fail http://localhost:4568$KEIBI_PREFIX/health || exit 11 | CMD ["wgo", "run", "-race", "."] 12 | -------------------------------------------------------------------------------- /auth/dbc/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.28.0 4 | 5 | package dbc 6 | 7 | import ( 8 | "context" 9 | 10 | "github.com/jackc/pgx/v5" 11 | "github.com/jackc/pgx/v5/pgconn" 12 | ) 13 | 14 | type DBTX interface { 15 | Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) 16 | Query(context.Context, string, ...interface{}) (pgx.Rows, error) 17 | QueryRow(context.Context, string, ...interface{}) pgx.Row 18 | } 19 | 20 | func New(db DBTX) *Queries { 21 | return &Queries{db: db} 22 | } 23 | 24 | type Queries struct { 25 | db DBTX 26 | } 27 | 28 | func (q *Queries) WithTx(tx pgx.Tx) *Queries { 29 | return &Queries{ 30 | db: tx, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /auth/kerror.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type KError struct { 4 | Status int `json:"status" example:"404"` 5 | Message string `json:"message" example:"No user found with this id"` 6 | Details any `json:"details"` 7 | } 8 | -------------------------------------------------------------------------------- /auth/page.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/url" 4 | 5 | type Page[T any] struct { 6 | Items []T `json:"items"` 7 | This string `json:"this" example:"https://kyoo.zoriya.dev/auth/users"` 8 | Next *string `json:"next" example:"https://kyoo.zoriya.dev/auth/users?after=aoeusth"` 9 | } 10 | 11 | func NewPage(items []User, url *url.URL, limit int32) Page[User] { 12 | this := url.String() 13 | 14 | var next *string 15 | if len(items) == int(limit) && limit > 0 { 16 | query := url.Query() 17 | query.Set("after", items[len(items)-1].Id.String()) 18 | url.RawQuery = query.Encode() 19 | nextU := url.String() 20 | next = &nextU 21 | } 22 | 23 | return Page[User]{ 24 | Items: items, 25 | This: this, 26 | Next: next, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /auth/sql/migrations/000001_users.down.sql: -------------------------------------------------------------------------------- 1 | begin; 2 | 3 | drop table oidc_handle; 4 | drop table users; 5 | 6 | commit; 7 | -------------------------------------------------------------------------------- /auth/sql/migrations/000001_users.up.sql: -------------------------------------------------------------------------------- 1 | begin; 2 | 3 | create table users( 4 | pk serial primary key, 5 | id uuid not null default gen_random_uuid(), 6 | username varchar(256) not null unique, 7 | email varchar(320) not null unique, 8 | password text, 9 | claims jsonb not null, 10 | 11 | created_date timestamptz not null default now()::timestamptz, 12 | last_seen timestamptz not null default now()::timestamptz 13 | ); 14 | 15 | create table oidc_handle( 16 | user_pk integer not null references users(pk) on delete cascade, 17 | provider varchar(256) not null, 18 | 19 | id text not null, 20 | username varchar(256) not null, 21 | profile_url text, 22 | 23 | access_token text, 24 | refresh_token text, 25 | expire_at timestamptz, 26 | 27 | constraint oidc_handle_pk primary key (user_pk, provider) 28 | ); 29 | 30 | commit; 31 | -------------------------------------------------------------------------------- /auth/sql/migrations/000002_sessions.down.sql: -------------------------------------------------------------------------------- 1 | begin; 2 | 3 | drop table sessions; 4 | 5 | commit; 6 | -------------------------------------------------------------------------------- /auth/sql/migrations/000002_sessions.up.sql: -------------------------------------------------------------------------------- 1 | begin; 2 | 3 | create table sessions( 4 | pk serial primary key, 5 | id uuid not null default gen_random_uuid(), 6 | token varchar(128) not null unique, 7 | user_pk integer not null references users(pk) on delete cascade, 8 | created_date timestamptz not null default now()::timestamptz, 9 | last_used timestamptz not null default now()::timestamptz, 10 | device varchar(1024) 11 | ); 12 | 13 | commit; 14 | -------------------------------------------------------------------------------- /auth/sql/migrations/000003_apikeys.down.sql: -------------------------------------------------------------------------------- 1 | begin; 2 | 3 | drop table apikeys; 4 | 5 | commit; 6 | -------------------------------------------------------------------------------- /auth/sql/migrations/000003_apikeys.up.sql: -------------------------------------------------------------------------------- 1 | begin; 2 | 3 | create table apikeys( 4 | pk serial primary key, 5 | id uuid not null default gen_random_uuid(), 6 | name varchar(256) not null unique, 7 | token varchar(128) not null unique, 8 | claims jsonb not null, 9 | 10 | created_by integer references users(pk) on delete cascade, 11 | created_at timestamptz not null default now()::timestamptz, 12 | last_used timestamptz not null default now()::timestamptz 13 | ); 14 | 15 | commit; 16 | -------------------------------------------------------------------------------- /auth/sql/queries/apikeys.sql: -------------------------------------------------------------------------------- 1 | -- name: GetApiKey :one 2 | select 3 | * 4 | from 5 | apikeys 6 | where 7 | name = $1 8 | and token = $2; 9 | 10 | -- name: TouchApiKey :exec 11 | update 12 | apikeys 13 | set 14 | last_used = now()::timestamptz 15 | where 16 | pk = $1; 17 | 18 | -- name: ListApiKeys :many 19 | select 20 | * 21 | from 22 | apikeys 23 | order by 24 | last_used; 25 | 26 | -- name: CreateApiKey :one 27 | insert into apikeys(name, token, claims, created_by) 28 | values ($1, $2, $3, $4) 29 | returning 30 | *; 31 | 32 | -- name: DeleteApiKey :one 33 | delete from apikeys 34 | where id = $1 35 | returning 36 | *; 37 | 38 | -------------------------------------------------------------------------------- /auth/sql/queries/sessions.sql: -------------------------------------------------------------------------------- 1 | -- name: GetUserFromToken :one 2 | select 3 | s.pk, 4 | s.id, 5 | s.last_used, 6 | sqlc.embed(u) 7 | from 8 | users as u 9 | inner join sessions as s on u.pk = s.user_pk 10 | where 11 | s.token = $1 12 | limit 1; 13 | 14 | -- name: TouchSession :exec 15 | update 16 | sessions 17 | set 18 | last_used = now()::timestamptz 19 | where 20 | pk = $1; 21 | 22 | -- name: GetUserSessions :many 23 | select 24 | s.* 25 | from 26 | sessions as s 27 | inner join users as u on u.pk = s.user_pk 28 | where 29 | u.pk = $1 30 | order by 31 | last_used; 32 | 33 | -- name: CreateSession :one 34 | insert into sessions(token, user_pk, device) 35 | values ($1, $2, $3) 36 | returning 37 | *; 38 | 39 | -- name: DeleteSession :one 40 | delete from sessions as s using users as u 41 | where s.user_pk = u.pk 42 | and s.id = $1 43 | and u.id = sqlc.arg(user_id) 44 | returning 45 | s.*; 46 | 47 | -- name: ClearOtherSessions :exec 48 | delete from sessions as s using users as u 49 | where s.user_pk = u.pk 50 | and s.id != @session_id 51 | and u.id = @user_id; 52 | -------------------------------------------------------------------------------- /auth/sqlc.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | sql: 3 | - engine: "postgresql" 4 | queries: "sql/queries" 5 | schema: "sql/migrations" 6 | gen: 7 | go: 8 | package: "dbc" 9 | sql_package: "pgx/v5" 10 | out: "dbc" 11 | emit_pointers_for_null_types: true 12 | emit_json_tags: true 13 | json_tags_case_style: camel 14 | initialisms: [] 15 | overrides: 16 | - db_type: "timestamptz" 17 | go_type: 18 | import: "time" 19 | type: "Time" 20 | - db_type: "timestamptz" 21 | nullable: true 22 | go_type: 23 | import: "time" 24 | type: "Time" 25 | pointer: true 26 | - db_type: "uuid" 27 | go_type: 28 | import: "github.com/google/uuid" 29 | type: "UUID" 30 | - db_type: "jsonb" 31 | go_type: 32 | type: "interface{}" 33 | - column: "users.claims" 34 | go_type: 35 | import: "github.com/golang-jwt/jwt/v5" 36 | package: "jwt" 37 | type: "MapClaims" 38 | - column: "apikeys.claims" 39 | go_type: 40 | import: "github.com/golang-jwt/jwt/v5" 41 | package: "jwt" 42 | type: "MapClaims" 43 | 44 | 45 | -------------------------------------------------------------------------------- /auth/tests/apikey.hurl: -------------------------------------------------------------------------------- 1 | # perm check 2 | POST {{host}}/keys 3 | { 4 | "name": "dryflower", 5 | "claims": { 6 | "isAdmin": true, 7 | "permissions": ["core.read"] 8 | } 9 | } 10 | HTTP 401 11 | 12 | POST {{host}}/keys 13 | # this is created from the gh workflow file's env var 14 | X-API-KEY: hurl-1234apikey 15 | { 16 | "name": "dryflower", 17 | "claims": { 18 | "isAdmin": true, 19 | "permissions": ["apikeys.read"] 20 | } 21 | } 22 | HTTP 201 23 | [Captures] 24 | id: jsonpath "$.id" 25 | token: jsonpath "$.token" 26 | 27 | GET {{host}}/jwt 28 | X-API-KEY: {{token}} 29 | HTTP 200 30 | [Captures] 31 | jwt: jsonpath "$.token" 32 | 33 | # Duplicates email 34 | POST {{host}}/keys 35 | X-API-KEY: hurl-1234apikey 36 | { 37 | "name": "dryflower", 38 | "claims": { 39 | "isAdmin": true, 40 | "permissions": ["core.read"] 41 | } 42 | } 43 | HTTP 409 44 | 45 | # List 46 | GET {{host}}/keys 47 | Authorization: Bearer {{jwt}} 48 | HTTP 200 49 | [Asserts] 50 | jsonpath "$.items[0].id" == {{id}} 51 | jsonpath "$.items[0].name" == "dryflower" 52 | jsonpath "$.items[0].claims.permissions" contains "apikeys.read" 53 | 54 | 55 | DELETE {{host}}/keys/{{id}} 56 | Authorization: Bearer {{jwt}} 57 | HTTP 403 58 | 59 | DELETE {{host}}/keys/{{id}} 60 | X-API-KEY: hurl-1234apikey 61 | HTTP 200 62 | -------------------------------------------------------------------------------- /auth/tests/basic.hurl: -------------------------------------------------------------------------------- 1 | # Bad Account (login fails if user does not exist) 2 | POST {{host}}/sessions 3 | { 4 | "login": "i-don-t-exist", 5 | "password": "pass" 6 | } 7 | HTTP 404 8 | 9 | # Invalid username 10 | POST {{host}}/sessions 11 | { 12 | "login": "invalid-username-user", 13 | "password": "pass" 14 | } 15 | HTTP 404 16 | 17 | # Me cant be accessed without an account 18 | GET {{host}}/users/me 19 | HTTP 401 20 | -------------------------------------------------------------------------------- /auth/tests/change-password.hurl: -------------------------------------------------------------------------------- 1 | POST {{host}}/users 2 | { 3 | "username": "edit-password", 4 | "password": "password-login-user", 5 | "email": "invalid-password-user@zoriya.dev" 6 | } 7 | HTTP 201 8 | [Captures] 9 | first_token: jsonpath "$.token" 10 | 11 | POST {{host}}/sessions 12 | { 13 | "login": "edit-password", 14 | "password": "password-login-user" 15 | } 16 | HTTP 201 17 | [Captures] 18 | token: jsonpath "$.token" 19 | 20 | GET {{host}}/jwt 21 | Authorization: Bearer {{token}} 22 | HTTP 200 23 | [Captures] 24 | jwt: jsonpath "$.token" 25 | 26 | PATCH {{host}}/users/me/password 27 | Authorization: Bearer {{jwt}} 28 | { 29 | "password": "new-password" 30 | } 31 | HTTP 204 32 | 33 | # Invalid password login 34 | POST {{host}}/jwt 35 | Authorization: Bearer {{first_token}} 36 | HTTP 403 37 | 38 | DELETE {{host}}/users/me 39 | Authorization: Bearer {{jwt}} 40 | HTTP 200 41 | -------------------------------------------------------------------------------- /auth/tests/edit-settings.hurl: -------------------------------------------------------------------------------- 1 | POST {{host}}/users 2 | { 3 | "username": "edit-settings", 4 | "password": "password-login-user", 5 | "email": "edit-settings@zoriya.dev" 6 | } 7 | HTTP 201 8 | [Captures] 9 | token: jsonpath "$.token" 10 | 11 | GET {{host}}/jwt 12 | Authorization: Bearer {{token}} 13 | HTTP 200 14 | [Captures] 15 | jwt: jsonpath "$.token" 16 | 17 | PATCH {{host}}/users/me 18 | Authorization: Bearer {{jwt}} 19 | { 20 | "claims": { 21 | "preferOriginal": true 22 | } 23 | } 24 | HTTP 200 25 | [Asserts] 26 | jsonpath "$.claims.preferOriginal" == true 27 | jsonpath "$.username" == "edit-settings" 28 | 29 | GET {{host}}/jwt 30 | Authorization: Bearer {{token}} 31 | HTTP 200 32 | 33 | DELETE {{host}}/users/me 34 | Authorization: Bearer {{jwt}} 35 | HTTP 200 36 | -------------------------------------------------------------------------------- /autosync/.env.example: -------------------------------------------------------------------------------- 1 | # vi: ft=sh 2 | # shellcheck disable=SC2034 3 | 4 | # RabbitMQ settings 5 | # URL examples: https://docs.aio-pika.com/#url-examples 6 | # This uses AIORMQ (https://github.com/mosquito/aiormq/) under the hood, and supports whatever the library supports. 7 | # RABBITMQ_URL=ampqs://user:password@rabbitmq-server:1234/vhost?capath=/path/to/cacert.pem&certfile=/path/to/cert.pem&keyfile=/path/to/key.pem 8 | # These values are ignored when the RABBITMQ_URL is set 9 | RABBITMQ_HOST=rabbitmq 10 | RABBITMQ_PORT=5672 11 | RABBITMQ_USER=guest 12 | RABBITMQ_PASSWORD=guest 13 | -------------------------------------------------------------------------------- /autosync/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | -------------------------------------------------------------------------------- /autosync/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.13 2 | WORKDIR /app 3 | 4 | COPY ./requirements.txt . 5 | RUN pip3 install -r ./requirements.txt 6 | 7 | COPY . . 8 | ENTRYPOINT ["python3", "-m", "autosync"] 9 | -------------------------------------------------------------------------------- /autosync/autosync/__init__.py: -------------------------------------------------------------------------------- 1 | async def main(): 2 | import logging 3 | from autosync.services.simkl import Simkl 4 | from autosync.services.aggregate import Aggregate 5 | from autosync.consumer import Consumer 6 | 7 | logging.basicConfig(level=logging.INFO) 8 | 9 | service = Aggregate([Simkl()]) 10 | async with Consumer() as consumer: 11 | await consumer.listen(service) 12 | -------------------------------------------------------------------------------- /autosync/autosync/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import asyncio 4 | import autosync 5 | 6 | asyncio.run(autosync.main()) 7 | -------------------------------------------------------------------------------- /autosync/autosync/models/episode.py: -------------------------------------------------------------------------------- 1 | from msgspec import Struct 2 | from autosync.models.show import Show 3 | 4 | from .metadataid import EpisodeID 5 | 6 | 7 | class Episode(Struct, rename="camel", tag_field="kind", tag="episode"): 8 | external_id: dict[str, EpisodeID] 9 | show: Show 10 | season_number: int 11 | episode_number: int 12 | absolute_number: int 13 | -------------------------------------------------------------------------------- /autosync/autosync/models/message.py: -------------------------------------------------------------------------------- 1 | from msgspec import Struct 2 | from autosync.models.episode import Episode 3 | from autosync.models.movie import Movie 4 | from autosync.models.show import Show 5 | from autosync.models.user import User 6 | from autosync.models.watch_status import WatchStatus 7 | 8 | 9 | class WatchStatusMessage(WatchStatus): 10 | user: User 11 | resource: Movie | Show | Episode 12 | 13 | 14 | class Message(Struct, rename="camel"): 15 | action: str 16 | type: str 17 | value: WatchStatusMessage 18 | -------------------------------------------------------------------------------- /autosync/autosync/models/metadataid.py: -------------------------------------------------------------------------------- 1 | from msgspec import Struct 2 | from typing import Optional 3 | 4 | 5 | class MetadataID(Struct, rename="camel"): 6 | data_id: str 7 | link: Optional[str] 8 | 9 | 10 | class EpisodeID(Struct, rename="camel"): 11 | show_id: str 12 | season: Optional[int] 13 | episode: int 14 | link: Optional[str] 15 | -------------------------------------------------------------------------------- /autosync/autosync/models/movie.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from datetime import date 3 | from msgspec import Struct 4 | 5 | from .metadataid import MetadataID 6 | 7 | 8 | class Movie(Struct, rename="camel", tag_field="kind", tag="movie"): 9 | name: str 10 | air_date: Optional[date] 11 | external_id: dict[str, MetadataID] 12 | 13 | @property 14 | def year(self): 15 | return self.air_date.year if self.air_date is not None else None 16 | -------------------------------------------------------------------------------- /autosync/autosync/models/show.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from datetime import date 3 | from msgspec import Struct 4 | 5 | from .metadataid import MetadataID 6 | 7 | 8 | class Show(Struct, rename="camel", tag_field="kind", tag="show"): 9 | name: str 10 | start_air: Optional[date] 11 | external_id: dict[str, MetadataID] 12 | 13 | @property 14 | def year(self): 15 | return self.start_air.year if self.start_air is not None else None 16 | -------------------------------------------------------------------------------- /autosync/autosync/models/user.py: -------------------------------------------------------------------------------- 1 | from msgspec import Struct 2 | from datetime import datetime 3 | from typing import Optional 4 | 5 | 6 | class JwtToken(Struct): 7 | token_type: str 8 | access_token: str 9 | refresh_token: Optional[str] 10 | expire_at: datetime 11 | 12 | 13 | class ExternalToken(Struct, rename="camel"): 14 | id: str 15 | username: str 16 | profile_url: Optional[str] 17 | token: JwtToken 18 | 19 | 20 | class User(Struct, rename="camel", tag_field="kind", tag="user"): 21 | id: str 22 | username: str 23 | email: str 24 | permissions: list[str] 25 | settings: dict[str, str] 26 | external_id: dict[str, ExternalToken] 27 | -------------------------------------------------------------------------------- /autosync/autosync/models/watch_status.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Optional 3 | from enum import Enum 4 | 5 | from msgspec import Struct 6 | 7 | 8 | class Status(str, Enum): 9 | COMPLETED = "Completed" 10 | WATCHING = "Watching" 11 | DROPED = "Droped" 12 | PLANNED = "Planned" 13 | DELETED = "Deleted" 14 | 15 | 16 | class WatchStatus(Struct, rename="camel"): 17 | added_date: datetime 18 | played_date: Optional[datetime] 19 | status: Status 20 | watched_time: Optional[int] 21 | watched_percent: Optional[int] 22 | -------------------------------------------------------------------------------- /autosync/autosync/services/aggregate.py: -------------------------------------------------------------------------------- 1 | from logging import getLogger 2 | from autosync.services.service import Service 3 | from ..models.user import User 4 | from ..models.show import Show 5 | from ..models.movie import Movie 6 | from ..models.episode import Episode 7 | from ..models.watch_status import WatchStatus 8 | 9 | logger = getLogger(__name__) 10 | 11 | 12 | class Aggregate(Service): 13 | def __init__(self, services: list[Service]): 14 | self._services = [x for x in services if x.enabled] 15 | logger.info("Autosync enabled with %s", [x.name for x in self._services]) 16 | 17 | @property 18 | def name(self) -> str: 19 | return "aggragate" 20 | 21 | def update(self, user: User, resource: Movie | Show | Episode, status: WatchStatus): 22 | for service in self._services: 23 | try: 24 | service.update(user, resource, status) 25 | except Exception as e: 26 | logger.exception( 27 | "Unhandled error on autosync %s:", service.name, exc_info=e 28 | ) 29 | -------------------------------------------------------------------------------- /autosync/autosync/services/service.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod, abstractproperty 2 | 3 | from ..models.user import User 4 | from ..models.show import Show 5 | from ..models.movie import Movie 6 | from ..models.episode import Episode 7 | from ..models.watch_status import WatchStatus 8 | 9 | 10 | class Service: 11 | @abstractproperty 12 | def name(self) -> str: 13 | raise NotImplementedError 14 | 15 | @abstractproperty 16 | def enabled(self) -> bool: 17 | return True 18 | 19 | @abstractmethod 20 | def update(self, user: User, resource: Movie | Show | Episode, status: WatchStatus): 21 | raise NotImplementedError 22 | -------------------------------------------------------------------------------- /autosync/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.ruff.format] 2 | indent-style = "tab" 3 | -------------------------------------------------------------------------------- /autosync/requirements.txt: -------------------------------------------------------------------------------- 1 | aio-pika 2 | msgspec 3 | requests 4 | -------------------------------------------------------------------------------- /back/.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-ef": { 6 | "version": "8.0.16", 7 | "commands": [ 8 | "dotnet-ef" 9 | ] 10 | }, 11 | "csharpier": { 12 | "version": "0.28.2", 13 | "commands": [ 14 | "dotnet-csharpier" 15 | ] 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /back/.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | Dockerfile.dev 3 | Dockerfile.* 4 | .dockerignore 5 | .gitignore 6 | docker-compose.yml 7 | README.md 8 | **/build 9 | **/dist 10 | **/bin 11 | **/obj 12 | out 13 | docs 14 | tests 15 | front 16 | video 17 | nginx.conf.template 18 | -------------------------------------------------------------------------------- /back/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:8.0 2 | RUN apt-get update && apt-get install -y curl 3 | WORKDIR /app 4 | 5 | COPY Kyoo.sln ./Kyoo.sln 6 | COPY nuget.config ./nuget.config 7 | COPY src/Directory.Build.props src/Directory.Build.props 8 | COPY src/Kyoo.Authentication/Kyoo.Authentication.csproj src/Kyoo.Authentication/Kyoo.Authentication.csproj 9 | COPY src/Kyoo.Abstractions/Kyoo.Abstractions.csproj src/Kyoo.Abstractions/Kyoo.Abstractions.csproj 10 | COPY src/Kyoo.Core/Kyoo.Core.csproj src/Kyoo.Core/Kyoo.Core.csproj 11 | COPY src/Kyoo.Postgresql/Kyoo.Postgresql.csproj src/Kyoo.Postgresql/Kyoo.Postgresql.csproj 12 | COPY src/Kyoo.Meilisearch/Kyoo.Meilisearch.csproj src/Kyoo.Meilisearch/Kyoo.Meilisearch.csproj 13 | COPY src/Kyoo.RabbitMq/Kyoo.RabbitMq.csproj src/Kyoo.RabbitMq/Kyoo.RabbitMq.csproj 14 | COPY src/Kyoo.Swagger/Kyoo.Swagger.csproj src/Kyoo.Swagger/Kyoo.Swagger.csproj 15 | RUN dotnet restore 16 | 17 | WORKDIR /app 18 | EXPOSE 5000 19 | ENV DOTNET_USE_POLLING_FILE_WATCHER 1 20 | # HEALTHCHECK --interval=30s CMD curl --fail http://localhost:5000/health || exit 21 | HEALTHCHECK CMD true 22 | ENTRYPOINT ["dotnet", "watch", "--non-interactive", "run", "--no-restore", "--project", "/app/src/Kyoo.Core"] 23 | -------------------------------------------------------------------------------- /back/ef.rsp: -------------------------------------------------------------------------------- 1 | --project 2 | src/Kyoo.Postgresql 3 | --msbuildprojectextensionspath 4 | out/obj/Kyoo.Postgresql 5 | -------------------------------------------------------------------------------- /back/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoriya/Kyoo/37e0fc3f0e9b7acd090563021aab557cc68415ce/back/icon.ico -------------------------------------------------------------------------------- /back/nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /back/src/Kyoo.Abstractions/Controllers/IScanner.cs: -------------------------------------------------------------------------------- 1 | // Kyoo - A portable and vast media library solution. 2 | // Copyright (c) Kyoo. 3 | // 4 | // See AUTHORS.md and LICENSE file in the project root for full license information. 5 | // 6 | // Kyoo is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // any later version. 10 | // 11 | // Kyoo is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with Kyoo. If not, see . 18 | 19 | using System; 20 | using System.Threading.Tasks; 21 | 22 | namespace Kyoo.Abstractions.Controllers; 23 | 24 | public interface IScanner 25 | { 26 | Task SendRescanRequest(); 27 | Task SendRefreshRequest(string kind, Guid id); 28 | } 29 | -------------------------------------------------------------------------------- /back/src/Kyoo.Abstractions/Kyoo.Abstractions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Kyoo.Abstractions 4 | Base package to create plugins for Kyoo. 5 | Kyoo.Abstractions 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /back/src/Kyoo.Abstractions/Models/Attributes/ComputedAttribute.cs: -------------------------------------------------------------------------------- 1 | // Kyoo - A portable and vast media library solution. 2 | // Copyright (c) Kyoo. 3 | // 4 | // See AUTHORS.md and LICENSE file in the project root for full license information. 5 | // 6 | // Kyoo is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // any later version. 10 | // 11 | // Kyoo is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with Kyoo. If not, see . 18 | 19 | using System; 20 | 21 | namespace Kyoo.Abstractions.Models.Attributes; 22 | 23 | /// 24 | /// An attribute to inform that the property is computed automatically and can't be assigned manually. 25 | /// 26 | [AttributeUsage(AttributeTargets.Property)] 27 | public class ComputedAttribute : NotMergeableAttribute { } 28 | -------------------------------------------------------------------------------- /back/src/Kyoo.Abstractions/Models/Attributes/OneOfAttribute.cs: -------------------------------------------------------------------------------- 1 | // Kyoo - A portable and vast media library solution. 2 | // Copyright (c) Kyoo. 3 | // 4 | // See AUTHORS.md and LICENSE file in the project root for full license information. 5 | // 6 | // Kyoo is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // any later version. 10 | // 11 | // Kyoo is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with Kyoo. If not, see . 18 | 19 | using System; 20 | 21 | namespace Kyoo.Abstractions.Models.Attributes; 22 | 23 | /// 24 | /// An attribute to inform that this interface is a type union 25 | /// 26 | [AttributeUsage(AttributeTargets.Interface)] 27 | public class OneOfAttribute : Attribute 28 | { 29 | /// 30 | /// The types this union concist of. 31 | /// 32 | public Type[] Types { get; set; } 33 | } 34 | -------------------------------------------------------------------------------- /back/src/Kyoo.Abstractions/Models/Exceptions/DuplicatedItemException.cs: -------------------------------------------------------------------------------- 1 | // Kyoo - A portable and vast media library solution. 2 | // Copyright (c) Kyoo. 3 | // 4 | // See AUTHORS.md and LICENSE file in the project root for full license information. 5 | // 6 | // Kyoo is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // any later version. 10 | // 11 | // Kyoo is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with Kyoo. If not, see . 18 | 19 | using System; 20 | 21 | namespace Kyoo.Abstractions.Models.Exceptions; 22 | 23 | /// 24 | /// An exception raised when an item already exists in the database. 25 | /// 26 | [Serializable] 27 | public class DuplicatedItemException(object? existing = null) 28 | : Exception("Already exists in the database.") 29 | { 30 | /// 31 | /// The existing object. 32 | /// 33 | public object? Existing { get; } = existing; 34 | } 35 | -------------------------------------------------------------------------------- /back/src/Kyoo.Abstractions/Models/Exceptions/UnauthorizedException.cs: -------------------------------------------------------------------------------- 1 | // Kyoo - A portable and vast media library solution. 2 | // Copyright (c) Kyoo. 3 | // 4 | // See AUTHORS.md and LICENSE file in the project root for full license information. 5 | // 6 | // Kyoo is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // any later version. 10 | // 11 | // Kyoo is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with Kyoo. If not, see . 18 | 19 | using System; 20 | 21 | namespace Kyoo.Abstractions.Models.Exceptions; 22 | 23 | [Serializable] 24 | public class UnauthorizedException : Exception 25 | { 26 | public UnauthorizedException() 27 | : base("User not authenticated or token invalid.") { } 28 | 29 | public UnauthorizedException(string message) 30 | : base(message) { } 31 | } 32 | -------------------------------------------------------------------------------- /back/src/Kyoo.Abstractions/Models/INews.cs: -------------------------------------------------------------------------------- 1 | // Kyoo - A portable and vast media library solution. 2 | // Copyright (c) Kyoo. 3 | // 4 | // See AUTHORS.md and LICENSE file in the project root for full license information. 5 | // 6 | // Kyoo is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // any later version. 10 | // 11 | // Kyoo is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with Kyoo. If not, see . 18 | 19 | using Kyoo.Abstractions.Controllers; 20 | using Kyoo.Abstractions.Models.Attributes; 21 | 22 | namespace Kyoo.Abstractions.Models; 23 | 24 | /// 25 | /// A show, a movie or a collection. 26 | /// 27 | [OneOf(Types = [typeof(Episode), typeof(Movie)])] 28 | public interface INews : IResource, IThumbnails, IAddedDate, IQuery 29 | { 30 | static Sort IQuery.DefaultSort => new Sort.By(nameof(AddedDate), true); 31 | } 32 | -------------------------------------------------------------------------------- /back/src/Kyoo.Abstractions/Models/IWatchlist.cs: -------------------------------------------------------------------------------- 1 | // Kyoo - A portable and vast media library solution. 2 | // Copyright (c) Kyoo. 3 | // 4 | // See AUTHORS.md and LICENSE file in the project root for full license information. 5 | // 6 | // Kyoo is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // any later version. 10 | // 11 | // Kyoo is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with Kyoo. If not, see . 18 | 19 | using Kyoo.Abstractions.Models.Attributes; 20 | 21 | namespace Kyoo.Abstractions.Models; 22 | 23 | /// 24 | /// A watch list item. 25 | /// 26 | [OneOf(Types = new[] { typeof(Show), typeof(Movie) })] 27 | public interface IWatchlist : IResource, IThumbnails, IMetadata, IAddedDate { } 28 | -------------------------------------------------------------------------------- /back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IAddedDate.cs: -------------------------------------------------------------------------------- 1 | // Kyoo - A portable and vast media library solution. 2 | // Copyright (c) Kyoo. 3 | // 4 | // See AUTHORS.md and LICENSE file in the project root for full license information. 5 | // 6 | // Kyoo is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // any later version. 10 | // 11 | // Kyoo is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with Kyoo. If not, see . 18 | 19 | using System; 20 | 21 | namespace Kyoo.Abstractions.Models; 22 | 23 | /// 24 | /// An interface applied to resources. 25 | /// 26 | public interface IAddedDate 27 | { 28 | /// 29 | /// The date at which this resource was added to kyoo. 30 | /// 31 | public DateTime AddedDate { get; set; } 32 | } 33 | -------------------------------------------------------------------------------- /back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IMetadata.cs: -------------------------------------------------------------------------------- 1 | // Kyoo - A portable and vast media library solution. 2 | // Copyright (c) Kyoo. 3 | // 4 | // See AUTHORS.md and LICENSE file in the project root for full license information. 5 | // 6 | // Kyoo is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // any later version. 10 | // 11 | // Kyoo is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with Kyoo. If not, see . 18 | 19 | using System.Collections.Generic; 20 | 21 | namespace Kyoo.Abstractions.Models; 22 | 23 | /// 24 | /// An interface applied to resources containing external metadata. 25 | /// 26 | public interface IMetadata 27 | { 28 | /// 29 | /// The link to metadata providers that this show has. See for more information. 30 | /// 31 | public Dictionary ExternalId { get; set; } 32 | } 33 | -------------------------------------------------------------------------------- /back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IQuery.cs: -------------------------------------------------------------------------------- 1 | // Kyoo - A portable and vast media library solution. 2 | // Copyright (c) Kyoo. 3 | // 4 | // See AUTHORS.md and LICENSE file in the project root for full license information. 5 | // 6 | // Kyoo is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // any later version. 10 | // 11 | // Kyoo is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with Kyoo. If not, see . 18 | 19 | using System; 20 | using Kyoo.Abstractions.Controllers; 21 | 22 | namespace Kyoo.Abstractions.Models; 23 | 24 | public interface IQuery 25 | { 26 | /// 27 | /// The sorting that will be used when no user defined one is present. 28 | /// 29 | public static virtual Sort DefaultSort => throw new NotImplementedException(); 30 | } 31 | -------------------------------------------------------------------------------- /back/src/Kyoo.Authentication/Attributes/DisableOnEnvVarAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Mvc.Filters; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace Kyoo.Authentication.Attributes; 7 | 8 | /// 9 | /// Disables the action if the specified environment variable is set to true. 10 | /// 11 | public class DisableOnEnvVarAttribute(string varName) : Attribute, IResourceFilter 12 | { 13 | public void OnResourceExecuting(ResourceExecutingContext context) 14 | { 15 | var config = context.HttpContext.RequestServices.GetRequiredService(); 16 | 17 | if (config.GetValue(varName, false)) 18 | context.Result = new Microsoft.AspNetCore.Mvc.NotFoundResult(); 19 | } 20 | 21 | public void OnResourceExecuted(ResourceExecutedContext context) { } 22 | } 23 | -------------------------------------------------------------------------------- /back/src/Kyoo.Authentication/Kyoo.Authentication.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /back/src/Kyoo.Authentication/Models/Options/AuthenticationOption.cs: -------------------------------------------------------------------------------- 1 | // Kyoo - A portable and vast media library solution. 2 | // Copyright (c) Kyoo. 3 | // 4 | // See AUTHORS.md and LICENSE file in the project root for full license information. 5 | // 6 | // Kyoo is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // any later version. 10 | // 11 | // Kyoo is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with Kyoo. If not, see . 18 | 19 | namespace Kyoo.Authentication.Models; 20 | 21 | public class AuthenticationOption 22 | { 23 | public byte[] Secret { get; set; } 24 | } 25 | -------------------------------------------------------------------------------- /back/src/Kyoo.Core/Storage/IStorage.cs: -------------------------------------------------------------------------------- 1 | // Kyoo - A portable and vast media library solution. 2 | // Copyright (c) Kyoo. 3 | // 4 | // See AUTHORS.md and LICENSE file in the project root for full license information. 5 | // 6 | // Kyoo is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // any later version. 10 | // 11 | // Kyoo is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with Kyoo. If not, see . 18 | 19 | using System.IO; 20 | using System.Threading.Tasks; 21 | 22 | namespace Kyoo.Core.Storage; 23 | 24 | /// 25 | /// Interface for storage operations. 26 | /// 27 | public interface IStorage 28 | { 29 | Task DoesExist(string path); 30 | Task Read(string path); 31 | Task Write(Stream stream, string path); 32 | Task Delete(string path); 33 | } 34 | -------------------------------------------------------------------------------- /back/src/Kyoo.Meilisearch/Kyoo.Meilisearch.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | enable 4 | Kyoo.Meilisearch 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /back/src/Kyoo.Postgresql/DbConfigurationProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Kyoo.Postgresql; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.Extensions.Configuration; 6 | 7 | public class DbConfigurationProvider(Action action) : ConfigurationProvider 8 | { 9 | public override void Load() 10 | { 11 | DbContextOptionsBuilder builder = new(); 12 | action(builder); 13 | using var context = new PostgresContext(builder.Options, null!); 14 | Data = context.Options.ToDictionary(c => c.Key, c => c.Value)!; 15 | } 16 | } 17 | 18 | public class DbConfigurationSource(Action action) : IConfigurationSource 19 | { 20 | public IConfigurationProvider Build(IConfigurationBuilder builder) => 21 | new DbConfigurationProvider(action); 22 | } 23 | 24 | public class ServerOption 25 | { 26 | public string Key { get; set; } 27 | public string Value { get; set; } 28 | } 29 | -------------------------------------------------------------------------------- /back/src/Kyoo.Postgresql/Kyoo.Postgresql.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Kyoo.Postgresql 4 | Kyoo.Postgresql 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /back/src/Kyoo.Postgresql/Migrations/20231220093441_Settings.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Kyoo.Postgresql.Migrations; 6 | 7 | /// 8 | public partial class Settings : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.AddColumn( 14 | name: "settings", 15 | table: "users", 16 | type: "json", 17 | nullable: false, 18 | defaultValue: "{}" 19 | ); 20 | } 21 | 22 | /// 23 | protected override void Down(MigrationBuilder migrationBuilder) 24 | { 25 | migrationBuilder.DropColumn(name: "settings", table: "users"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /back/src/Kyoo.Postgresql/Migrations/20240204193443_RemoveUserLogo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | namespace Kyoo.Postgresql.Migrations; 4 | 5 | /// 6 | public partial class RemoveUserLogo : Migration 7 | { 8 | /// 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.DropColumn(name: "logo_blurhash", table: "users"); 12 | 13 | migrationBuilder.DropColumn(name: "logo_source", table: "users"); 14 | } 15 | 16 | /// 17 | protected override void Down(MigrationBuilder migrationBuilder) 18 | { 19 | migrationBuilder.AddColumn( 20 | name: "logo_blurhash", 21 | table: "users", 22 | type: "character varying(32)", 23 | maxLength: 32, 24 | nullable: true 25 | ); 26 | 27 | migrationBuilder.AddColumn( 28 | name: "logo_source", 29 | table: "users", 30 | type: "text", 31 | nullable: true 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /back/src/Kyoo.Postgresql/Migrations/20240219170615_AddPlayPermission.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Kyoo.Postgresql.Migrations; 6 | 7 | /// 8 | public partial class AddPlayPermission : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | // language=PostgreSQL 14 | migrationBuilder.Sql( 15 | "update users set permissions = ARRAY_APPEND(permissions, 'overall.play');" 16 | ); 17 | } 18 | 19 | /// 20 | protected override void Down(MigrationBuilder migrationBuilder) { } 21 | } 22 | -------------------------------------------------------------------------------- /back/src/Kyoo.Postgresql/Migrations/20240229202049_AddUserExternalId.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Kyoo.Postgresql.Migrations; 6 | 7 | /// 8 | public partial class AddUserExternalId : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.AddColumn( 14 | name: "external_id", 15 | table: "users", 16 | type: "json", 17 | nullable: false, 18 | defaultValue: "{}" 19 | ); 20 | } 21 | 22 | /// 23 | protected override void Down(MigrationBuilder migrationBuilder) 24 | { 25 | migrationBuilder.DropColumn(name: "external_id", table: "users"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /back/src/Kyoo.Postgresql/Migrations/20240302151906_MakePasswordOptional.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Kyoo.Postgresql.Migrations; 6 | 7 | /// 8 | public partial class MakePasswordOptional : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.AlterColumn( 14 | name: "password", 15 | table: "users", 16 | type: "text", 17 | nullable: true, 18 | oldClrType: typeof(string), 19 | oldType: "text" 20 | ); 21 | } 22 | 23 | /// 24 | protected override void Down(MigrationBuilder migrationBuilder) 25 | { 26 | migrationBuilder.AlterColumn( 27 | name: "password", 28 | table: "users", 29 | type: "text", 30 | nullable: false, 31 | defaultValue: "", 32 | oldClrType: typeof(string), 33 | oldType: "text", 34 | oldNullable: true 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /back/src/Kyoo.Postgresql/Migrations/20240423151632_AddServerOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | using Microsoft.EntityFrameworkCore.Migrations; 4 | 5 | #nullable disable 6 | 7 | namespace Kyoo.Postgresql.Migrations 8 | { 9 | /// 10 | public partial class AddServerOptions : Migration 11 | { 12 | /// 13 | protected override void Up(MigrationBuilder migrationBuilder) 14 | { 15 | migrationBuilder.CreateTable( 16 | name: "options", 17 | columns: table => new 18 | { 19 | key = table.Column(type: "text", nullable: false), 20 | value = table.Column(type: "text", nullable: false) 21 | }, 22 | constraints: table => 23 | { 24 | table.PrimaryKey("pk_options", x => x.key); 25 | } 26 | ); 27 | byte[] secret = new byte[128]; 28 | using var rng = RandomNumberGenerator.Create(); 29 | rng.GetBytes(secret); 30 | migrationBuilder.InsertData( 31 | "options", 32 | new[] { "key", "value" }, 33 | new[] { "AUTHENTICATION_SECRET", Convert.ToBase64String(secret) } 34 | ); 35 | } 36 | 37 | /// 38 | protected override void Down(MigrationBuilder migrationBuilder) 39 | { 40 | migrationBuilder.DropTable(name: "options"); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /back/src/Kyoo.Postgresql/Migrations/20240506175054_FixSeasonMetadataId.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Kyoo.Postgresql.Migrations 6 | { 7 | /// 8 | public partial class FixSeasonMetadataId : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | // language=PostgreSQL 14 | migrationBuilder.Sql( 15 | """ 16 | update seasons as s set external_id = ( 17 | SELECT jsonb_build_object( 18 | 'themoviedatabase', jsonb_build_object( 19 | 'DataId', sh.external_id->'themoviedatabase'->'DataId', 20 | 'Link', s.external_id->'themoviedatabase'->'Link' 21 | ) 22 | ) 23 | FROM shows AS sh 24 | WHERE sh.id = s.show_id 25 | ); 26 | """ 27 | ); 28 | } 29 | 30 | /// 31 | protected override void Down(MigrationBuilder migrationBuilder) { } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /back/src/Kyoo.RabbitMq/Kyoo.RabbitMq.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | enable 4 | Kyoo.RabbitMq 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /back/src/Kyoo.Swagger/Kyoo.Swagger.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Kyoo.Swagger 4 | Kyoo.Swagger 5 | disable 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /back/stylecop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", 3 | "settings": { 4 | "documentationRules": { 5 | "copyrightText": "Kyoo - A portable and vast media library solution.\nCopyright (c) Kyoo.\n\nSee AUTHORS.md and LICENSE file in the project root for full license information.\n\nKyoo is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\nany later version.\n\nKyoo is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with Kyoo. If not, see .", 6 | "xmlHeader": false 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /back/tests/.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | -------------------------------------------------------------------------------- /back/tests/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.robotidy] 2 | configure = [ 3 | "MergeAndOrderSections:order=comments,settings,keywords,variables,testcases" 4 | ] 5 | -------------------------------------------------------------------------------- /back/tests/rest.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Common things to handle rest requests 3 | 4 | Library REST http://localhost:8901/api 5 | -------------------------------------------------------------------------------- /chart/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /chart/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: kyoo 3 | description: Kyoo is an open source media server alternative to Jellyfin and Plex. 4 | 5 | type: application 6 | version: 0.0.0 7 | appVersion: "0.0.0" 8 | 9 | icon: https://raw.githubusercontent.com/zoriya/Kyoo/refs/heads/master/icons/icon.svg 10 | 11 | dependencies: 12 | - condition: meilisearch.enabled 13 | name: meilisearch 14 | repository: https://meilisearch.github.io/meilisearch-kubernetes 15 | version: 0.13.0 16 | - condition: postgresql.enabled 17 | name: postgresql 18 | repository: https://charts.bitnami.com/bitnami 19 | version: 16.7.5 20 | - condition: rabbitmq.enabled 21 | name: rabbitmq 22 | repository: https://charts.bitnami.com/bitnami 23 | version: 16.0.3 24 | -------------------------------------------------------------------------------- /chart/templates/autosync/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.autosync.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | automountServiceAccountToken: {{ .Values.autosync.serviceAccount.automount }} 5 | metadata: 6 | name: {{ include "kyoo.autosync.serviceAccountName" . }} 7 | labels: 8 | {{- include "kyoo.labels" (dict "context" . "component" .Values.autosync.name "name" .Values.autosync.name) | nindent 4 }} 9 | {{- with .Values.autosync.serviceAccount.annotations }} 10 | annotations: 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | {{- end }} 14 | -------------------------------------------------------------------------------- /chart/templates/back/pvc.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.back.persistence.enabled (not .Values.back.persistence.existingClaim) }} 2 | apiVersion: v1 3 | kind: PersistentVolumeClaim 4 | metadata: 5 | name: {{ include "kyoo.backmetadata.fullname" . }} 6 | labels: 7 | {{- include "kyoo.labels" (dict "context" . "component" .Values.back.name "name" .Values.back.name) | nindent 4 }} 8 | {{- with (mergeOverwrite (deepCopy .Values.global.persistentVolumeClaimAnnotations) .Values.back.persistence.annotations) }} 9 | annotations: 10 | {{- range $key, $value := . }} 11 | {{ $key }}: {{ $value | quote }} 12 | {{- end }} 13 | {{- end }} 14 | spec: 15 | accessModes: 16 | {{- range .Values.back.persistence.accessModes }} 17 | - {{ . }} 18 | {{- end }} 19 | resources: 20 | requests: 21 | storage: {{ .Values.back.persistence.size }} 22 | {{- if .Values.back.persistence.storageClass }} 23 | storageClassName: {{ .Values.back.persistence.storageClass }} 24 | {{- end }} 25 | {{- end }} 26 | -------------------------------------------------------------------------------- /chart/templates/back/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | {{- if .Values.back.service.annotations }} 5 | annotations: 6 | {{- range $key, $value := .Values.back.service.annotations }} 7 | {{ $key }}: {{ $value | quote }} 8 | {{- end }} 9 | {{- end }} 10 | name: {{ include "kyoo.back.fullname" . }} 11 | labels: 12 | {{- include "kyoo.labels" (dict "context" . "component" .Values.back.name "name" .Values.back.name) | nindent 4 }} 13 | {{- with .Values.back.service.labels }} 14 | {{- toYaml . | nindent 4 }} 15 | {{- end }} 16 | spec: 17 | type: {{ .Values.back.service.type }} 18 | ports: 19 | - port: 5000 20 | targetPort: 5000 21 | protocol: TCP 22 | name: main 23 | selector: 24 | {{- include "kyoo.selectorLabels" (dict "context" . "name" .Values.back.name) | nindent 4 }} 25 | -------------------------------------------------------------------------------- /chart/templates/back/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.back.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | automountServiceAccountToken: {{ .Values.back.serviceAccount.automount }} 5 | metadata: 6 | name: {{ include "kyoo.back.serviceAccountName" . }} 7 | labels: 8 | {{- include "kyoo.labels" (dict "context" . "component" .Values.back.name "name" .Values.back.name) | nindent 4 }} 9 | {{- with .Values.back.serviceAccount.annotations }} 10 | annotations: 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | {{- end }} 14 | -------------------------------------------------------------------------------- /chart/templates/extra-manifests.yaml: -------------------------------------------------------------------------------- 1 | {{ range .Values.extraObjects }} 2 | --- 3 | {{ if typeIs "string" . }} 4 | {{- tpl . $ }} 5 | {{- else }} 6 | {{- tpl (toYaml .) $ }} 7 | {{- end }} 8 | {{ end }} 9 | -------------------------------------------------------------------------------- /chart/templates/front/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | {{- if .Values.front.service.annotations }} 5 | annotations: 6 | {{- range $key, $value := .Values.front.service.annotations }} 7 | {{ $key }}: {{ $value | quote }} 8 | {{- end }} 9 | {{- end }} 10 | name: {{ include "kyoo.front.fullname" . }} 11 | labels: 12 | {{- include "kyoo.labels" (dict "context" . "component" .Values.front.name "name" .Values.front.name) | nindent 4 }} 13 | {{- with .Values.front.service.labels }} 14 | {{- toYaml . | nindent 4 }} 15 | {{- end }} 16 | spec: 17 | type: {{ .Values.front.service.type }} 18 | ports: 19 | - port: 8901 20 | targetPort: 8901 21 | protocol: TCP 22 | name: main 23 | selector: 24 | {{- include "kyoo.selectorLabels" (dict "context" . "name" .Values.front.name) | nindent 4 }} 25 | -------------------------------------------------------------------------------- /chart/templates/front/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.front.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | automountServiceAccountToken: {{ .Values.front.serviceAccount.automount }} 5 | metadata: 6 | name: {{ include "kyoo.front.serviceAccountName" . }} 7 | labels: 8 | {{- include "kyoo.labels" (dict "context" . "component" .Values.front.name "name" .Values.front.name) | nindent 4 }} 9 | {{- with .Values.front.serviceAccount.annotations }} 10 | annotations: 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | {{- end }} 14 | -------------------------------------------------------------------------------- /chart/templates/matcher/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.matcher.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | automountServiceAccountToken: {{ .Values.matcher.serviceAccount.automount }} 5 | metadata: 6 | name: {{ include "kyoo.matcher.serviceAccountName" . }} 7 | labels: 8 | {{- include "kyoo.labels" (dict "context" . "component" .Values.matcher.name "name" .Values.matcher.name) | nindent 4 }} 9 | {{- with .Values.matcher.serviceAccount.annotations }} 10 | annotations: 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | {{- end }} 14 | -------------------------------------------------------------------------------- /chart/templates/scanner/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.scanner.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | automountServiceAccountToken: {{ .Values.scanner.serviceAccount.automount }} 5 | metadata: 6 | name: {{ include "kyoo.scanner.serviceAccountName" . }} 7 | labels: 8 | {{- include "kyoo.labels" (dict "context" . "component" .Values.scanner.name "name" .Values.scanner.name) | nindent 4 }} 9 | {{- with .Values.scanner.serviceAccount.annotations }} 10 | annotations: 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | {{- end }} 14 | -------------------------------------------------------------------------------- /chart/templates/transcoder/pvc.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.transcoder.persistence.enabled (not .Values.transcoder.persistence.existingClaim) }} 2 | apiVersion: v1 3 | kind: PersistentVolumeClaim 4 | metadata: 5 | name: {{ include "kyoo.transcodermetadata.fullname" . }} 6 | labels: 7 | {{- include "kyoo.labels" (dict "context" . "component" .Values.transcoder.name "name" .Values.transcoder.name) | nindent 4 }} 8 | {{- with (mergeOverwrite (deepCopy .Values.global.persistentVolumeClaimAnnotations) .Values.transcoder.persistence.annotations) }} 9 | annotations: 10 | {{- range $key, $value := . }} 11 | {{ $key }}: {{ $value | quote }} 12 | {{- end }} 13 | {{- end }} 14 | spec: 15 | accessModes: 16 | {{- range .Values.transcoder.persistence.accessModes }} 17 | - {{ . }} 18 | {{- end }} 19 | resources: 20 | requests: 21 | storage: {{ .Values.transcoder.persistence.size }} 22 | {{- if .Values.transcoder.persistence.storageClass }} 23 | storageClassName: {{ .Values.transcoder.persistence.storageClass }} 24 | {{- end }} 25 | {{- end }} 26 | -------------------------------------------------------------------------------- /chart/templates/transcoder/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | {{- if .Values.transcoder.service.annotations }} 5 | annotations: 6 | {{- range $key, $value := .Values.transcoder.service.annotations }} 7 | {{ $key }}: {{ $value | quote }} 8 | {{- end }} 9 | {{- end }} 10 | name: {{ include "kyoo.transcoder.fullname" . }} 11 | labels: 12 | {{- include "kyoo.labels" (dict "context" . "component" .Values.transcoder.name "name" .Values.transcoder.name) | nindent 4 }} 13 | {{- with .Values.transcoder.service.labels }} 14 | {{- toYaml . | nindent 4 }} 15 | {{- end }} 16 | spec: 17 | type: {{ .Values.transcoder.service.type }} 18 | ports: 19 | - port: 7666 20 | targetPort: 7666 21 | protocol: TCP 22 | name: main 23 | selector: 24 | {{- include "kyoo.selectorLabels" (dict "context" . "name" .Values.transcoder.name) | nindent 4 }} 25 | -------------------------------------------------------------------------------- /chart/templates/transcoder/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.transcoder.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | automountServiceAccountToken: {{ .Values.transcoder.serviceAccount.automount }} 5 | metadata: 6 | name: {{ include "kyoo.transcoder.serviceAccountName" . }} 7 | labels: 8 | {{- include "kyoo.labels" (dict "context" . "component" .Values.transcoder.name "name" .Values.transcoder.name) | nindent 4 }} 9 | {{- with .Values.transcoder.serviceAccount.annotations }} 10 | annotations: 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | {{- end }} 14 | -------------------------------------------------------------------------------- /front/.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | Dockerfile.dev 3 | .dockerignore 4 | .eslintrc.json 5 | .gitignore 6 | node_modules 7 | npm-debug.log 8 | README.md 9 | .next 10 | .expo 11 | .git 12 | .yarn 13 | !.yarn/releases 14 | !.yarn/plugins 15 | -------------------------------------------------------------------------------- /front/.gitattributes: -------------------------------------------------------------------------------- 1 | /.yarn/releases/** binary 2 | /.yarn/plugins/** binary 3 | -------------------------------------------------------------------------------- /front/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # testing 9 | coverage 10 | 11 | # next.js 12 | .next/ 13 | out/ 14 | 15 | # production 16 | build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | 37 | .pnp.* 38 | .yarn/* 39 | !.yarn/patches 40 | !.yarn/plugins 41 | !.yarn/releases 42 | !.yarn/sdks 43 | 44 | .expo 45 | 46 | 47 | apps/web/next-env.d.ts 48 | -------------------------------------------------------------------------------- /front/.yarn/sdks/eslint/bin/eslint.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require eslint/bin/eslint.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real eslint/bin/eslint.js your application uses 20 | module.exports = absRequire(`eslint/bin/eslint.js`); 21 | -------------------------------------------------------------------------------- /front/.yarn/sdks/eslint/lib/api.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require eslint 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real eslint your application uses 20 | module.exports = absRequire(`eslint`); 21 | -------------------------------------------------------------------------------- /front/.yarn/sdks/eslint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint", 3 | "version": "8.19.0-sdk", 4 | "main": "./lib/api.js", 5 | "type": "commonjs" 6 | } 7 | -------------------------------------------------------------------------------- /front/.yarn/sdks/integrations.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by @yarnpkg/sdks. 2 | # Manual changes might be lost! 3 | 4 | -------------------------------------------------------------------------------- /front/.yarn/sdks/prettier/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require prettier/index.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real prettier/index.js your application uses 20 | module.exports = absRequire(`prettier/index.js`); 21 | -------------------------------------------------------------------------------- /front/.yarn/sdks/prettier/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prettier", 3 | "version": "2.7.1-sdk", 4 | "main": "./index.js", 5 | "type": "commonjs" 6 | } 7 | -------------------------------------------------------------------------------- /front/.yarn/sdks/typescript/bin/tsc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/bin/tsc 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/bin/tsc your application uses 20 | module.exports = absRequire(`typescript/bin/tsc`); 21 | -------------------------------------------------------------------------------- /front/.yarn/sdks/typescript/bin/tsserver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/bin/tsserver 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/bin/tsserver your application uses 20 | module.exports = absRequire(`typescript/bin/tsserver`); 21 | -------------------------------------------------------------------------------- /front/.yarn/sdks/typescript/lib/tsc.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/lib/tsc.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/lib/tsc.js your application uses 20 | module.exports = absRequire(`typescript/lib/tsc.js`); 21 | -------------------------------------------------------------------------------- /front/.yarn/sdks/typescript/lib/typescript.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/lib/typescript.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/lib/typescript.js your application uses 20 | module.exports = absRequire(`typescript/lib/typescript.js`); 21 | -------------------------------------------------------------------------------- /front/.yarn/sdks/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript", 3 | "version": "4.7.4-sdk", 4 | "main": "./lib/typescript.js", 5 | "type": "commonjs" 6 | } 7 | -------------------------------------------------------------------------------- /front/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | packageExtensions: 4 | "@expo/cli@*": 5 | dependencies: 6 | expo-modules-autolinking: "*" 7 | babel-preset-expo@*: 8 | dependencies: 9 | "@babel/core": "*" 10 | expo-asset@*: 11 | dependencies: 12 | expo: "*" 13 | react-native-codegen@*: 14 | peerDependenciesMeta: 15 | "@babel/preset-env": 16 | optional: true 17 | 18 | plugins: 19 | - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs 20 | spec: "@yarnpkg/plugin-workspace-tools" 21 | - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs 22 | spec: "@yarnpkg/plugin-interactive-tools" 23 | 24 | yarnPath: .yarn/releases/yarn-3.2.4.cjs 25 | -------------------------------------------------------------------------------- /front/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine AS builder 2 | WORKDIR /app 3 | COPY .yarn ./.yarn 4 | COPY .yarnrc.yml ./ 5 | COPY package.json yarn.lock ./ 6 | COPY apps/web/package.json apps/web/package.json 7 | COPY apps/mobile/package.json apps/mobile/package.json 8 | COPY packages/ui/package.json packages/ui/package.json 9 | COPY packages/primitives/package.json packages/primitives/package.json 10 | COPY packages/models/package.json packages/models/package.json 11 | RUN yarn --immutable 12 | 13 | COPY . . 14 | ENV NEXT_TELEMETRY_DISABLED=1 15 | ENV NODE_ENV=production 16 | RUN yarn build:web 17 | 18 | 19 | FROM node:18-alpine 20 | WORKDIR /app 21 | 22 | COPY --from=builder /app/apps/web/.next/standalone/apps/web . 23 | COPY --from=builder /app/apps/web/.next/standalone/node_modules ./node_modules 24 | COPY --from=builder /app/apps/web/.next/static ./.next/static/ 25 | COPY --from=builder /app/apps/web/public ./public 26 | 27 | EXPOSE 8901 28 | ENV PORT=8901 29 | 30 | ENV NEXT_TELEMETRY_DISABLED=1 31 | ENV NODE_ENV=production 32 | CMD ["node", "server.js"] 33 | -------------------------------------------------------------------------------- /front/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine 2 | RUN apk add git bash 3 | WORKDIR /app 4 | COPY .yarn ./.yarn 5 | COPY .yarnrc.yml ./ 6 | COPY package.json yarn.lock ./ 7 | COPY apps/web/package.json apps/web/package.json 8 | COPY apps/mobile/package.json apps/mobile/package.json 9 | COPY packages/ui/package.json packages/ui/package.json 10 | COPY packages/primitives/package.json packages/primitives/package.json 11 | COPY packages/models/package.json packages/models/package.json 12 | RUN yarn --immutable 13 | 14 | ENV NEXT_TELEMETRY_DISABLED=1 15 | EXPOSE 3000 16 | EXPOSE 8081 17 | ENTRYPOINT ["yarn", "dev"] 18 | -------------------------------------------------------------------------------- /front/apps/mobile/app/(app)/(tabs)/browse.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { BrowsePage } from "@kyoo/ui"; 22 | 23 | export default BrowsePage; 24 | -------------------------------------------------------------------------------- /front/apps/mobile/app/(app)/(tabs)/downloads.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { DownloadPage } from "@kyoo/ui"; 22 | 23 | export default DownloadPage; 24 | -------------------------------------------------------------------------------- /front/apps/mobile/app/(app)/(tabs)/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { HomePage } from "@kyoo/ui"; 22 | import { withRoute } from "../../utils"; 23 | 24 | export default withRoute(HomePage, { 25 | options: { headerTransparent: true, headerStyle: { backgroundColor: "transparent" } }, 26 | statusBar: { barStyle: "light-content" }, 27 | }); 28 | -------------------------------------------------------------------------------- /front/apps/mobile/app/(app)/admin/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { AdminPage } from "@kyoo/ui"; 22 | 23 | export default AdminPage; 24 | -------------------------------------------------------------------------------- /front/apps/mobile/app/(app)/collection/[slug]/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { CollectionPage } from "@kyoo/ui"; 22 | import { withRoute } from "../../../utils"; 23 | 24 | export default withRoute(CollectionPage, { 25 | options: { headerTransparent: true, headerStyle: { backgroundColor: "transparent" } }, 26 | statusBar: { barStyle: "light-content" }, 27 | }); 28 | -------------------------------------------------------------------------------- /front/apps/mobile/app/(app)/movie/[slug]/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { MovieDetails } from "@kyoo/ui"; 22 | import { withRoute } from "../../../utils"; 23 | 24 | export default withRoute(MovieDetails, { 25 | options: { headerTransparent: true, headerStyle: { backgroundColor: "transparent" } }, 26 | statusBar: { barStyle: "light-content" }, 27 | }); 28 | -------------------------------------------------------------------------------- /front/apps/mobile/app/(app)/movie/[slug]/watch.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { Player } from "@kyoo/ui"; 22 | import { withRoute } from "../../../utils"; 23 | 24 | export default withRoute( 25 | Player, 26 | { 27 | options: { 28 | headerShown: false, 29 | navigationBarHidden: true, 30 | }, 31 | statusBar: { hidden: true }, 32 | fullscreen: true, 33 | }, 34 | { type: "movie" }, 35 | ); 36 | -------------------------------------------------------------------------------- /front/apps/mobile/app/(app)/settings/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { SettingsPage } from "@kyoo/ui"; 22 | 23 | export default SettingsPage; 24 | -------------------------------------------------------------------------------- /front/apps/mobile/app/(app)/show/[slug].tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { ShowDetails } from "@kyoo/ui"; 22 | import { withRoute } from "../../utils"; 23 | 24 | export default withRoute(ShowDetails, { 25 | options: { headerTransparent: true, headerStyle: { backgroundColor: "transparent" } }, 26 | statusBar: { barStyle: "light-content" }, 27 | }); 28 | -------------------------------------------------------------------------------- /front/apps/mobile/app/(app)/watch/[slug].tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { Player } from "@kyoo/ui"; 22 | import { withRoute } from "../../utils"; 23 | 24 | export default withRoute( 25 | Player, 26 | { 27 | options: { 28 | headerShown: false, 29 | navigationBarHidden: true, 30 | }, 31 | statusBar: { hidden: true }, 32 | fullscreen: true, 33 | }, 34 | { type: "episode" }, 35 | ); 36 | -------------------------------------------------------------------------------- /front/apps/mobile/app/(public)/connection-error.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { ConnectionError } from "@kyoo/ui"; 22 | 23 | export default ConnectionError; 24 | -------------------------------------------------------------------------------- /front/apps/mobile/app/(public)/login/callback.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { OidcCallbackPage } from "@kyoo/ui"; 22 | import { withRoute } from "../../utils"; 23 | 24 | export default withRoute(OidcCallbackPage); 25 | -------------------------------------------------------------------------------- /front/apps/mobile/app/(public)/login/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { LoginPage } from "@kyoo/ui"; 22 | import { withRoute } from "../../utils"; 23 | 24 | export default withRoute(LoginPage); 25 | -------------------------------------------------------------------------------- /front/apps/mobile/app/(public)/register/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { RegisterPage } from "@kyoo/ui"; 22 | import { withRoute } from "../../utils"; 23 | 24 | export default withRoute(RegisterPage); 25 | -------------------------------------------------------------------------------- /front/apps/mobile/app/(public)/server-url/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { ServerUrlPage } from "@kyoo/ui"; 22 | 23 | export default ServerUrlPage; 24 | -------------------------------------------------------------------------------- /front/apps/mobile/app/(public)/settings/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { SettingsPage } from "@kyoo/ui"; 22 | 23 | export default SettingsPage; 24 | -------------------------------------------------------------------------------- /front/apps/mobile/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoriya/Kyoo/37e0fc3f0e9b7acd090563021aab557cc68415ce/front/apps/mobile/assets/icon.png -------------------------------------------------------------------------------- /front/apps/mobile/eas.json: -------------------------------------------------------------------------------- 1 | { 2 | "cli": { 3 | "version": ">= 3.0.0", 4 | "appVersionSource": "remote" 5 | }, 6 | "build": { 7 | "development": { 8 | "developmentClient": true, 9 | "distribution": "internal", 10 | "env": { 11 | "APP_VARIANT": "development" 12 | }, 13 | "channel": "development" 14 | }, 15 | "preview": { 16 | "distribution": "internal", 17 | "channel": "preview", 18 | "android": { 19 | "buildType": "apk" 20 | } 21 | }, 22 | "production": { 23 | "channel": "prod" 24 | } 25 | }, 26 | "submit": { 27 | "production": {} 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /front/apps/mobile/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/tsconfig.base", 3 | "compilerOptions": { 4 | "strict": true 5 | }, 6 | "include": [ 7 | "**/*.ts", 8 | "**/*.tsx", 9 | "../../packages/ui/src/i18n-d.d.ts", 10 | "../../packages/ui/src/svg.d.ts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /front/apps/web/public/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoriya/Kyoo/37e0fc3f0e9b7acd090563021aab557cc68415ce/front/apps/web/public/banner.png -------------------------------------------------------------------------------- /front/apps/web/public/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoriya/Kyoo/37e0fc3f0e9b7acd090563021aab557cc68415ce/front/apps/web/public/icon-128x128.png -------------------------------------------------------------------------------- /front/apps/web/public/icon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoriya/Kyoo/37e0fc3f0e9b7acd090563021aab557cc68415ce/front/apps/web/public/icon-16x16.png -------------------------------------------------------------------------------- /front/apps/web/public/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoriya/Kyoo/37e0fc3f0e9b7acd090563021aab557cc68415ce/front/apps/web/public/icon-256x256.png -------------------------------------------------------------------------------- /front/apps/web/public/icon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoriya/Kyoo/37e0fc3f0e9b7acd090563021aab557cc68415ce/front/apps/web/public/icon-32x32.png -------------------------------------------------------------------------------- /front/apps/web/public/icon-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoriya/Kyoo/37e0fc3f0e9b7acd090563021aab557cc68415ce/front/apps/web/public/icon-64x64.png -------------------------------------------------------------------------------- /front/apps/web/src/pages/admin/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { AdminPage } from "@kyoo/ui"; 22 | import { withRoute } from "~/router"; 23 | 24 | export default withRoute(AdminPage); 25 | -------------------------------------------------------------------------------- /front/apps/web/src/pages/browse/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { BrowsePage } from "@kyoo/ui"; 22 | import { withRoute } from "~/router"; 23 | 24 | export default withRoute(BrowsePage); 25 | -------------------------------------------------------------------------------- /front/apps/web/src/pages/collection/[slug]/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { CollectionPage } from "@kyoo/ui"; 22 | import { withRoute } from "~/router"; 23 | 24 | export default withRoute(CollectionPage); 25 | -------------------------------------------------------------------------------- /front/apps/web/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { HomePage } from "@kyoo/ui"; 22 | 23 | export default HomePage; 24 | -------------------------------------------------------------------------------- /front/apps/web/src/pages/login/callback.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { OidcCallbackPage } from "@kyoo/ui"; 22 | import { withRoute } from "~/router"; 23 | 24 | export default withRoute(OidcCallbackPage); 25 | -------------------------------------------------------------------------------- /front/apps/web/src/pages/login/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { LoginPage } from "@kyoo/ui"; 22 | import { withRoute } from "~/router"; 23 | 24 | export default withRoute(LoginPage); 25 | -------------------------------------------------------------------------------- /front/apps/web/src/pages/movie/[slug]/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { MovieDetails } from "@kyoo/ui"; 22 | import { withRoute } from "~/router"; 23 | 24 | export default withRoute(MovieDetails); 25 | -------------------------------------------------------------------------------- /front/apps/web/src/pages/movie/[slug]/watch.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { Player } from "@kyoo/ui"; 22 | import { withRoute } from "~/router"; 23 | 24 | export default withRoute(Player, { type: "movie" }); 25 | -------------------------------------------------------------------------------- /front/apps/web/src/pages/register/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { RegisterPage } from "@kyoo/ui"; 22 | import { withRoute } from "~/router"; 23 | 24 | export default withRoute(RegisterPage); 25 | -------------------------------------------------------------------------------- /front/apps/web/src/pages/search/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { SearchPage } from "@kyoo/ui"; 22 | import { withRoute } from "~/router"; 23 | 24 | export default withRoute(SearchPage); 25 | -------------------------------------------------------------------------------- /front/apps/web/src/pages/settings/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { SettingsPage } from "@kyoo/ui"; 22 | import { withRoute } from "~/router"; 23 | 24 | export default withRoute(SettingsPage); 25 | -------------------------------------------------------------------------------- /front/apps/web/src/pages/setup/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { SetupPage } from "@kyoo/ui"; 22 | import { withRoute } from "~/router"; 23 | 24 | export default withRoute(SetupPage); 25 | -------------------------------------------------------------------------------- /front/apps/web/src/pages/show/[slug].tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { ShowDetails } from "@kyoo/ui"; 22 | import { withRoute } from "~/router"; 23 | 24 | export default withRoute(ShowDetails); 25 | -------------------------------------------------------------------------------- /front/apps/web/src/pages/watch/[slug].tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { Player } from "@kyoo/ui"; 22 | import { withRoute } from "~/router"; 23 | 24 | export default withRoute(Player, { type: "episode" }); 25 | -------------------------------------------------------------------------------- /front/apps/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "~/*": ["src/*"] 20 | } 21 | }, 22 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "../../packages/ui/src/i18n-d.d.ts"], 23 | "exclude": ["node_modules"] 24 | } 25 | -------------------------------------------------------------------------------- /front/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../biome.json"] 3 | } 4 | -------------------------------------------------------------------------------- /front/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kyoo", 3 | "private": true, 4 | "scripts": { 5 | "dev": "yarn workspaces foreach -pi run dev", 6 | "web": "yarn workspace web dev", 7 | "mobile": "yarn workspace mobile dev", 8 | "build:web": "yarn workspace web build", 9 | "build:mobile": "yarn workspace mobile build", 10 | "build:mobile:apk": "yarn workspace mobile build:apk", 11 | "build:mobile:dev": "yarn workspace mobile build:dev", 12 | "update": "yarn workspace mobile update", 13 | "lint": "biome lint .", 14 | "lint:fix": "biome lint . --write", 15 | "format": "biome format .", 16 | "format:fix": "biome format . --write" 17 | }, 18 | "workspaces": ["apps/*", "packages/*"], 19 | "devDependencies": { 20 | "@biomejs/biome": "1.8.3", 21 | "typescript": "5.5.4" 22 | }, 23 | "packageManager": "yarn@3.2.4" 24 | } 25 | -------------------------------------------------------------------------------- /front/packages/models/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kyoo/models", 3 | "main": "src/index.ts", 4 | "types": "src/index.ts", 5 | "sideEffects": false, 6 | "packageManager": "yarn@3.2.4", 7 | "devDependencies": { 8 | "react-native-mmkv": "^2.12.2", 9 | "typescript": "^5.5.4" 10 | }, 11 | "peerDependencies": { 12 | "@tanstack/react-query": "*", 13 | "react": "*", 14 | "react-native": "*" 15 | }, 16 | "peerDependenciesMeta": { 17 | "react-native-web": { 18 | "optional": true 19 | } 20 | }, 21 | "dependencies": { 22 | "zod": "^3.23.8" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /front/packages/models/src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | export * from "./accounts"; 22 | export { storage } from "./account-internal"; 23 | export * from "./theme"; 24 | export * from "./resources"; 25 | export * from "./traits"; 26 | export * from "./page"; 27 | export * from "./kyoo-errors"; 28 | export * from "./utils"; 29 | export * from "./login"; 30 | export * from "./issue"; 31 | 32 | export * from "./query"; 33 | -------------------------------------------------------------------------------- /front/packages/models/src/kyoo-errors.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | /** 22 | * The list of errors that where made in the request. 23 | */ 24 | export interface KyooErrors { 25 | /** 26 | * The list of errors that where made in the request. 27 | * 28 | * @example `["InvalidFilter: no field 'startYear' on a collection"]` 29 | */ 30 | errors: string[]; 31 | 32 | status?: number | "aborted" | "parse" | "json"; 33 | } 34 | -------------------------------------------------------------------------------- /front/packages/models/src/resources/news.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { z } from "zod"; 22 | import { EpisodeP } from "./episode"; 23 | import { MovieP } from "./movie"; 24 | 25 | export const NewsP = z.union([ 26 | /* 27 | * Either an episode 28 | */ 29 | EpisodeP, 30 | /* 31 | * Or a Movie 32 | */ 33 | MovieP, 34 | ]); 35 | 36 | /** 37 | * A new item added to kyoo. 38 | */ 39 | export type News = z.infer; 40 | -------------------------------------------------------------------------------- /front/packages/models/src/resources/quality.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { z } from "zod"; 22 | 23 | export const QualityP = z 24 | .union([ 25 | z.literal("original"), 26 | z.literal("8k"), 27 | z.literal("4k"), 28 | z.literal("1440p"), 29 | z.literal("1080p"), 30 | z.literal("720p"), 31 | z.literal("480p"), 32 | z.literal("360p"), 33 | z.literal("240p"), 34 | ]) 35 | .default("original"); 36 | 37 | /** 38 | * A Video Quality Enum. 39 | */ 40 | export type Quality = z.infer; 41 | -------------------------------------------------------------------------------- /front/packages/models/src/resources/studio.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { z } from "zod"; 22 | import { ResourceP } from "../traits/resource"; 23 | 24 | export const StudioP = ResourceP("studio").extend({ 25 | /** 26 | * The name of this studio. 27 | */ 28 | name: z.string(), 29 | }); 30 | 31 | /** 32 | * A studio that make shows. 33 | */ 34 | export type Studio = z.infer; 35 | -------------------------------------------------------------------------------- /front/packages/models/src/resources/watchlist.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { z } from "zod"; 22 | import { MovieP } from "./movie"; 23 | import { ShowP } from "./show"; 24 | 25 | export const WatchlistP = z.union([ 26 | /* 27 | * Either a show 28 | */ 29 | ShowP, 30 | /* 31 | * Or a Movie 32 | */ 33 | MovieP, 34 | ]); 35 | 36 | /** 37 | * A item in the user's watchlist. 38 | */ 39 | export type Watchlist = z.infer; 40 | -------------------------------------------------------------------------------- /front/packages/models/src/traits/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | export * from "./resource"; 22 | export * from "./images"; 23 | -------------------------------------------------------------------------------- /front/packages/models/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "declaration": true, 6 | "sourceMap": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "esModuleInterop": true, 13 | "module": "esnext", 14 | "moduleResolution": "node", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "jsx": "react-jsx", 18 | "incremental": true, 19 | "baseUrl": ".", 20 | "paths": { 21 | "~/*": ["src/*"] 22 | } 23 | }, 24 | "include": ["**/*.ts", "**/*.tsx"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /front/packages/primitives/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | export const imageBorderRadius = 10; 22 | -------------------------------------------------------------------------------- /front/packages/primitives/src/svg.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | declare module "*.svg" { 22 | import type React from "react"; 23 | import type { SvgProps } from "react-native-svg"; 24 | const content: React.FC; 25 | export default content; 26 | } 27 | -------------------------------------------------------------------------------- /front/packages/primitives/src/themes/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | export * from "./theme"; 22 | export * from "./catppuccin"; 23 | -------------------------------------------------------------------------------- /front/packages/primitives/src/tooltip.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { ToastAndroid } from "react-native"; 22 | 23 | export const tooltip = (tooltip: string, up?: boolean) => ({ 24 | onLongPress: () => { 25 | ToastAndroid.show(tooltip, ToastAndroid.SHORT); 26 | }, 27 | }); 28 | 29 | import type { Tooltip as RTooltip } from "react-tooltip"; 30 | export const Tooltip: typeof RTooltip = (() => null) as any; 31 | -------------------------------------------------------------------------------- /front/packages/primitives/src/utils/capitalize.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | export const capitalize = (str: string): string => { 22 | return str 23 | .split(" ") 24 | .map((s) => s.trim()) 25 | .map((s) => { 26 | if (s.length > 1) { 27 | return s.charAt(0).toUpperCase() + s.slice(1); 28 | } 29 | return s; 30 | }) 31 | .join(" "); 32 | }; 33 | -------------------------------------------------------------------------------- /front/packages/primitives/src/utils/head.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | export const Head = ({ 22 | title, 23 | description, 24 | image, 25 | }: { 26 | title?: string | null; 27 | description?: string | null; 28 | image?: string | null; 29 | }) => { 30 | return null; 31 | }; 32 | -------------------------------------------------------------------------------- /front/packages/primitives/src/utils/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | export * from "./breakpoints"; 22 | export * from "./nojs"; 23 | export * from "./head"; 24 | export * from "./spacing"; 25 | export * from "./capitalize"; 26 | export * from "./touchonly"; 27 | export * from "./page-style"; 28 | -------------------------------------------------------------------------------- /front/packages/primitives/src/utils/nojs.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import type { ViewProps } from "react-native"; 22 | 23 | export const hiddenIfNoJs: ViewProps = { style: { $$css: true, noJs: "noJsHidden" } as any }; 24 | 25 | export const HiddenIfNoJs = () => ( 26 | 35 | ); 36 | -------------------------------------------------------------------------------- /front/packages/primitives/src/utils/page-style.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { useSafeAreaInsets } from "react-native-safe-area-context"; 22 | 23 | export const usePageStyle = () => { 24 | const insets = useSafeAreaInsets(); 25 | return { paddingBottom: insets.bottom } as const; 26 | }; 27 | -------------------------------------------------------------------------------- /front/packages/primitives/src/utils/page-style.web.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | export const usePageStyle = () => { 22 | return {} as const; 23 | }; 24 | -------------------------------------------------------------------------------- /front/packages/primitives/src/utils/spacing.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import { Platform } from "react-native"; 22 | import { px } from "yoshiki/native"; 23 | 24 | export const important = (value: T): T => { 25 | return `${value} !important` as T; 26 | }; 27 | 28 | export const ts = (spacing: number) => { 29 | return px(spacing * 8); 30 | }; 31 | 32 | export const focusReset: object = 33 | Platform.OS === "web" 34 | ? { 35 | boxShadow: "unset", 36 | outline: "none", 37 | } 38 | : {}; 39 | -------------------------------------------------------------------------------- /front/packages/primitives/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "declaration": true, 6 | "sourceMap": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "esModuleInterop": true, 13 | "module": "esnext", 14 | "moduleResolution": "node", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "jsx": "react-jsx", 18 | "incremental": true, 19 | "baseUrl": ".", 20 | "paths": { 21 | "~/*": ["src/*"] 22 | } 23 | }, 24 | "include": ["**/*.ts", "**/*.tsx"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /front/packages/ui/src/details/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | export { MovieDetails } from "./movie"; 22 | export { ShowDetails } from "./show"; 23 | -------------------------------------------------------------------------------- /front/packages/ui/src/downloads/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | export { DownloadPage } from "./page"; 22 | export { DownloadProvider, useDownloader } from "./state"; 23 | -------------------------------------------------------------------------------- /front/packages/ui/src/errors/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | export * from "./error"; 22 | export * from "./unauthorized"; 23 | export * from "./connection"; 24 | export * from "./setup"; 25 | -------------------------------------------------------------------------------- /front/packages/ui/src/i18n-d.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | import "i18next"; 22 | import type en from "../../../translations/en.json"; 23 | 24 | declare module "i18next" { 25 | interface CustomTypeOptions { 26 | returnNull: false; 27 | resources: { translation: typeof en }; 28 | } 29 | 30 | interface i18n { 31 | systemLanguage: string; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /front/packages/ui/src/login/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | export { LoginPage } from "./login"; 22 | export { RegisterPage } from "./register"; 23 | export { ServerUrlPage } from "./server-url"; 24 | export { OidcCallbackPage } from "./oidc"; 25 | -------------------------------------------------------------------------------- /front/packages/ui/src/svg.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Kyoo - A portable and vast media library solution. 3 | * Copyright (c) Kyoo. 4 | * 5 | * See AUTHORS.md and LICENSE file in the project root for full license information. 6 | * 7 | * Kyoo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * any later version. 11 | * 12 | * Kyoo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Kyoo. If not, see . 19 | */ 20 | 21 | declare module "*.svg" { 22 | import type React from "react"; 23 | import type { SvgProps } from "react-native-svg"; 24 | const content: React.FC; 25 | export default content; 26 | } 27 | -------------------------------------------------------------------------------- /front/packages/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "declaration": true, 6 | "sourceMap": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "esModuleInterop": true, 13 | "module": "esnext", 14 | "moduleResolution": "node", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "jsx": "react-jsx", 18 | "incremental": true 19 | }, 20 | "include": ["**/*.ts", "**/*.tsx"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /front/translations/index.js: -------------------------------------------------------------------------------- 1 | import am from "./am"; 2 | import ar from "./ar"; 3 | import de from "./de"; 4 | import en from "./en"; 5 | import es from "./es"; 6 | import fr from "./fr"; 7 | import it from "./it"; 8 | import ko from "./ko"; 9 | import ml from "./ml"; 10 | import nl from "./nl"; 11 | import pl from "./pl"; 12 | import pt from "./pt"; 13 | import pt_br from "./pt_br"; 14 | import ro from "./ro"; 15 | import ru from "./ru"; 16 | import ta from "./ta"; 17 | import tr from "./tr"; 18 | import uk from "./uk"; 19 | import zh from "./zh"; 20 | 21 | export default { 22 | am: { translation: am }, 23 | ar: { translation: ar }, 24 | de: { translation: de }, 25 | en: { translation: en }, 26 | es: { translation: es }, 27 | fr: { translation: fr }, 28 | it: { translation: it }, 29 | ko: { translation: ko }, 30 | ml: { translation: ml }, 31 | nl: { translation: nl }, 32 | pl: { translation: pl }, 33 | pt: { translation: pt }, 34 | "pt-BR": { translation: pt_br }, 35 | ro: { translation: ro }, 36 | ru: { translation: ru }, 37 | ta: { translation: ta }, 38 | tr: { translation: tr }, 39 | uk: { translation: uk }, 40 | zh: { translation: zh }, 41 | }; 42 | -------------------------------------------------------------------------------- /icons/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoriya/Kyoo/37e0fc3f0e9b7acd090563021aab557cc68415ce/icons/banner.png -------------------------------------------------------------------------------- /icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoriya/Kyoo/37e0fc3f0e9b7acd090563021aab557cc68415ce/icons/icon-128x128.png -------------------------------------------------------------------------------- /icons/icon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoriya/Kyoo/37e0fc3f0e9b7acd090563021aab557cc68415ce/icons/icon-16x16.png -------------------------------------------------------------------------------- /icons/icon-256x256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoriya/Kyoo/37e0fc3f0e9b7acd090563021aab557cc68415ce/icons/icon-256x256.ico -------------------------------------------------------------------------------- /icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoriya/Kyoo/37e0fc3f0e9b7acd090563021aab557cc68415ce/icons/icon-256x256.png -------------------------------------------------------------------------------- /icons/icon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoriya/Kyoo/37e0fc3f0e9b7acd090563021aab557cc68415ce/icons/icon-32x32.png -------------------------------------------------------------------------------- /icons/icon-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoriya/Kyoo/37e0fc3f0e9b7acd090563021aab557cc68415ce/icons/icon-64x64.png -------------------------------------------------------------------------------- /renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:recommended", ":disableRateLimiting"], 4 | "schedule": ["on monday"], 5 | "minimumReleaseAge": "5 days", 6 | "ignorePaths": ["**/front/**"], 7 | "packageRules": [ 8 | { 9 | "matchDatasources": ["docker"], 10 | "matchPackagePatterns": ["meilisearch"], 11 | "allowedVersions": "<1.5" 12 | }, 13 | { 14 | "matchDatasources": ["docker"], 15 | "matchPackagePatterns": ["postgres"], 16 | "allowedVersions": "<16" 17 | } 18 | ], 19 | "postUpdateOptions": [ 20 | "gomodTidy", 21 | "gomodUpdateImportPaths" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /scanner/.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile* 2 | 3 | -------------------------------------------------------------------------------- /scanner/.env.example: -------------------------------------------------------------------------------- 1 | # vi: ft=sh 2 | # shellcheck disable=SC2034 3 | 4 | # RabbitMQ settings 5 | # URL examples: https://docs.aio-pika.com/#url-examples 6 | # This uses AIORMQ (https://github.com/mosquito/aiormq/) under the hood, and supports whatever the library supports. 7 | # RABBITMQ_URL=ampqs://user:password@rabbitmq-server:1234/vhost?capath=/path/to/cacert.pem&certfile=/path/to/cert.pem&keyfile=/path/to/key.pem 8 | # These values are ignored when the RABBITMQ_URL is set 9 | RABBITMQ_HOST=rabbitmq 10 | RABBITMQ_PORT=5672 11 | RABBITMQ_USER=guest 12 | RABBITMQ_PASSWORD=guest 13 | -------------------------------------------------------------------------------- /scanner/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | -------------------------------------------------------------------------------- /scanner/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.13 2 | WORKDIR /app 3 | 4 | COPY ./requirements.txt . 5 | RUN pip3 install -r ./requirements.txt 6 | 7 | COPY . . 8 | ENTRYPOINT ["python3", "-m"] 9 | CMD ["scanner"] 10 | -------------------------------------------------------------------------------- /scanner/matcher/__init__.py: -------------------------------------------------------------------------------- 1 | async def main(): 2 | import logging 3 | import sys 4 | from providers.provider import Provider 5 | from providers.kyoo_client import KyooClient 6 | from .matcher import Matcher 7 | from .subscriber import Subscriber 8 | 9 | logging.basicConfig(level=logging.INFO) 10 | if len(sys.argv) > 1 and sys.argv[1] == "-v": 11 | logging.basicConfig(level=logging.DEBUG) 12 | logging.getLogger("watchfiles").setLevel(logging.WARNING) 13 | logging.getLogger("rebulk").setLevel(logging.WARNING) 14 | 15 | async with KyooClient() as kyoo, Subscriber() as sub: 16 | provider = Provider.get_default(kyoo.client) 17 | matcher = Matcher(kyoo, provider) 18 | await sub.listen(matcher) 19 | -------------------------------------------------------------------------------- /scanner/matcher/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import asyncio 4 | import matcher 5 | 6 | asyncio.run(matcher.main()) 7 | -------------------------------------------------------------------------------- /scanner/providers/rabbit_base.py: -------------------------------------------------------------------------------- 1 | import os 2 | from aio_pika import connect_robust 3 | 4 | 5 | class RabbitBase: 6 | QUEUE = "scanner" 7 | 8 | async def __aenter__(self): 9 | self._con = await connect_robust( 10 | os.environ.get("RABBITMQ_URL"), 11 | host=os.environ.get("RABBITMQ_HOST", "rabbitmq"), 12 | port=int(os.environ.get("RABBITMQ_PORT", "5672")), 13 | login=os.environ.get("RABBITMQ_DEFAULT_USER", "guest"), 14 | password=os.environ.get("RABBITMQ_DEFAULT_PASS", "guest"), 15 | ) 16 | 17 | # Attempt to declare the queue passively in case it already exists. 18 | try: 19 | self._channel = await self._con.channel() 20 | self._queue = await self._channel.declare_queue(self.QUEUE, passive=True) 21 | return self 22 | except Exception: 23 | # The server will close the channel on error. 24 | # Cleanup the reference to it. 25 | await self._channel.close() 26 | 27 | # The queue does not exist, so actively declare it. 28 | self._channel = await self._con.channel() 29 | self._queue = await self._channel.declare_queue(self.QUEUE) 30 | return self 31 | 32 | async def __aexit__(self, exc_type, exc_value, exc_tb): 33 | await self._channel.close() 34 | await self._con.close() 35 | -------------------------------------------------------------------------------- /scanner/providers/types/collection.py: -------------------------------------------------------------------------------- 1 | from dataclasses import asdict, dataclass, field 2 | from typing import Optional 3 | 4 | from providers.utils import ProviderError, select_translation, select_image 5 | 6 | from .metadataid import MetadataID 7 | 8 | 9 | @dataclass 10 | class CollectionTranslation: 11 | name: str 12 | overview: Optional[str] = None 13 | posters: list[str] = field(default_factory=list) 14 | logos: list[str] = field(default_factory=list) 15 | thumbnails: list[str] = field(default_factory=list) 16 | 17 | 18 | @dataclass 19 | class Collection: 20 | external_id: dict[str, MetadataID] 21 | translations: dict[str, CollectionTranslation] = field(default_factory=dict) 22 | 23 | def to_kyoo(self): 24 | trans = select_translation(self) 25 | if trans is None: 26 | raise ProviderError( 27 | "Could not find translations for the collection. Aborting" 28 | ) 29 | return { 30 | **asdict(self), 31 | **asdict(trans), 32 | "poster": select_image(self, "posters"), 33 | "thumbnail": select_image(self, "thumbnails"), 34 | "logo": select_image(self, "logos"), 35 | } 36 | -------------------------------------------------------------------------------- /scanner/providers/types/episode.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from dataclasses import dataclass, field, asdict 3 | from typing import Optional 4 | 5 | from providers.utils import select_translation 6 | 7 | from .show import Show 8 | from .metadataid import MetadataID 9 | 10 | 11 | @dataclass 12 | class PartialShow: 13 | name: str 14 | original_language: Optional[str] 15 | external_id: dict[str, MetadataID] 16 | 17 | 18 | @dataclass 19 | class EpisodeID: 20 | show_id: str 21 | season: Optional[int] 22 | episode: int 23 | link: str 24 | 25 | 26 | @dataclass 27 | class EpisodeTranslation: 28 | name: Optional[str] 29 | overview: Optional[str] = None 30 | 31 | 32 | @dataclass 33 | class Episode: 34 | show: Show | PartialShow 35 | season_number: int 36 | episode_number: int 37 | absolute_number: int 38 | runtime: Optional[int] 39 | release_date: Optional[date | int] 40 | thumbnail: Optional[str] 41 | external_id: dict[str, EpisodeID] 42 | 43 | path: Optional[str] = None 44 | show_id: Optional[str] = None 45 | season_id: Optional[str] = None 46 | translations: dict[str, EpisodeTranslation] = field(default_factory=dict) 47 | 48 | def to_kyoo(self): 49 | trans = select_translation(self) or EpisodeTranslation("") 50 | return { 51 | **asdict(self), 52 | **asdict(trans), 53 | "show": None, 54 | } 55 | -------------------------------------------------------------------------------- /scanner/providers/types/genre.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Genre(str, Enum): 5 | ACTION = "Action" 6 | ADVENTURE = "Adventure" 7 | ANIMATION = "Animation" 8 | COMEDY = "Comedy" 9 | CRIME = "Crime" 10 | DOCUMENTARY = "Documentary" 11 | DRAMA = "Drama" 12 | FAMILY = "Family" 13 | FANTASY = "Fantasy" 14 | HISTORY = "History" 15 | HORROR = "Horror" 16 | MUSIC = "Music" 17 | MYSTERY = "Mystery" 18 | ROMANCE = "Romance" 19 | SCIENCE_FICTION = "ScienceFiction" 20 | THRILLER = "Thriller" 21 | WAR = "War" 22 | WESTERN = "Western" 23 | KIDS = "Kids" 24 | NEWS = "News" 25 | REALITY = "Reality" 26 | SOAP = "Soap" 27 | TALK = "Talk" 28 | POLITICS = "Politics" 29 | 30 | def to_kyoo(self): 31 | return self.value 32 | -------------------------------------------------------------------------------- /scanner/providers/types/metadataid.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Optional 3 | 4 | 5 | @dataclass 6 | class MetadataID: 7 | data_id: str 8 | link: Optional[str] 9 | 10 | def __post_init__(self): 11 | self.data_id = str(self.data_id) 12 | -------------------------------------------------------------------------------- /scanner/providers/types/season.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from dataclasses import dataclass, field, asdict 3 | from typing import Optional 4 | 5 | from providers.utils import select_translation, select_image 6 | 7 | from .metadataid import MetadataID 8 | 9 | 10 | @dataclass 11 | class SeasonTranslation: 12 | name: Optional[str] = None 13 | overview: Optional[str] = None 14 | posters: list[str] = field(default_factory=list) 15 | thumbnails: list[str] = field(default_factory=list) 16 | 17 | 18 | @dataclass 19 | class Season: 20 | season_number: int 21 | # This is not used by kyoo, this is just used internaly by the TMDB provider. 22 | # maybe this should be moved? 23 | episodes_count: int 24 | start_air: Optional[date | int] = None 25 | end_air: Optional[date | int] = None 26 | external_id: dict[str, MetadataID] = field(default_factory=dict) 27 | 28 | show_id: Optional[str] = None 29 | translations: dict[str, SeasonTranslation] = field(default_factory=dict) 30 | 31 | def to_kyoo(self): 32 | trans = select_translation(self) or SeasonTranslation() 33 | return { 34 | **asdict(self), 35 | **asdict(trans), 36 | "poster": select_image(self, "posters"), 37 | "thumbnail": select_image(self, "thumbnails"), 38 | } 39 | -------------------------------------------------------------------------------- /scanner/providers/types/studio.py: -------------------------------------------------------------------------------- 1 | from dataclasses import asdict, dataclass, field 2 | 3 | from .metadataid import MetadataID 4 | 5 | 6 | @dataclass 7 | class Studio: 8 | name: str 9 | logos: list[str] = field(default_factory=list) 10 | external_id: dict[str, MetadataID] = field(default_factory=dict) 11 | 12 | def to_kyoo(self): 13 | return { 14 | **asdict(self), 15 | "logo": next(iter(self.logos), None), 16 | } 17 | -------------------------------------------------------------------------------- /scanner/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.ruff.format] 2 | indent-style = "tab" 3 | 4 | [tool.pyright] 5 | reportAbstractUsage = false 6 | -------------------------------------------------------------------------------- /scanner/requirements.txt: -------------------------------------------------------------------------------- 1 | guessit@git+https://github.com/zoriya/guessit 2 | aiohttp 3 | jsons 4 | watchfiles 5 | aio-pika 6 | msgspec 7 | langcodes 8 | -------------------------------------------------------------------------------- /scanner/scanner/__init__.py: -------------------------------------------------------------------------------- 1 | async def main(): 2 | import asyncio 3 | import os 4 | import logging 5 | from .monitor import monitor 6 | from .scanner import scan 7 | from .refresher import refresh 8 | from .publisher import Publisher 9 | from .subscriber import Subscriber 10 | from providers.kyoo_client import KyooClient 11 | 12 | logging.basicConfig(level=logging.INFO) 13 | logging.getLogger("watchfiles").setLevel(logging.WARNING) 14 | 15 | async with ( 16 | Publisher() as publisher, 17 | Subscriber() as subscriber, 18 | KyooClient() as client, 19 | ): 20 | path = os.environ.get("SCANNER_LIBRARY_ROOT", "/video") 21 | 22 | async def scan_all(): 23 | await scan(path, publisher, client, remove_deleted=True) 24 | 25 | await asyncio.gather( 26 | monitor(path, publisher, client), 27 | scan_all(), 28 | refresh(publisher, client), 29 | subscriber.listen(scan_all), 30 | ) 31 | -------------------------------------------------------------------------------- /scanner/scanner/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import asyncio 4 | import scanner 5 | 6 | asyncio.run(scanner.main()) 7 | -------------------------------------------------------------------------------- /scanner/scanner/publisher.py: -------------------------------------------------------------------------------- 1 | from guessit.jsonutils import json 2 | from aio_pika import Message 3 | from logging import getLogger 4 | from typing import Literal 5 | 6 | from providers.rabbit_base import RabbitBase 7 | 8 | logger = getLogger(__name__) 9 | 10 | 11 | class Publisher(RabbitBase): 12 | async def _publish(self, data: dict): 13 | await self._channel.default_exchange.publish( 14 | Message(json.dumps(data).encode()), 15 | routing_key=self.QUEUE, 16 | ) 17 | 18 | async def add(self, path: str): 19 | await self._publish({"action": "scan", "path": path}) 20 | 21 | async def delete(self, path: str): 22 | await self._publish({"action": "delete", "path": path}) 23 | 24 | async def refresh( 25 | self, 26 | kind: Literal["collection", "show", "movie", "season", "episode"], 27 | id: str, 28 | **_kwargs, 29 | ): 30 | await self._publish({"action": "refresh", "kind": kind, "id": id}) 31 | -------------------------------------------------------------------------------- /scanner/scanner/refresher.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from logging import getLogger 3 | 4 | from providers.kyoo_client import KyooClient 5 | from scanner.publisher import Publisher 6 | 7 | 8 | logger = getLogger(__name__) 9 | 10 | 11 | async def refresh(publisher: Publisher, client: KyooClient): 12 | while True: 13 | # Check for updates every 4 hours 14 | await asyncio.sleep(60 * 60 * 4) 15 | todo = await client.get("refreshables") 16 | logger.info("Refreshing %d items", len(todo)) 17 | await asyncio.gather(*(publisher.refresh(**x) for x in todo)) 18 | logger.info("Refresh finish. Will check for new items to refresh in 4 hours") 19 | -------------------------------------------------------------------------------- /scanner/scanner/subscriber.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from guessit.jsonutils import json 3 | from aio_pika.abc import AbstractIncomingMessage 4 | from logging import getLogger 5 | 6 | from providers.rabbit_base import RabbitBase 7 | 8 | logger = getLogger(__name__) 9 | 10 | 11 | class Subscriber(RabbitBase): 12 | QUEUE = "scanner.rescan" 13 | 14 | async def listen(self, scan): 15 | async def on_message(message: AbstractIncomingMessage): 16 | try: 17 | await scan() 18 | await message.ack() 19 | except Exception as e: 20 | logger.exception("Unhandled error", exc_info=e) 21 | await message.reject() 22 | 23 | await self._queue.consume(on_message) 24 | await asyncio.Future() 25 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | {pkgs ? import {}}: let 2 | python = pkgs.python312.withPackages (ps: 3 | with ps; [ 4 | guessit 5 | aiohttp 6 | jsons 7 | watchfiles 8 | pika 9 | aio-pika 10 | requests 11 | dataclasses-json 12 | msgspec 13 | langcodes 14 | ]); 15 | dotnet = with pkgs.dotnetCorePackages; 16 | combinePackages [ 17 | sdk_8_0 18 | aspnetcore_8_0 19 | ]; 20 | in 21 | pkgs.mkShell { 22 | packages = with pkgs; [ 23 | # nodejs-18_x 24 | nodePackages.yarn 25 | dotnet 26 | csharpier 27 | python 28 | ruff 29 | go 30 | wgo 31 | mediainfo 32 | ffmpeg-full 33 | postgresql_15 34 | pgformatter 35 | biome 36 | kubernetes-helm 37 | go-migrate 38 | sqlc 39 | go-swag 40 | bun 41 | pkg-config 42 | nodejs 43 | node-gyp 44 | vips 45 | hurl 46 | ]; 47 | 48 | DOTNET_ROOT = "${dotnet}"; 49 | 50 | SHARP_FORCE_GLOBAL_LIBVIPS = 1; 51 | } 52 | -------------------------------------------------------------------------------- /transcoder/.dockerignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /transcoder/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | transcoder 3 | -------------------------------------------------------------------------------- /transcoder/migrations/000001_init_db.down.sql: -------------------------------------------------------------------------------- 1 | begin; 2 | 3 | drop table info; 4 | drop table videos; 5 | drop table audios; 6 | drop table subtitles; 7 | drop table chapters; 8 | drop type chapter_type; 9 | 10 | commit; 11 | -------------------------------------------------------------------------------- /transcoder/migrations/000002_add_hearing_impaired_column.down.sql: -------------------------------------------------------------------------------- 1 | begin; 2 | 3 | alter table subtitles drop column is_hearing_impaired; 4 | 5 | commit; 6 | -------------------------------------------------------------------------------- /transcoder/migrations/000002_add_hearing_impaired_column.up.sql: -------------------------------------------------------------------------------- 1 | begin; 2 | 3 | alter table subtitles add column is_hearing_impaired boolean not null default false; 4 | 5 | commit; 6 | -------------------------------------------------------------------------------- /transcoder/src/audiostream.go: -------------------------------------------------------------------------------- 1 | package src 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | type AudioStream struct { 9 | Stream 10 | index uint32 11 | } 12 | 13 | func (t *Transcoder) NewAudioStream(file *FileStream, idx uint32) (*AudioStream, error) { 14 | log.Printf("Creating a audio stream %d for %s", idx, file.Info.Path) 15 | 16 | keyframes, err := t.metadataService.GetKeyframes(file.Info, false, idx) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | ret := new(AudioStream) 22 | ret.index = idx 23 | NewStream(file, keyframes, ret, &ret.Stream) 24 | return ret, nil 25 | } 26 | 27 | func (as *AudioStream) getOutPath(encoder_id int) string { 28 | return fmt.Sprintf("%s/segment-a%d-%d-%%d.ts", as.file.Out, as.index, encoder_id) 29 | } 30 | 31 | func (as *AudioStream) getFlags() Flags { 32 | return AudioF 33 | } 34 | 35 | func (as *AudioStream) getTranscodeArgs(segments string) []string { 36 | return []string{ 37 | "-map", fmt.Sprintf("0:a:%d", as.index), 38 | "-c:a", "aac", 39 | // TODO: Support 5.1 audio streams. 40 | "-ac", "2", 41 | // TODO: Support multi audio qualities. 42 | "-b:a", "128k", 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /transcoder/src/settings.go: -------------------------------------------------------------------------------- 1 | package src 2 | 3 | import ( 4 | "os" 5 | "path" 6 | ) 7 | 8 | func GetEnvOr(env string, def string) string { 9 | out := os.Getenv(env) 10 | if out == "" { 11 | return def 12 | } 13 | return out 14 | } 15 | 16 | type SettingsT struct { 17 | Outpath string 18 | RoutePrefix string 19 | SafePath string 20 | HwAccel HwAccelT 21 | } 22 | 23 | type HwAccelT struct { 24 | Name string 25 | DecodeFlags []string 26 | EncodeFlags []string 27 | NoResizeFilter string 28 | ScaleFilter string 29 | } 30 | 31 | var Settings = SettingsT{ 32 | // we manually add a folder to make sure we do not delete user data. 33 | Outpath: path.Join(GetEnvOr("GOCODER_CACHE_ROOT", "/cache"), "kyoo_cache"), 34 | RoutePrefix: GetEnvOr("GOCODER_PREFIX", ""), 35 | SafePath: GetEnvOr("GOCODER_SAFE_PATH", "/video"), 36 | HwAccel: DetectHardwareAccel(), 37 | } 38 | -------------------------------------------------------------------------------- /transcoder/src/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "time" 8 | ) 9 | 10 | func PrintExecTime(message string, args ...any) func() { 11 | msg := fmt.Sprintf(message, args...) 12 | start := time.Now() 13 | log.Printf("Running %s", msg) 14 | 15 | return func() { 16 | log.Printf("%s finished in %s", msg, time.Since(start)) 17 | } 18 | } 19 | 20 | func Filter[E any](s []E, f func(E) bool) []E { 21 | s2 := make([]E, 0, len(s)) 22 | for _, e := range s { 23 | if f(e) { 24 | s2 = append(s2, e) 25 | } 26 | } 27 | return s2 28 | } 29 | 30 | // Count returns the number of elements in s that are equal to e. 31 | func Count[S []E, E comparable](s S, e E) int { 32 | var n int 33 | for _, v := range s { 34 | if v == e { 35 | n++ 36 | } 37 | } 38 | return n 39 | } 40 | 41 | // CleanupWithErr runs a cleanup function and checks if it returns an error. 42 | // If the cleanup function returns an error, it is joined with the original error 43 | // and assigned to the original error pointer. 44 | func CleanupWithErr(err *error, fn func() error, msg string, args ...any) { 45 | cleanupErr := fn() 46 | if err == nil { 47 | return 48 | } 49 | 50 | if cleanupErr != nil { 51 | *err = fmt.Errorf("%s: %w", fmt.Sprintf(msg, args...), cleanupErr) 52 | } 53 | *err = errors.Join(*err, cleanupErr) 54 | } 55 | --------------------------------------------------------------------------------