v6.1.0
-----BEGIN PGP SIGNATURE----- iQEzBAABCAAdFiEExEqtY4NnkSypPt1XWDphLYkBWb4FAmYvTgIACgkQWDphLYkB Wb6g6wgAljLbcIKveQgJGWcwlwLefkVDd6up+kBv+0pCFOkdIihV6G7SQRdUujS3 U3/mjOB+hgtIbv9Bu9ahY30831px7gm0/5d7hntkCqoM805kkaf2U2y62oFY+SNq VeiUu0L3tnHdaxIdgsKq+2sX/dZKA0bai+IjcjJaEXc8ZVwiNVU+qXLzEk3xVECM o2LtJp10q8qIA+YjIc9niMfz94a/iwUaix9VY3uG3o8xNeBVVM9PcZDRywFc7NJI Ve/OeEYQ4ydjWYV/RaNZQAVc9zFJObrFwOjWwdYshSz8NbwM8GaxwU36SQUpvfg5 JEF2jmGRA6FzTaaOeA8gwR8O6rq3FA== =PULO -----END PGP SIGNATURE----- gpgsig -----BEGIN PGP SIGNATURE----- iQJNBAABCAA3FiEEGCBYi9NGimfngx9dVTwOu+tdXwgFAmYxd7MZHGtvdG92YWxl eGFyaWFuQGdtYWlsLmNvbQAKCRBVPA67611fCOHjD/44LHXuzlMJBFvW7CLxmao8 ryUuO/BPd2eBHzhZ3b+qJzv9A1bIVqh4ocgRtn8Kukll+z1JWaJi9Tc79ReoOmOK kI9kEM8YTKWwoyun0akaEn+K9xm13XeqJdWLmaIsHhChuj5fvAnfaXHuFOb13LCG wN03RePZfXZMX9OziuXKbCN29f4zthzpObompzw2Lz7qOfVv5FFIJPE2v/vP8Mnq 09KTVnb2hHluy1pAjpDPRNS9yzIqXXrifGjxKF8PSnoUqzlz/KN4/wfzD1PBXa3m io5b5w/yPRvRZd8u6Co5MMoFZvV2veYEEuIQIklRuw0Ag5wIr4unCfoEo/dMcwBZ AWRizDxo6C95Y2mbCnXfw7Sx8oDWWH623qUcIJPW0llE/K0LyVvwM6LGdszcFUjD iHAWARg01p8xX/TUz4baAP5Rt0i1v4lUhAg/kR+x2kcVuxcMj6/b+jhZElF2KTPt LcQwhTovvz7WtspP4hmRY9i3DGydr/4LSl8+n6hHDbvs4gznsQwcD2sSJoN1cSzR XrAhMN2HyKw/ok4Sqen01oPJZL5XdzJVFFWLfAZZ/A1J9uk9UHAzBsyNXWfwK9KN /p9u2JN/uXgvmtj4uc8ECaFwyuK05NFS1Ax5PT649Ugein/shHIQCchVArVahtm8 xAt7J4WEPTB+KfUi+vK6yQ== =LCjE -----END PGP SIGNATURE----- Merge tag 'v6.1.0' into changes v6.1.0
This commit is contained in:
commit
29ac4fc084
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -1,6 +1,6 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## v6.1.0-rc.1
|
## v6.1.0
|
||||||
|
|
||||||
### IMPORTANT NOTES
|
### IMPORTANT NOTES
|
||||||
|
|
||||||
|
@ -11,6 +11,11 @@
|
||||||
* Views use a *Session ID* generated by the web browser instead of using the request IP (former behavior can be restored in YAML config)
|
* Views use a *Session ID* generated by the web browser instead of using the request IP (former behavior can be restored in YAML config)
|
||||||
* The goal of this change is to get closer to how other video platforms like Mux, Vimeo, or Instagram work
|
* The goal of this change is to get closer to how other video platforms like Mux, Vimeo, or Instagram work
|
||||||
|
|
||||||
|
### SECURITY
|
||||||
|
|
||||||
|
* Compact ActivityPub JSON-LD objects before using them to prevent incorrect access control @tesaguri
|
||||||
|
* Protect ActivityPub information related to private/internal/blocked videos
|
||||||
|
|
||||||
### Admin config (non-exhaustive)
|
### Admin config (non-exhaustive)
|
||||||
|
|
||||||
* **Breaking changes**:
|
* **Breaking changes**:
|
||||||
|
@ -127,6 +132,9 @@
|
||||||
* Fix view endpoint crash on geoip update failure
|
* Fix view endpoint crash on geoip update failure
|
||||||
* Fix setting video subtitle from URL query
|
* Fix setting video subtitle from URL query
|
||||||
* Fix selecting "Display all languages/categories/licences" in videos search resulting in an empty search
|
* Fix selecting "Display all languages/categories/licences" in videos search resulting in an empty search
|
||||||
|
* Fix followers/following counter of local ActivityPub actors
|
||||||
|
* Fix notification button link on mobile
|
||||||
|
* Fix player subtitles on iOS
|
||||||
|
|
||||||
|
|
||||||
## v6.0.4
|
## v6.0.4
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "peertube-client",
|
"name": "peertube-client",
|
||||||
"version": "6.1.0-rc.1",
|
"version": "6.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"author": {
|
"author": {
|
||||||
|
|
|
@ -216,7 +216,10 @@ export class VideoListComponent extends RestTable <Video> implements OnInit {
|
||||||
|
|
||||||
getFilesSize (video: Video) {
|
getFilesSize (video: Video) {
|
||||||
let total = getAllFiles(video).reduce((p, f) => p += f.size, 0)
|
let total = getAllFiles(video).reduce((p, f) => p += f.size, 0)
|
||||||
total += video.videoSource?.size || 0
|
|
||||||
|
if (video.videoSource?.fileDownloadUrl) {
|
||||||
|
total += video.videoSource.size || 0
|
||||||
|
}
|
||||||
|
|
||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
|
|
|
@ -309,7 +309,7 @@ export class VideoStatsComponent implements OnInit {
|
||||||
{
|
{
|
||||||
label: $localize`Views`,
|
label: $localize`Views`,
|
||||||
value: this.numberFormatter.transform(this.video.views),
|
value: this.numberFormatter.transform(this.video.views),
|
||||||
help: $localize`A view means that someone watched the video for at least 30 seconds`
|
help: $localize`A view means that someone watched the video for several seconds (10 seconds by default)`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: $localize`Likes`,
|
label: $localize`Likes`,
|
||||||
|
|
|
@ -1,21 +1,29 @@
|
||||||
import { Subject, Subscription } from 'rxjs'
|
|
||||||
import { filter } from 'rxjs/operators'
|
|
||||||
import { Component, EventEmitter, Output, OnDestroy, OnInit, ViewChild } from '@angular/core'
|
|
||||||
import { NavigationEnd, Router } from '@angular/router'
|
|
||||||
import { Notifier, PeerTubeSocket, ScreenService } from '@app/core'
|
|
||||||
import { NgbPopover, NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
|
|
||||||
import { UserNotificationsComponent } from '@app/shared/standalone-notifications/user-notifications.component'
|
|
||||||
import { GlobalIconComponent } from '@app/shared/shared-icons/global-icon.component'
|
|
||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
|
import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'
|
||||||
|
import { NavigationEnd, Router, RouterLink, RouterLinkActive } from '@angular/router'
|
||||||
|
import { Notifier, PeerTubeSocket, ScreenService } from '@app/core'
|
||||||
|
import { GlobalIconComponent } from '@app/shared/shared-icons/global-icon.component'
|
||||||
import { LoaderComponent } from '@app/shared/shared-main/loaders/loader.component'
|
import { LoaderComponent } from '@app/shared/shared-main/loaders/loader.component'
|
||||||
import { UserNotificationService } from '@app/shared/shared-main/users/user-notification.service'
|
import { UserNotificationService } from '@app/shared/shared-main/users/user-notification.service'
|
||||||
|
import { UserNotificationsComponent } from '@app/shared/standalone-notifications/user-notifications.component'
|
||||||
|
import { NgbPopover, NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { Subject, Subscription } from 'rxjs'
|
||||||
|
import { filter } from 'rxjs/operators'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-notification',
|
selector: 'my-notification',
|
||||||
templateUrl: './notification.component.html',
|
templateUrl: './notification.component.html',
|
||||||
styleUrls: [ './notification.component.scss' ],
|
styleUrls: [ './notification.component.scss' ],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [ CommonModule, NgbPopoverModule, UserNotificationsComponent, GlobalIconComponent, LoaderComponent ]
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
NgbPopoverModule,
|
||||||
|
UserNotificationsComponent,
|
||||||
|
GlobalIconComponent,
|
||||||
|
LoaderComponent,
|
||||||
|
RouterLink,
|
||||||
|
RouterLinkActive
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export class NotificationComponent implements OnInit, OnDestroy {
|
export class NotificationComponent implements OnInit, OnDestroy {
|
||||||
@ViewChild('popover', { static: true }) popover: NgbPopover
|
@ViewChild('popover', { static: true }) popover: NgbPopover
|
||||||
|
|
|
@ -361,7 +361,10 @@ export class PeerTubePlayer {
|
||||||
|
|
||||||
getVideojsOptions (): videojs.PlayerOptions {
|
getVideojsOptions (): videojs.PlayerOptions {
|
||||||
const html5 = {
|
const html5 = {
|
||||||
preloadTextTracks: false
|
preloadTextTracks: false,
|
||||||
|
// Prevent a bug on iOS where the text tracks added by peertube plugin are removed on play
|
||||||
|
// See https://github.com/Chocobozzz/PeerTube/issues/6351
|
||||||
|
nativeTextTracks: false
|
||||||
}
|
}
|
||||||
|
|
||||||
const plugins: VideoJSPluginOptions = {
|
const plugins: VideoJSPluginOptions = {
|
||||||
|
|
|
@ -83,7 +83,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<my-app role="main">
|
<my-app>
|
||||||
</my-app>
|
</my-app>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,4 @@
|
||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
<file source-language="en-US" datatype="plaintext" original="ng2.template" target-language="ro">
|
<file source-language="en-US" datatype="plaintext" original="ng2.template" target-language="ro">
|
||||||
<body>
|
<body>
|
||||||
|
@ -106,33 +106,33 @@
|
||||||
<context context-type="linenumber">5</context>
|
<context context-type="linenumber">5</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="ngb.pagination.first-aria" datatype="html">
|
<trans-unit id="ngb.pagination.first-aria" datatype="html" xml:space="preserve">
|
||||||
<source>First</source>
|
<source>First</source>
|
||||||
<target/>
|
<target state="translated">Primul</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/pagination/pagination.d.ts</context>
|
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/pagination/pagination.d.ts</context>
|
||||||
<context context-type="linenumber">14</context>
|
<context context-type="linenumber">14</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="ngb.pagination.previous-aria" datatype="html">
|
<trans-unit id="ngb.pagination.previous-aria" datatype="html" xml:space="preserve">
|
||||||
<source>Previous</source>
|
<source>Previous</source>
|
||||||
<target/>
|
<target state="translated">Anterior</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/pagination/pagination.d.ts</context>
|
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/pagination/pagination.d.ts</context>
|
||||||
<context context-type="linenumber">24</context>
|
<context context-type="linenumber">24</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="ngb.pagination.next-aria" datatype="html">
|
<trans-unit id="ngb.pagination.next-aria" datatype="html" xml:space="preserve">
|
||||||
<source>Next</source>
|
<source>Next</source>
|
||||||
<target/>
|
<target state="translated">Următorul</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/pagination/pagination.d.ts</context>
|
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/pagination/pagination.d.ts</context>
|
||||||
<context context-type="linenumber">44</context>
|
<context context-type="linenumber">44</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="ngb.pagination.last-aria" datatype="html">
|
<trans-unit id="ngb.pagination.last-aria" datatype="html" xml:space="preserve">
|
||||||
<source>Last</source>
|
<source>Last</source>
|
||||||
<target/>
|
<target state="translated">Ultimul</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/pagination/pagination.d.ts</context>
|
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/pagination/pagination.d.ts</context>
|
||||||
<context context-type="linenumber">53</context>
|
<context context-type="linenumber">53</context>
|
||||||
|
@ -146,9 +146,9 @@
|
||||||
<context context-type="linenumber">7</context>
|
<context context-type="linenumber">7</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="ngb.timepicker.increment-hours" datatype="html">
|
<trans-unit id="ngb.timepicker.increment-hours" datatype="html" xml:space="preserve">
|
||||||
<source>Increment hours</source>
|
<source>Increment hours</source>
|
||||||
<target/>
|
<target state="translated">Adaugă ore</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/timepicker/timepicker.d.ts</context>
|
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/timepicker/timepicker.d.ts</context>
|
||||||
<context context-type="linenumber">9</context>
|
<context context-type="linenumber">9</context>
|
||||||
|
@ -170,17 +170,17 @@
|
||||||
<context context-type="linenumber">15</context>
|
<context context-type="linenumber">15</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="ngb.timepicker.decrement-hours" datatype="html">
|
<trans-unit id="ngb.timepicker.decrement-hours" datatype="html" xml:space="preserve">
|
||||||
<source>Decrement hours</source>
|
<source>Decrement hours</source>
|
||||||
<target/>
|
<target state="translated">Scade ore</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/timepicker/timepicker.d.ts</context>
|
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/timepicker/timepicker.d.ts</context>
|
||||||
<context context-type="linenumber">23</context>
|
<context context-type="linenumber">23</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="ngb.timepicker.increment-minutes" datatype="html">
|
<trans-unit id="ngb.timepicker.increment-minutes" datatype="html" xml:space="preserve">
|
||||||
<source>Increment minutes</source>
|
<source>Increment minutes</source>
|
||||||
<target/>
|
<target state="translated">Adaugă minute</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/timepicker/timepicker.d.ts</context>
|
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/timepicker/timepicker.d.ts</context>
|
||||||
<context context-type="linenumber">32</context>
|
<context context-type="linenumber">32</context>
|
||||||
|
@ -202,17 +202,17 @@
|
||||||
<context context-type="linenumber">37</context>
|
<context context-type="linenumber">37</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="ngb.timepicker.decrement-minutes" datatype="html">
|
<trans-unit id="ngb.timepicker.decrement-minutes" datatype="html" xml:space="preserve">
|
||||||
<source>Decrement minutes</source>
|
<source>Decrement minutes</source>
|
||||||
<target/>
|
<target state="translated">Scade minute</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/timepicker/timepicker.d.ts</context>
|
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/timepicker/timepicker.d.ts</context>
|
||||||
<context context-type="linenumber">45</context>
|
<context context-type="linenumber">45</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="ngb.timepicker.increment-seconds" datatype="html">
|
<trans-unit id="ngb.timepicker.increment-seconds" datatype="html" xml:space="preserve">
|
||||||
<source>Increment seconds</source>
|
<source>Increment seconds</source>
|
||||||
<target/>
|
<target state="translated">Adaugă secunde</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/timepicker/timepicker.d.ts</context>
|
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/timepicker/timepicker.d.ts</context>
|
||||||
<context context-type="linenumber">54</context>
|
<context context-type="linenumber">54</context>
|
||||||
|
@ -234,9 +234,9 @@
|
||||||
<context context-type="linenumber">59</context>
|
<context context-type="linenumber">59</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="ngb.timepicker.decrement-seconds" datatype="html">
|
<trans-unit id="ngb.timepicker.decrement-seconds" datatype="html" xml:space="preserve">
|
||||||
<source>Decrement seconds</source>
|
<source>Decrement seconds</source>
|
||||||
<target/>
|
<target state="translated">Scade secunde</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/timepicker/timepicker.d.ts</context>
|
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/timepicker/timepicker.d.ts</context>
|
||||||
<context context-type="linenumber">67</context>
|
<context context-type="linenumber">67</context>
|
||||||
|
@ -298,17 +298,17 @@
|
||||||
<context context-type="linenumber">32</context>
|
<context context-type="linenumber">32</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="066903c4bc7d397c799979d64ce8c450792eb664" datatype="html">
|
<trans-unit id="066903c4bc7d397c799979d64ce8c450792eb664" datatype="html" xml:space="preserve">
|
||||||
<source>Your video <x id="START_LINK" ctype="x-a" equiv-text="<a>"/><x id="INTERPOLATION" equiv-text="{{ notification.videoBlacklist.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="</a>"/> has been blacklisted </source>
|
<source>Your video <x id="START_LINK" ctype="x-a" equiv-text="<a>"/><x id="INTERPOLATION" equiv-text="{{ notification.videoBlacklist.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="</a>"/> has been blacklisted </source>
|
||||||
<target/>
|
<target state="translated">Videoul tău <x id="START_LINK" ctype="x-a" equiv-text="<a>"/><x id="INTERPOLATION" equiv-text="{{ notification.videoBlacklist.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="</a>"/> a fost blocat. </target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/shared/users/user-notifications.component.html</context>
|
<context context-type="sourcefile">src/app/shared/users/user-notifications.component.html</context>
|
||||||
<context context-type="linenumber">40</context>
|
<context context-type="linenumber">40</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="b5e2152dd5b4222093fcec9c8289f12308a598e7" datatype="html">
|
<trans-unit id="b5e2152dd5b4222093fcec9c8289f12308a598e7" datatype="html" xml:space="preserve">
|
||||||
<source><x id="START_LINK" ctype="x-a" equiv-text="<a>"/>A new video abuse<x id="CLOSE_LINK" ctype="x-a" equiv-text="</a>"/> has been created on video <x id="START_LINK_1" ctype="x-a" equiv-text="<a>"/><x id="INTERPOLATION" equiv-text="{{ notification.videoAbuse.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="</a>"/> </source>
|
<source><x id="START_LINK" ctype="x-a" equiv-text="<a>"/>A new video abuse<x id="CLOSE_LINK" ctype="x-a" equiv-text="</a>"/> has been created on video <x id="START_LINK_1" ctype="x-a" equiv-text="<a>"/><x id="INTERPOLATION" equiv-text="{{ notification.videoAbuse.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="</a>"/> </source>
|
||||||
<target/>
|
<target state="translated"><x id="START_LINK" ctype="x-a" equiv-text="<a>"/>A new video abuse<x id="CLOSE_LINK" ctype="x-a" equiv-text="</a>"/> has been created on video <x id="START_LINK_1" ctype="x-a" equiv-text="<a>"/><x id="INTERPOLATION" equiv-text="{{ notification.videoAbuse.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="</a>"/> </target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/shared/users/user-notifications.component.html</context>
|
<context context-type="sourcefile">src/app/shared/users/user-notifications.component.html</context>
|
||||||
<context context-type="linenumber">48</context>
|
<context context-type="linenumber">48</context>
|
||||||
|
@ -438,9 +438,9 @@
|
||||||
<context context-type="linenumber">42</context>
|
<context context-type="linenumber">42</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1394835141143590910" datatype="html">
|
<trans-unit id="1394835141143590910" datatype="html" xml:space="preserve">
|
||||||
<source>Start at</source>
|
<source>Start at</source>
|
||||||
<target/>
|
<target state="translated">Începe la</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/shared/video-playlist/video-add-to-playlist.component.html</context>
|
<context context-type="sourcefile">src/app/shared/video-playlist/video-add-to-playlist.component.html</context>
|
||||||
<context context-type="linenumber">17</context>
|
<context context-type="linenumber">17</context>
|
||||||
|
@ -454,9 +454,9 @@
|
||||||
<context context-type="linenumber">75</context>
|
<context context-type="linenumber">75</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5964984095397511808" datatype="html">
|
<trans-unit id="5964984095397511808" datatype="html" xml:space="preserve">
|
||||||
<source>Stop at</source>
|
<source>Stop at</source>
|
||||||
<target/>
|
<target state="translated">Oprește la</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/shared/video-playlist/video-add-to-playlist.component.html</context>
|
<context context-type="sourcefile">src/app/shared/video-playlist/video-add-to-playlist.component.html</context>
|
||||||
<context context-type="linenumber">31</context>
|
<context context-type="linenumber">31</context>
|
||||||
|
@ -766,17 +766,17 @@
|
||||||
<context context-type="linenumber">26</context>
|
<context context-type="linenumber">26</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8558962068274430520" datatype="html">
|
<trans-unit id="8558962068274430520" datatype="html" xml:space="preserve">
|
||||||
<source>Unfederate the video</source>
|
<source>Unfederate the video</source>
|
||||||
<target/>
|
<target state="translated">Decuplați videoclipul</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/shared/video/modals/video-blacklist.component.html</context>
|
<context context-type="sourcefile">src/app/shared/video/modals/video-blacklist.component.html</context>
|
||||||
<context context-type="linenumber">23</context>
|
<context context-type="linenumber">23</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7539427273132299890" datatype="html">
|
<trans-unit id="7539427273132299890" datatype="html" xml:space="preserve">
|
||||||
<source>Unlisted</source>
|
<source>Unlisted</source>
|
||||||
<target/>
|
<target state="translated">Nelistat</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/shared/video/video-miniature.component.html</context>
|
<context context-type="sourcefile">src/app/shared/video/video-miniature.component.html</context>
|
||||||
<context context-type="linenumber">6</context>
|
<context context-type="linenumber">6</context>
|
||||||
|
@ -802,9 +802,9 @@
|
||||||
<context context-type="linenumber">7</context>
|
<context context-type="linenumber">7</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7688104409544625220" datatype="html">
|
<trans-unit id="7688104409544625220" datatype="html" xml:space="preserve">
|
||||||
<source>{VAR_PLURAL, plural, =1 {1 view} other {<x id="INTERPOLATION" equiv-text="{{ video.views | myNumberFormatter }}"/> views} }</source>
|
<source>{VAR_PLURAL, plural, =1 {1 view} other {<x id="INTERPOLATION" equiv-text="{{ video.views | myNumberFormatter }}"/> views} }</source>
|
||||||
<target/>
|
<target state="translated">{VAR_PLURAL, plural, =1 {1 view} other {<x id="INTERPOLATION" equiv-text="{{ video.views | myNumberFormatter }}"/> views} }</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/shared/video/video-miniature.component.html</context>
|
<context context-type="sourcefile">src/app/shared/video/video-miniature.component.html</context>
|
||||||
<context context-type="linenumber">23</context>
|
<context context-type="linenumber">23</context>
|
||||||
|
@ -818,9 +818,9 @@
|
||||||
<context context-type="linenumber">41</context>
|
<context context-type="linenumber">41</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3514509630940272440" datatype="html">
|
<trans-unit id="3514509630940272440" datatype="html" xml:space="preserve">
|
||||||
<source>Sensitive</source>
|
<source>Sensitive</source>
|
||||||
<target/>
|
<target state="translated">Sensitiv</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/shared/video/video-miniature.component.html</context>
|
<context context-type="sourcefile">src/app/shared/video/video-miniature.component.html</context>
|
||||||
<context context-type="linenumber">45</context>
|
<context context-type="linenumber">45</context>
|
||||||
|
@ -874,9 +874,9 @@
|
||||||
<context context-type="linenumber">100</context>
|
<context context-type="linenumber">100</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5263519165976128456" datatype="html">
|
<trans-unit id="5263519165976128456" datatype="html" xml:space="preserve">
|
||||||
<source>Edit starts/stops at</source>
|
<source>Edit starts/stops at</source>
|
||||||
<target/>
|
<target state="translated">Editarea începe/oprește la</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/shared/video-playlist/video-playlist-element-miniature.component.html</context>
|
<context context-type="sourcefile">src/app/shared/video-playlist/video-playlist-element-miniature.component.html</context>
|
||||||
<context context-type="linenumber">50</context>
|
<context context-type="linenumber">50</context>
|
||||||
|
@ -1182,9 +1182,9 @@
|
||||||
<context context-type="linenumber">17</context>
|
<context context-type="linenumber">17</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="c590f63488a5179f20a46930cf5e673f8136673d" datatype="html">
|
<trans-unit id="c590f63488a5179f20a46930cf5e673f8136673d" datatype="html" xml:space="preserve">
|
||||||
<source>You can interact with this via any ActivityPub-capable fediverse instance.<x id="LINE_BREAK" ctype="lb" equiv-text="<br/>"/><x id="LINE_BREAK" ctype="lb" equiv-text="<br/>"/> For instance with Mastodon or Pleroma you can type the current URL in the search box and interact with it there. </source>
|
<source>You can interact with this via any ActivityPub-capable fediverse instance.<x id="LINE_BREAK" ctype="lb" equiv-text="<br/>"/><x id="LINE_BREAK" ctype="lb" equiv-text="<br/>"/> For instance with Mastodon or Pleroma you can type the current URL in the search box and interact with it there. </source>
|
||||||
<target/>
|
<target state="translated">Poți interacționa cu acesta prin orice ActivityPub-compatibil instanță fediverse.<x id="LINE_BREAK" ctype="lb" equiv-text="<br/>"/><x id="LINE_BREAK" ctype="lb" equiv-text="<br/>"/>De exemplu cu Mastodon sau Pleroma poți scrie URL-ul curent în cutia de căutare și interacționa cu el acolo. </target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/shared/user-subscription/remote-subscribe.component.html</context>
|
<context context-type="sourcefile">src/app/shared/user-subscription/remote-subscribe.component.html</context>
|
||||||
<context context-type="linenumber">26</context>
|
<context context-type="linenumber">26</context>
|
||||||
|
@ -1198,9 +1198,9 @@
|
||||||
<context context-type="linenumber">5</context>
|
<context context-type="linenumber">5</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5975923297757530070" datatype="html">
|
<trans-unit id="5975923297757530070" datatype="html" xml:space="preserve">
|
||||||
<source><x id="START_TAG_DIV" ctype="x-div" equiv-text="<div>"/>Default NSFW/sensitive videos policy<x id="CLOSE_TAG_DIV" ctype="x-div" equiv-text="</div>"/> <x id="START_TAG_DIV_1" ctype="x-div" equiv-text="<div>"/>can be redefined by the users<x id="CLOSE_TAG_DIV" ctype="x-div" equiv-text="</div>"/> </source>
|
<source><x id="START_TAG_DIV" ctype="x-div" equiv-text="<div>"/>Default NSFW/sensitive videos policy<x id="CLOSE_TAG_DIV" ctype="x-div" equiv-text="</div>"/> <x id="START_TAG_DIV_1" ctype="x-div" equiv-text="<div>"/>can be redefined by the users<x id="CLOSE_TAG_DIV" ctype="x-div" equiv-text="</div>"/> </source>
|
||||||
<target/>
|
<target state="translated"><x id="START_TAG_DIV" ctype="x-div" equiv-text="<div>"/>Predefinit NSFW/sensitive videoclipuri regulamentului<x id="CLOSE_TAG_DIV" ctype="x-div" equiv-text="</div>"/><x id="START_TAG_DIV_1" ctype="x-div" equiv-text="<div>"/>poate fii redefinit de<x id="CLOSE_TAG_DIV" ctype="x-div" equiv-text="</div>"/>. </target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/shared/instance/instance-features-table.component.html</context>
|
<context context-type="sourcefile">src/app/shared/instance/instance-features-table.component.html</context>
|
||||||
<context context-type="linenumber">11</context>
|
<context context-type="linenumber">11</context>
|
||||||
|
@ -1266,9 +1266,9 @@
|
||||||
<context context-type="linenumber">139</context>
|
<context context-type="linenumber">139</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1502595455339510144" datatype="html">
|
<trans-unit id="1502595455339510144" datatype="html" xml:space="preserve">
|
||||||
<source>Unlimited <x id="START_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="<ng-container>"/>(<x id="INTERPOLATION" equiv-text="{{ dailyUserVideoQuota | bytes: 0 }}"/> per day)<x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="</ng-container>"/> </source>
|
<source>Unlimited <x id="START_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="<ng-container>"/>(<x id="INTERPOLATION" equiv-text="{{ dailyUserVideoQuota | bytes: 0 }}"/> per day)<x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="</ng-container>"/> </source>
|
||||||
<target/>
|
<target state="translated">Nelimitat<x id="START_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="<ng-container>"/><x id="INTERPOLATION" equiv-text="{{ dailyUserVideoQuota | bytes: 0 }}"/>pe zi<x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="</ng-container>"/>/ </target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/shared/instance/instance-features-table.component.html</context>
|
<context context-type="sourcefile">src/app/shared/instance/instance-features-table.component.html</context>
|
||||||
<context context-type="linenumber">59</context>
|
<context context-type="linenumber">59</context>
|
||||||
|
@ -1306,9 +1306,9 @@
|
||||||
<context context-type="linenumber">77</context>
|
<context context-type="linenumber">77</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7683705529753923369" datatype="html">
|
<trans-unit id="7683705529753923369" datatype="html" xml:space="preserve">
|
||||||
<source>Player</source>
|
<source>Player</source>
|
||||||
<target/>
|
<target state="translated">Jucător</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/shared/instance/instance-features-table.component.html</context>
|
<context context-type="sourcefile">src/app/shared/instance/instance-features-table.component.html</context>
|
||||||
<context context-type="linenumber">85</context>
|
<context context-type="linenumber">85</context>
|
||||||
|
@ -1334,9 +1334,9 @@
|
||||||
<context context-type="linenumber">7</context>
|
<context context-type="linenumber">7</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8630916846096019339" datatype="html">
|
<trans-unit id="8630916846096019339" datatype="html" xml:space="preserve">
|
||||||
<source>Users can resolve distant content</source>
|
<source>Users can resolve distant content</source>
|
||||||
<target/>
|
<target state="translated">Utilizatorii pot accesa conținutul de la distanță</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/shared/instance/instance-features-table.component.html</context>
|
<context context-type="sourcefile">src/app/shared/instance/instance-features-table.component.html</context>
|
||||||
<context context-type="linenumber">100</context>
|
<context context-type="linenumber">100</context>
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -4228,11 +4228,11 @@
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
||||||
<context context-type="linenumber">281</context>
|
<context context-type="linenumber">284</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
||||||
<context context-type="linenumber">336</context>
|
<context context-type="linenumber">339</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/+admin/system/runners/runner-job-list/runner-job-list.component.ts</context>
|
<context context-type="sourcefile">src/app/+admin/system/runners/runner-job-list/runner-job-list.component.ts</context>
|
||||||
|
@ -6444,91 +6444,91 @@
|
||||||
<source>Are you sure you want to delete this <x id="PH" equiv-text="file.resolution.label"/> file?</source>
|
<source>Are you sure you want to delete this <x id="PH" equiv-text="file.resolution.label"/> file?</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
||||||
<context context-type="linenumber">225</context>
|
<context context-type="linenumber">228</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6693349469471580292" datatype="html">
|
<trans-unit id="6693349469471580292" datatype="html">
|
||||||
<source>Delete file</source>
|
<source>Delete file</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
||||||
<context context-type="linenumber">226</context>
|
<context context-type="linenumber">229</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7016764388104297354" datatype="html">
|
<trans-unit id="7016764388104297354" datatype="html">
|
||||||
<source>File removed.</source>
|
<source>File removed.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
||||||
<context context-type="linenumber">232</context>
|
<context context-type="linenumber">235</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="925076027211452339" datatype="html">
|
<trans-unit id="925076027211452339" datatype="html">
|
||||||
<source>Are you sure you want to delete the original file of this video?</source>
|
<source>Are you sure you want to delete the original file of this video?</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
||||||
<context context-type="linenumber">241</context>
|
<context context-type="linenumber">244</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3014914668468316940" datatype="html">
|
<trans-unit id="3014914668468316940" datatype="html">
|
||||||
<source>Delete original file</source>
|
<source>Delete original file</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
||||||
<context context-type="linenumber">242</context>
|
<context context-type="linenumber">245</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6647462936549531405" datatype="html">
|
<trans-unit id="6647462936549531405" datatype="html">
|
||||||
<source>Original file removed.</source>
|
<source>Original file removed.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
||||||
<context context-type="linenumber">248</context>
|
<context context-type="linenumber">251</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1314383205093440631" datatype="html">
|
<trans-unit id="1314383205093440631" datatype="html">
|
||||||
<source>Are you sure you want to delete {count, plural, =1 {this video} other {these <x id="count"/> videos}}?</source>
|
<source>Are you sure you want to delete {count, plural, =1 {this video} other {these <x id="count"/> videos}}?</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
||||||
<context context-type="linenumber">277</context>
|
<context context-type="linenumber">280</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5779580280418408097" datatype="html">
|
<trans-unit id="5779580280418408097" datatype="html">
|
||||||
<source>Deleted {count, plural, =1 {1 video} other {<x id="count"/> videos}}.</source>
|
<source>Deleted {count, plural, =1 {1 video} other {<x id="count"/> videos}}.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
||||||
<context context-type="linenumber">289</context>
|
<context context-type="linenumber">292</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="9164541937317586242" datatype="html">
|
<trans-unit id="9164541937317586242" datatype="html">
|
||||||
<source>Unblocked {count, plural, =1 {1 video} other {<x id="count"/> videos}}.</source>
|
<source>Unblocked {count, plural, =1 {1 video} other {<x id="count"/> videos}}.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
||||||
<context context-type="linenumber">307</context>
|
<context context-type="linenumber">310</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6228449077605046873" datatype="html">
|
<trans-unit id="6228449077605046873" datatype="html">
|
||||||
<source>Are you sure you want to delete {count, plural, =1 {1 HLS streaming playlist} other {<x id="count"/> HLS streaming playlists}}?</source>
|
<source>Are you sure you want to delete {count, plural, =1 {1 HLS streaming playlist} other {<x id="count"/> HLS streaming playlists}}?</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
||||||
<context context-type="linenumber">325</context>
|
<context context-type="linenumber">328</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4435640428611044716" datatype="html">
|
<trans-unit id="4435640428611044716" datatype="html">
|
||||||
<source>Are you sure you want to delete Web Video files of {count, plural, =1 {1 video} other {<x id="count"/> videos}}?</source>
|
<source>Are you sure you want to delete Web Video files of {count, plural, =1 {1 video} other {<x id="count"/> videos}}?</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
||||||
<context context-type="linenumber">331</context>
|
<context context-type="linenumber">334</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1571742433738679426" datatype="html">
|
<trans-unit id="1571742433738679426" datatype="html">
|
||||||
<source>Files were removed.</source>
|
<source>Files were removed.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
||||||
<context context-type="linenumber">342</context>
|
<context context-type="linenumber">345</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7054344823477412274" datatype="html">
|
<trans-unit id="7054344823477412274" datatype="html">
|
||||||
<source>Transcoding jobs created.</source>
|
<source>Transcoding jobs created.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
<context context-type="sourcefile">src/app/+admin/overview/videos/video-list.component.ts</context>
|
||||||
<context context-type="linenumber">354</context>
|
<context context-type="linenumber">357</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2591467977473302125" datatype="html">
|
<trans-unit id="2591467977473302125" datatype="html">
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -14,51 +14,51 @@
|
||||||
"Copy magnet URI": "Copiar lo magnet URI",
|
"Copy magnet URI": "Copiar lo magnet URI",
|
||||||
"Total downloaded: ": "Total telecargat : ",
|
"Total downloaded: ": "Total telecargat : ",
|
||||||
"Total uploaded: ": "Total enviat : ",
|
"Total uploaded: ": "Total enviat : ",
|
||||||
"From servers: ": "From servers: ",
|
"From servers: ": "Dels servidors : ",
|
||||||
"From peers: ": "From peers: ",
|
"From peers: ": "Dels pars : ",
|
||||||
"Normal mode": "Normal mode",
|
"Normal mode": "Normal mode",
|
||||||
"Stats for nerds": "Stats for nerds",
|
"Stats for nerds": "Estatisticas pels nerds",
|
||||||
"Theater mode": "Theater mode",
|
"Theater mode": "Mòde cinèma",
|
||||||
"Video UUID": "Video UUID",
|
"Video UUID": "UUID de la vidèo",
|
||||||
"Viewport / Frames": "Viewport / Frames",
|
"Viewport / Frames": "Fenèstra d’afichatge / Quadres",
|
||||||
"Resolution": "Resolution",
|
"Resolution": "Resolucion",
|
||||||
"Volume": "Volume",
|
"Volume": "Volume",
|
||||||
"Codecs": "Codecs",
|
"Codecs": "Codecs",
|
||||||
"Color": "Color",
|
"Color": "Color",
|
||||||
"Go back to the live": "Go back to the live",
|
"Go back to the live": "Tornar al dirèct",
|
||||||
"Connection Speed": "Connection Speed",
|
"Connection Speed": "Velocitat de la connexion",
|
||||||
"Network Activity": "Network Activity",
|
"Network Activity": "Activitat ret",
|
||||||
"Total Transfered": "Total Transfered",
|
"Total Transfered": "Total Transferit",
|
||||||
"Download Breakdown": "Download Breakdown",
|
"Download Breakdown": "Reparticion dels telecargaments",
|
||||||
"Buffer Progress": "Buffer Progress",
|
"Buffer Progress": "Buffer Progress",
|
||||||
"Buffer State": "Buffer State",
|
"Buffer State": "Estat memòria tampon",
|
||||||
"Live Latency": "Live Latency",
|
"Live Latency": "Laténcia en viu",
|
||||||
"P2P": "P2P",
|
"P2P": "P2P",
|
||||||
"{1} seconds": "{1} seconds",
|
"{1} seconds": "{1} segondas",
|
||||||
"enabled": "enabled",
|
"enabled": "activat",
|
||||||
"Playlist: {1}": "Playlist: {1}",
|
"Playlist: {1}": "Playlist: {1}",
|
||||||
"disabled": "disabled",
|
"disabled": "desactivat",
|
||||||
" off": " desactivats",
|
" off": " desactivats",
|
||||||
"Player mode": "Player mode",
|
"Player mode": "Player mode",
|
||||||
"Play in loop": "Play in loop",
|
"Play in loop": "Lector en bocla",
|
||||||
"This live has not started yet.": "This live has not started yet.",
|
"This live has not started yet.": "Lo dirècte a pas encara començat.",
|
||||||
"This live has ended.": "This live has ended.",
|
"This live has ended.": "Lo dirèct es acabat.",
|
||||||
"The video failed to play, will try to fast forward.": "The video failed to play, will try to fast forward.",
|
"The video failed to play, will try to fast forward.": "The video failed to play, will try to fast forward.",
|
||||||
"{1} / {2} dropped of {3}": "{1} / {2} dropped of {3}",
|
"{1} / {2} dropped of {3}": "{1} / {2} dropped of {3}",
|
||||||
" (muted)": " (muted)",
|
" (muted)": " (mut)",
|
||||||
"{1} from servers · {2} from peers": "{1} from servers · {2} from peers",
|
"{1} from servers · {2} from peers": "{1} dels servidors · {2} dels pars",
|
||||||
"Previous video": "Previous video",
|
"Previous video": "Vidèo precedenta",
|
||||||
"Video page (new window)": "Video page (new window)",
|
"Video page (new window)": "Pagina vidèo (fenèstra novèla)",
|
||||||
"Next video": "Next video",
|
"Next video": "Next video",
|
||||||
"This video is password protected": "This video is password protected",
|
"This video is password protected": "Un senhal protegís aquesta vidèo",
|
||||||
"You need a password to watch this video.": "You need a password to watch this video.",
|
"You need a password to watch this video.": "Devètz fornir un senhal per agachar aquesta vidèo.",
|
||||||
"Incorrect password, please enter a correct password": "Incorrect password, please enter a correct password",
|
"Incorrect password, please enter a correct password": "Senhal incorrècte, mercés de picar un senhal corrècte",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "Anullar",
|
||||||
"Up Next": "Up Next",
|
"Up Next": "Up Next",
|
||||||
"Autoplay is suspended": "Autoplay is suspended",
|
"Autoplay is suspended": "La lectura automatica es suspenduda",
|
||||||
"{1} (from edge: {2})": "{1} (from edge: {2})",
|
"{1} (from edge: {2})": "{1} (from edge: {2})",
|
||||||
"Disable subtitles": "Disable subtitles",
|
"Disable subtitles": "Desactivar los sostítols",
|
||||||
"Enable {1} subtitle": "Enable {1} subtitle",
|
"Enable {1} subtitle": "Activar {1} sostítol",
|
||||||
"Audio Player": "Lector àudio",
|
"Audio Player": "Lector àudio",
|
||||||
"Video Player": "Lector vidèo",
|
"Video Player": "Lector vidèo",
|
||||||
"Play": "Lectura",
|
"Play": "Lectura",
|
||||||
|
|
|
@ -272,5 +272,5 @@
|
||||||
"Traditional Chinese": "Traditional Chinese",
|
"Traditional Chinese": "Traditional Chinese",
|
||||||
"Misc": "Divèrs",
|
"Misc": "Divèrs",
|
||||||
"Normal mode": "Normal mode",
|
"Normal mode": "Normal mode",
|
||||||
"Theater mode": "Theater mode"
|
"Theater mode": "Mòde cinèma"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "peertube",
|
"name": "peertube",
|
||||||
"description": "PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.",
|
"description": "PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.",
|
||||||
"version": "6.1.0-rc.1",
|
"version": "6.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"licence": "AGPL-3.0",
|
"licence": "AGPL-3.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -237,7 +237,7 @@
|
||||||
"pngjs": "^7.0.0",
|
"pngjs": "^7.0.0",
|
||||||
"proxy": "^2.1.1",
|
"proxy": "^2.1.1",
|
||||||
"socket.io-client": "^4.5.4",
|
"socket.io-client": "^4.5.4",
|
||||||
"supertest": "^6.0.1",
|
"supertest": "^7.0.0",
|
||||||
"swagger-cli": "^4.0.2",
|
"swagger-cli": "^4.0.2",
|
||||||
"tsc-watch": "^6.0.0",
|
"tsc-watch": "^6.0.0",
|
||||||
"tsx": "^4.7.1",
|
"tsx": "^4.7.1",
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 49 KiB |
Binary file not shown.
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
|
@ -3,12 +3,12 @@
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { FIXTURE_URLS } from '@tests/shared/fixture-urls.js'
|
import { FIXTURE_URLS } from '@tests/shared/fixture-urls.js'
|
||||||
import { sortObjectComparator } from '@peertube/peertube-core-utils'
|
import { sortObjectComparator } from '@peertube/peertube-core-utils'
|
||||||
import { UserAdminFlag, UserRole, VideoBlacklist, VideoBlacklistType } from '@peertube/peertube-models'
|
import { HttpStatusCode, UserAdminFlag, UserRole, VideoBlacklist, VideoBlacklistType } from '@peertube/peertube-models'
|
||||||
import {
|
import {
|
||||||
BlacklistCommand,
|
BlacklistCommand,
|
||||||
cleanupTests,
|
cleanupTests,
|
||||||
createMultipleServers,
|
createMultipleServers,
|
||||||
doubleFollow, PeerTubeServer,
|
doubleFollow, makeActivityPubGetRequest, PeerTubeServer,
|
||||||
setAccessTokensToServers,
|
setAccessTokensToServers,
|
||||||
setDefaultChannelAvatar,
|
setDefaultChannelAvatar,
|
||||||
waitJobs
|
waitJobs
|
||||||
|
@ -298,6 +298,13 @@ describe('Test video blacklist', function () {
|
||||||
expect(video4Blacklisted.unfederated).to.be.true
|
expect(video4Blacklisted.unfederated).to.be.true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should not have AP comments/announces/likes/dislikes', async function () {
|
||||||
|
await makeActivityPubGetRequest(servers[0].url, `/videos/watch/${video3UUID}/comments`, HttpStatusCode.UNAUTHORIZED_401)
|
||||||
|
await makeActivityPubGetRequest(servers[0].url, `/videos/watch/${video3UUID}/announces`, HttpStatusCode.UNAUTHORIZED_401)
|
||||||
|
await makeActivityPubGetRequest(servers[0].url, `/videos/watch/${video3UUID}/likes`, HttpStatusCode.UNAUTHORIZED_401)
|
||||||
|
await makeActivityPubGetRequest(servers[0].url, `/videos/watch/${video3UUID}/dislikes`, HttpStatusCode.UNAUTHORIZED_401)
|
||||||
|
})
|
||||||
|
|
||||||
it('Should remove the video from blacklist and refederate the video', async function () {
|
it('Should remove the video from blacklist and refederate the video', async function () {
|
||||||
await command.remove({ videoId: video4UUID })
|
await command.remove({ videoId: video4UUID })
|
||||||
|
|
||||||
|
|
|
@ -165,15 +165,17 @@ describe('Test video source management', function () {
|
||||||
expect(data[1].videoSource.fileDownloadUrl).to.exist
|
expect(data[1].videoSource.fileDownloadUrl).to.exist
|
||||||
|
|
||||||
expect(data[2].videoSource).to.exist
|
expect(data[2].videoSource).to.exist
|
||||||
|
|
||||||
expect(data[2].videoSource.fileDownloadUrl).to.not.exist
|
expect(data[2].videoSource.fileDownloadUrl).to.not.exist
|
||||||
|
|
||||||
expect(data[2].videoSource.createdAt).to.exist
|
expect(data[2].videoSource.createdAt).to.exist
|
||||||
expect(data[2].videoSource.fps).to.be.null
|
expect(data[2].videoSource.fps).to.to.exist
|
||||||
expect(data[2].videoSource.height).to.be.null
|
expect(data[2].videoSource.height).to.to.exist
|
||||||
expect(data[2].videoSource.width).to.be.null
|
expect(data[2].videoSource.width).to.to.exist
|
||||||
expect(data[2].videoSource.resolution.id).to.be.null
|
expect(data[2].videoSource.resolution.id).to.to.exist
|
||||||
expect(data[2].videoSource.resolution.label).to.be.null
|
expect(data[2].videoSource.resolution.label).to.to.exist
|
||||||
expect(data[2].videoSource.size).to.be.null
|
expect(data[2].videoSource.size).to.to.exist
|
||||||
expect(data[2].videoSource.metadata).to.be.null
|
expect(data[2].videoSource.metadata).to.to.exist
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should delete all videos and do not have original files anymore', async function () {
|
it('Should delete all videos and do not have original files anymore', async function () {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
|
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
|
||||||
import { signAndContextify } from '@peertube/peertube-server/core/helpers/activity-pub-utils.js'
|
import { signAndContextify } from '@peertube/peertube-server/core/helpers/activity-pub-utils.js'
|
||||||
import { isHTTPSignatureVerified, parseHTTPSignature } from '@peertube/peertube-server/core/helpers/peertube-crypto.js'
|
import { isHTTPSignatureVerified, parseHTTPSignature } from '@peertube/peertube-server/core/helpers/peertube-crypto.js'
|
||||||
import { isJsonLDSignatureVerified, signJsonLDObject } from '@peertube/peertube-server/core/helpers/peertube-jsonld.js'
|
import { compactJSONLDAndCheckSignature, signJsonLDObject } from '@peertube/peertube-server/core/helpers/peertube-jsonld.js'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { readJsonSync } from 'fs-extra/esm'
|
import { readJsonSync } from 'fs-extra/esm'
|
||||||
import cloneDeep from 'lodash-es/cloneDeep.js'
|
import cloneDeep from 'lodash-es/cloneDeep.js'
|
||||||
|
@ -24,6 +24,10 @@ function fakeFilter () {
|
||||||
return (data: any) => Promise.resolve(data)
|
return (data: any) => Promise.resolve(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fakeExpressReq (body: any) {
|
||||||
|
return { body }
|
||||||
|
}
|
||||||
|
|
||||||
describe('Test activity pub helpers', function () {
|
describe('Test activity pub helpers', function () {
|
||||||
|
|
||||||
describe('When checking the Linked Signature', function () {
|
describe('When checking the Linked Signature', function () {
|
||||||
|
@ -33,7 +37,7 @@ describe('Test activity pub helpers', function () {
|
||||||
const publicKey = readJsonSync(buildAbsoluteFixturePath('./ap-json/mastodon/public-key.json')).publicKey
|
const publicKey = readJsonSync(buildAbsoluteFixturePath('./ap-json/mastodon/public-key.json')).publicKey
|
||||||
const fromActor = { publicKey, url: 'http://localhost:9002/accounts/peertube' }
|
const fromActor = { publicKey, url: 'http://localhost:9002/accounts/peertube' }
|
||||||
|
|
||||||
const result = await isJsonLDSignatureVerified(fromActor as any, body)
|
const result = await compactJSONLDAndCheckSignature(fromActor as any, fakeExpressReq(body))
|
||||||
|
|
||||||
expect(result).to.be.false
|
expect(result).to.be.false
|
||||||
})
|
})
|
||||||
|
@ -43,7 +47,7 @@ describe('Test activity pub helpers', function () {
|
||||||
const publicKey = readJsonSync(buildAbsoluteFixturePath('./ap-json/mastodon/bad-public-key.json')).publicKey
|
const publicKey = readJsonSync(buildAbsoluteFixturePath('./ap-json/mastodon/bad-public-key.json')).publicKey
|
||||||
const fromActor = { publicKey, url: 'http://localhost:9002/accounts/peertube' }
|
const fromActor = { publicKey, url: 'http://localhost:9002/accounts/peertube' }
|
||||||
|
|
||||||
const result = await isJsonLDSignatureVerified(fromActor as any, body)
|
const result = await compactJSONLDAndCheckSignature(fromActor as any, fakeExpressReq(body))
|
||||||
|
|
||||||
expect(result).to.be.false
|
expect(result).to.be.false
|
||||||
})
|
})
|
||||||
|
@ -53,7 +57,7 @@ describe('Test activity pub helpers', function () {
|
||||||
const publicKey = readJsonSync(buildAbsoluteFixturePath('./ap-json/mastodon/public-key.json')).publicKey
|
const publicKey = readJsonSync(buildAbsoluteFixturePath('./ap-json/mastodon/public-key.json')).publicKey
|
||||||
const fromActor = { publicKey, url: 'http://localhost:9002/accounts/peertube' }
|
const fromActor = { publicKey, url: 'http://localhost:9002/accounts/peertube' }
|
||||||
|
|
||||||
const result = await isJsonLDSignatureVerified(fromActor as any, body)
|
const result = await compactJSONLDAndCheckSignature(fromActor as any, fakeExpressReq(body))
|
||||||
|
|
||||||
expect(result).to.be.true
|
expect(result).to.be.true
|
||||||
})
|
})
|
||||||
|
@ -72,7 +76,7 @@ describe('Test activity pub helpers', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9002/accounts/peertube' }
|
const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9002/accounts/peertube' }
|
||||||
const result = await isJsonLDSignatureVerified(fromActor as any, signedBody)
|
const result = await compactJSONLDAndCheckSignature(fromActor as any, fakeExpressReq(signedBody))
|
||||||
|
|
||||||
expect(result).to.be.false
|
expect(result).to.be.false
|
||||||
})
|
})
|
||||||
|
@ -91,7 +95,7 @@ describe('Test activity pub helpers', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9002/accounts/peertube' }
|
const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9002/accounts/peertube' }
|
||||||
const result = await isJsonLDSignatureVerified(fromActor as any, signedBody)
|
const result = await compactJSONLDAndCheckSignature(fromActor as any, fakeExpressReq(signedBody))
|
||||||
|
|
||||||
expect(result).to.be.true
|
expect(result).to.be.true
|
||||||
})
|
})
|
||||||
|
|
|
@ -120,7 +120,7 @@ activityPubClientRouter.get('/videos/watch/:id/activity',
|
||||||
activityPubClientRouter.get('/videos/watch/:id/announces',
|
activityPubClientRouter.get('/videos/watch/:id/announces',
|
||||||
executeIfActivityPub,
|
executeIfActivityPub,
|
||||||
activityPubRateLimiter,
|
activityPubRateLimiter,
|
||||||
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
|
asyncMiddleware(videosCustomGetValidator('only-video-and-blacklist')),
|
||||||
asyncMiddleware(videoAnnouncesController)
|
asyncMiddleware(videoAnnouncesController)
|
||||||
)
|
)
|
||||||
activityPubClientRouter.get('/videos/watch/:id/announces/:actorId',
|
activityPubClientRouter.get('/videos/watch/:id/announces/:actorId',
|
||||||
|
@ -132,19 +132,19 @@ activityPubClientRouter.get('/videos/watch/:id/announces/:actorId',
|
||||||
activityPubClientRouter.get('/videos/watch/:id/likes',
|
activityPubClientRouter.get('/videos/watch/:id/likes',
|
||||||
executeIfActivityPub,
|
executeIfActivityPub,
|
||||||
activityPubRateLimiter,
|
activityPubRateLimiter,
|
||||||
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
|
asyncMiddleware(videosCustomGetValidator('only-video-and-blacklist')),
|
||||||
asyncMiddleware(videoLikesController)
|
asyncMiddleware(videoLikesController)
|
||||||
)
|
)
|
||||||
activityPubClientRouter.get('/videos/watch/:id/dislikes',
|
activityPubClientRouter.get('/videos/watch/:id/dislikes',
|
||||||
executeIfActivityPub,
|
executeIfActivityPub,
|
||||||
activityPubRateLimiter,
|
activityPubRateLimiter,
|
||||||
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
|
asyncMiddleware(videosCustomGetValidator('only-video-and-blacklist')),
|
||||||
asyncMiddleware(videoDislikesController)
|
asyncMiddleware(videoDislikesController)
|
||||||
)
|
)
|
||||||
activityPubClientRouter.get('/videos/watch/:id/comments',
|
activityPubClientRouter.get('/videos/watch/:id/comments',
|
||||||
executeIfActivityPub,
|
executeIfActivityPub,
|
||||||
activityPubRateLimiter,
|
activityPubRateLimiter,
|
||||||
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
|
asyncMiddleware(videosCustomGetValidator('only-video-and-blacklist')),
|
||||||
asyncMiddleware(videoCommentsController)
|
asyncMiddleware(videoCommentsController)
|
||||||
)
|
)
|
||||||
activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId',
|
activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId',
|
||||||
|
@ -175,7 +175,7 @@ activityPubClientRouter.get('/videos/watch/:id/chapters',
|
||||||
activityPubRateLimiter,
|
activityPubRateLimiter,
|
||||||
apVideoChaptersSetCacheKey,
|
apVideoChaptersSetCacheKey,
|
||||||
chaptersCacheRouteMiddleware(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS),
|
chaptersCacheRouteMiddleware(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS),
|
||||||
asyncMiddleware(videosCustomGetValidator('only-video')),
|
asyncMiddleware(videosCustomGetValidator('only-video-and-blacklist')),
|
||||||
asyncMiddleware(videoChaptersController)
|
asyncMiddleware(videoChaptersController)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -330,7 +330,7 @@ async function videoAnnounceController (req: express.Request, res: express.Respo
|
||||||
}
|
}
|
||||||
|
|
||||||
async function videoAnnouncesController (req: express.Request, res: express.Response) {
|
async function videoAnnouncesController (req: express.Request, res: express.Response) {
|
||||||
const video = res.locals.onlyImmutableVideo
|
const video = res.locals.onlyVideo
|
||||||
|
|
||||||
if (redirectIfNotOwned(video.url, res)) return
|
if (redirectIfNotOwned(video.url, res)) return
|
||||||
|
|
||||||
|
@ -347,7 +347,7 @@ async function videoAnnouncesController (req: express.Request, res: express.Resp
|
||||||
}
|
}
|
||||||
|
|
||||||
async function videoLikesController (req: express.Request, res: express.Response) {
|
async function videoLikesController (req: express.Request, res: express.Response) {
|
||||||
const video = res.locals.onlyImmutableVideo
|
const video = res.locals.onlyVideo
|
||||||
|
|
||||||
if (redirectIfNotOwned(video.url, res)) return
|
if (redirectIfNotOwned(video.url, res)) return
|
||||||
|
|
||||||
|
@ -357,7 +357,7 @@ async function videoLikesController (req: express.Request, res: express.Response
|
||||||
}
|
}
|
||||||
|
|
||||||
async function videoDislikesController (req: express.Request, res: express.Response) {
|
async function videoDislikesController (req: express.Request, res: express.Response) {
|
||||||
const video = res.locals.onlyImmutableVideo
|
const video = res.locals.onlyVideo
|
||||||
|
|
||||||
if (redirectIfNotOwned(video.url, res)) return
|
if (redirectIfNotOwned(video.url, res)) return
|
||||||
|
|
||||||
|
@ -367,7 +367,7 @@ async function videoDislikesController (req: express.Request, res: express.Respo
|
||||||
}
|
}
|
||||||
|
|
||||||
async function videoCommentsController (req: express.Request, res: express.Response) {
|
async function videoCommentsController (req: express.Request, res: express.Response) {
|
||||||
const video = res.locals.onlyImmutableVideo
|
const video = res.locals.onlyVideo
|
||||||
|
|
||||||
if (redirectIfNotOwned(video.url, res)) return
|
if (redirectIfNotOwned(video.url, res)) return
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { replaceChapters } from '@server/lib/video-chapters.js'
|
||||||
const videoChaptersRouter = express.Router()
|
const videoChaptersRouter = express.Router()
|
||||||
|
|
||||||
videoChaptersRouter.get('/:id/chapters',
|
videoChaptersRouter.get('/:id/chapters',
|
||||||
asyncMiddleware(videosCustomGetValidator('only-video')),
|
asyncMiddleware(videosCustomGetValidator('only-video-and-blacklist')),
|
||||||
asyncMiddleware(listVideoChapters)
|
asyncMiddleware(listVideoChapters)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import express from 'express'
|
import { HttpStatusCode, VideoChangeOwnershipStatus } from '@peertube/peertube-models'
|
||||||
import { HttpStatusCode, VideoChangeOwnershipStatus, VideoState } from '@peertube/peertube-models'
|
import { canVideoBeFederated } from '@server/lib/activitypub/videos/federate.js'
|
||||||
import { MVideoFullLight } from '@server/types/models/index.js'
|
import { MVideoFullLight } from '@server/types/models/index.js'
|
||||||
|
import express from 'express'
|
||||||
import { logger } from '../../../helpers/logger.js'
|
import { logger } from '../../../helpers/logger.js'
|
||||||
import { getFormattedObjects } from '../../../helpers/utils.js'
|
import { getFormattedObjects } from '../../../helpers/utils.js'
|
||||||
import { sequelizeTypescript } from '../../../initializers/database.js'
|
import { sequelizeTypescript } from '../../../initializers/database.js'
|
||||||
|
@ -113,7 +114,7 @@ function acceptOwnership (req: express.Request, res: express.Response) {
|
||||||
const targetVideoUpdated = await targetVideo.save({ transaction: t }) as MVideoFullLight
|
const targetVideoUpdated = await targetVideo.save({ transaction: t }) as MVideoFullLight
|
||||||
targetVideoUpdated.VideoChannel = channel
|
targetVideoUpdated.VideoChannel = channel
|
||||||
|
|
||||||
if (targetVideoUpdated.hasPrivacyForFederation() && targetVideoUpdated.state === VideoState.PUBLISHED) {
|
if (canVideoBeFederated(targetVideoUpdated)) {
|
||||||
await changeVideoChannelShare(targetVideoUpdated, oldVideoChannel, t)
|
await changeVideoChannelShare(targetVideoUpdated, oldVideoChannel, t)
|
||||||
await sendUpdateVideo(targetVideoUpdated, t, oldVideoChannel.Account.Actor)
|
await sendUpdateVideo(targetVideoUpdated, t, oldVideoChannel.Account.Actor)
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,12 +68,8 @@ async function deleteVideoLatestSourceFile (req: express.Request, res: express.R
|
||||||
await video.removeOriginalFile(videoSource)
|
await video.removeOriginalFile(videoSource)
|
||||||
|
|
||||||
videoSource.keptOriginalFilename = null
|
videoSource.keptOriginalFilename = null
|
||||||
videoSource.fps = null
|
videoSource.storage = null
|
||||||
videoSource.resolution = null
|
|
||||||
videoSource.width = null
|
|
||||||
videoSource.height = null
|
|
||||||
videoSource.metadata = null
|
|
||||||
videoSource.size = null
|
|
||||||
await videoSource.save()
|
await videoSource.save()
|
||||||
|
|
||||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||||
|
|
|
@ -7,7 +7,7 @@ const tokenRouter = express.Router()
|
||||||
|
|
||||||
tokenRouter.post('/:id/token',
|
tokenRouter.post('/:id/token',
|
||||||
optionalAuthenticate,
|
optionalAuthenticate,
|
||||||
asyncMiddleware(videosCustomGetValidator('only-video')),
|
asyncMiddleware(videosCustomGetValidator('only-video-and-blacklist')),
|
||||||
videoFileTokenValidator,
|
videoFileTokenValidator,
|
||||||
generateToken
|
generateToken
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import express, { UploadFiles } from 'express'
|
|
||||||
import { Transaction } from 'sequelize'
|
|
||||||
import { forceNumber } from '@peertube/peertube-core-utils'
|
import { forceNumber } from '@peertube/peertube-core-utils'
|
||||||
import { HttpStatusCode, ThumbnailType, VideoPrivacy, VideoPrivacyType, VideoUpdate } from '@peertube/peertube-models'
|
import { HttpStatusCode, ThumbnailType, VideoPrivacy, VideoPrivacyType, VideoUpdate } from '@peertube/peertube-models'
|
||||||
import { exists } from '@server/helpers/custom-validators/misc.js'
|
import { exists } from '@server/helpers/custom-validators/misc.js'
|
||||||
import { changeVideoChannelShare } from '@server/lib/activitypub/share.js'
|
import { changeVideoChannelShare } from '@server/lib/activitypub/share.js'
|
||||||
|
import { isNewVideoPrivacyForFederation, isPrivacyForFederation } from '@server/lib/activitypub/videos/federate.js'
|
||||||
|
import { updateLocalVideoMiniatureFromExisting } from '@server/lib/thumbnail.js'
|
||||||
|
import { replaceChaptersFromDescriptionIfNeeded } from '@server/lib/video-chapters.js'
|
||||||
|
import { addVideoJobsAfterUpdate } from '@server/lib/video-jobs.js'
|
||||||
import { VideoPathManager } from '@server/lib/video-path-manager.js'
|
import { VideoPathManager } from '@server/lib/video-path-manager.js'
|
||||||
import { setVideoPrivacy } from '@server/lib/video-privacy.js'
|
import { setVideoPrivacy } from '@server/lib/video-privacy.js'
|
||||||
import { setVideoTags } from '@server/lib/video.js'
|
import { setVideoTags } from '@server/lib/video.js'
|
||||||
|
@ -11,7 +13,9 @@ import { openapiOperationDoc } from '@server/middlewares/doc.js'
|
||||||
import { VideoPasswordModel } from '@server/models/video/video-password.js'
|
import { VideoPasswordModel } from '@server/models/video/video-password.js'
|
||||||
import { FilteredModelAttributes } from '@server/types/index.js'
|
import { FilteredModelAttributes } from '@server/types/index.js'
|
||||||
import { MVideoFullLight, MVideoThumbnail } from '@server/types/models/index.js'
|
import { MVideoFullLight, MVideoThumbnail } from '@server/types/models/index.js'
|
||||||
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger.js'
|
import express, { UploadFiles } from 'express'
|
||||||
|
import { Transaction } from 'sequelize'
|
||||||
|
import { VideoAuditView, auditLoggerFactory, getAuditIdFromRes } from '../../../helpers/audit-logger.js'
|
||||||
import { resetSequelizeInstance } from '../../../helpers/database-utils.js'
|
import { resetSequelizeInstance } from '../../../helpers/database-utils.js'
|
||||||
import { createReqFiles } from '../../../helpers/express-utils.js'
|
import { createReqFiles } from '../../../helpers/express-utils.js'
|
||||||
import { logger, loggerTagsFactory } from '../../../helpers/logger.js'
|
import { logger, loggerTagsFactory } from '../../../helpers/logger.js'
|
||||||
|
@ -22,9 +26,6 @@ import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist.js'
|
||||||
import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videosUpdateValidator } from '../../../middlewares/index.js'
|
import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videosUpdateValidator } from '../../../middlewares/index.js'
|
||||||
import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update.js'
|
import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update.js'
|
||||||
import { VideoModel } from '../../../models/video/video.js'
|
import { VideoModel } from '../../../models/video/video.js'
|
||||||
import { replaceChaptersFromDescriptionIfNeeded } from '@server/lib/video-chapters.js'
|
|
||||||
import { addVideoJobsAfterUpdate } from '@server/lib/video-jobs.js'
|
|
||||||
import { updateLocalVideoMiniatureFromExisting } from '@server/lib/thumbnail.js'
|
|
||||||
|
|
||||||
const lTags = loggerTagsFactory('api', 'video')
|
const lTags = loggerTagsFactory('api', 'video')
|
||||||
const auditLogger = auditLoggerFactory('videos')
|
const auditLogger = auditLoggerFactory('videos')
|
||||||
|
@ -53,7 +54,7 @@ async function updateVideo (req: express.Request, res: express.Response) {
|
||||||
const oldVideoAuditView = new VideoAuditView(videoFromReq.toFormattedDetailsJSON())
|
const oldVideoAuditView = new VideoAuditView(videoFromReq.toFormattedDetailsJSON())
|
||||||
const videoInfoToUpdate: VideoUpdate = req.body
|
const videoInfoToUpdate: VideoUpdate = req.body
|
||||||
|
|
||||||
const hadPrivacyForFederation = videoFromReq.hasPrivacyForFederation()
|
const hadPrivacyForFederation = isPrivacyForFederation(videoFromReq.privacy)
|
||||||
const oldPrivacy = videoFromReq.privacy
|
const oldPrivacy = videoFromReq.privacy
|
||||||
|
|
||||||
const thumbnails = await buildVideoThumbnailsFromReq(videoFromReq, req.files)
|
const thumbnails = await buildVideoThumbnailsFromReq(videoFromReq, req.files)
|
||||||
|
@ -192,7 +193,7 @@ async function updateVideoPrivacy (options: {
|
||||||
transaction: Transaction
|
transaction: Transaction
|
||||||
}) {
|
}) {
|
||||||
const { videoInstance, videoInfoToUpdate, hadPrivacyForFederation, transaction } = options
|
const { videoInstance, videoInfoToUpdate, hadPrivacyForFederation, transaction } = options
|
||||||
const isNewVideoForFederation = videoInstance.isNewVideoForFederation(videoInfoToUpdate.privacy)
|
const isNewVideoForFederation = isNewVideoPrivacyForFederation(videoInstance.privacy, videoInfoToUpdate.privacy)
|
||||||
|
|
||||||
const newPrivacy = forceNumber(videoInfoToUpdate.privacy) as VideoPrivacyType
|
const newPrivacy = forceNumber(videoInfoToUpdate.privacy) as VideoPrivacyType
|
||||||
setVideoPrivacy(videoInstance, newPrivacy)
|
setVideoPrivacy(videoInstance, newPrivacy)
|
||||||
|
@ -208,7 +209,7 @@ async function updateVideoPrivacy (options: {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unfederate the video if the new privacy is not compatible with federation
|
// Unfederate the video if the new privacy is not compatible with federation
|
||||||
if (hadPrivacyForFederation && !videoInstance.hasPrivacyForFederation()) {
|
if (hadPrivacyForFederation && !isPrivacyForFederation(videoInstance.privacy)) {
|
||||||
await VideoModel.sendDelete(videoInstance, { transaction })
|
await VideoModel.sendDelete(videoInstance, { transaction })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { ContextType } from '@peertube/peertube-models'
|
import { ContextType } from '@peertube/peertube-models'
|
||||||
import { ACTIVITY_PUB, REMOTE_SCHEME } from '@server/initializers/constants.js'
|
import { ACTIVITY_PUB, REMOTE_SCHEME } from '@server/initializers/constants.js'
|
||||||
|
import { isArray } from './custom-validators/misc.js'
|
||||||
import { buildDigest } from './peertube-crypto.js'
|
import { buildDigest } from './peertube-crypto.js'
|
||||||
import type { signJsonLDObject } from './peertube-jsonld.js'
|
import type { signJsonLDObject } from './peertube-jsonld.js'
|
||||||
import { doJSONRequest } from './requests.js'
|
import { doJSONRequest } from './requests.js'
|
||||||
import { isArray } from './custom-validators/misc.js'
|
|
||||||
|
|
||||||
export type ContextFilter = <T> (arg: T) => Promise<T>
|
export type ContextFilter = <T> (arg: T) => Promise<T>
|
||||||
|
|
||||||
|
@ -49,6 +49,18 @@ export async function getApplicationActorOfHost (host: string) {
|
||||||
return found?.href || undefined
|
return found?.href || undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAPPublicValue () {
|
||||||
|
return 'https://www.w3.org/ns/activitystreams#Public'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasAPPublic (toOrCC: string[]) {
|
||||||
|
if (!isArray(toOrCC)) return false
|
||||||
|
|
||||||
|
const publicValue = getAPPublicValue()
|
||||||
|
|
||||||
|
return toOrCC.some(f => f === 'as:Public' || publicValue)
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Private
|
// Private
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -58,7 +70,6 @@ type ContextValue = { [ id: string ]: (string | { '@type': string, '@id': string
|
||||||
const contextStore: { [ id in ContextType ]: (string | { [ id: string ]: string })[] } = {
|
const contextStore: { [ id in ContextType ]: (string | { [ id: string ]: string })[] } = {
|
||||||
Video: buildContext({
|
Video: buildContext({
|
||||||
Hashtag: 'as:Hashtag',
|
Hashtag: 'as:Hashtag',
|
||||||
uuid: 'sc:identifier',
|
|
||||||
category: 'sc:category',
|
category: 'sc:category',
|
||||||
licence: 'sc:license',
|
licence: 'sc:license',
|
||||||
subtitleLanguage: 'sc:subtitleLanguage',
|
subtitleLanguage: 'sc:subtitleLanguage',
|
||||||
|
@ -99,6 +110,11 @@ const contextStore: { [ id in ContextType ]: (string | { [ id: string ]: string
|
||||||
'@id': 'pt:aspectRatio'
|
'@id': 'pt:aspectRatio'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
uuid: {
|
||||||
|
'@type': 'sc:identifier',
|
||||||
|
'@id': 'pt:uuid'
|
||||||
|
},
|
||||||
|
|
||||||
originallyPublishedAt: 'sc:datePublished',
|
originallyPublishedAt: 'sc:datePublished',
|
||||||
|
|
||||||
uploadDate: 'sc:uploadDate',
|
uploadDate: 'sc:uploadDate',
|
||||||
|
@ -170,12 +186,23 @@ const contextStore: { [ id in ContextType ]: (string | { [ id: string ]: string
|
||||||
'@type': 'sc:Number',
|
'@type': 'sc:Number',
|
||||||
'@id': 'pt:stopTimestamp'
|
'@id': 'pt:stopTimestamp'
|
||||||
},
|
},
|
||||||
uuid: 'sc:identifier'
|
uuid: {
|
||||||
|
'@type': 'sc:identifier',
|
||||||
|
'@id': 'pt:uuid'
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
CacheFile: buildContext({
|
CacheFile: buildContext({
|
||||||
expires: 'sc:expires',
|
expires: 'sc:expires',
|
||||||
CacheFile: 'pt:CacheFile'
|
CacheFile: 'pt:CacheFile',
|
||||||
|
size: {
|
||||||
|
'@type': 'sc:Number',
|
||||||
|
'@id': 'pt:size'
|
||||||
|
},
|
||||||
|
fps: {
|
||||||
|
'@type': 'sc:Number',
|
||||||
|
'@id': 'pt:fps'
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Flag: buildContext({
|
Flag: buildContext({
|
||||||
|
@ -205,15 +232,21 @@ const contextStore: { [ id in ContextType ]: (string | { [ id: string ]: string
|
||||||
'@type': 'sc:Number',
|
'@type': 'sc:Number',
|
||||||
'@id': 'pt:startTimestamp'
|
'@id': 'pt:startTimestamp'
|
||||||
},
|
},
|
||||||
stopTimestamp: {
|
endTimestamp: {
|
||||||
'@type': 'sc:Number',
|
'@type': 'sc:Number',
|
||||||
'@id': 'pt:stopTimestamp'
|
'@id': 'pt:endTimestamp'
|
||||||
},
|
},
|
||||||
watchSection: {
|
uuid: {
|
||||||
'@type': 'sc:Number',
|
'@type': 'sc:identifier',
|
||||||
'@id': 'pt:stopTimestamp'
|
'@id': 'pt:uuid'
|
||||||
},
|
},
|
||||||
uuid: 'sc:identifier'
|
actionStatus: 'sc:actionStatus',
|
||||||
|
watchSections: {
|
||||||
|
'@type': '@id',
|
||||||
|
'@id': 'pt:watchSections'
|
||||||
|
},
|
||||||
|
addressRegion: 'sc:addressRegion',
|
||||||
|
addressCountry: 'sc:addressCountry'
|
||||||
}),
|
}),
|
||||||
|
|
||||||
View: buildContext({
|
View: buildContext({
|
||||||
|
@ -233,13 +266,46 @@ const contextStore: { [ id in ContextType ]: (string | { [ id: string ]: string
|
||||||
Rate: buildContext(),
|
Rate: buildContext(),
|
||||||
|
|
||||||
Chapters: buildContext({
|
Chapters: buildContext({
|
||||||
name: 'sc:name',
|
|
||||||
hasPart: 'sc:hasPart',
|
hasPart: 'sc:hasPart',
|
||||||
endOffset: 'sc:endOffset',
|
endOffset: 'sc:endOffset',
|
||||||
startOffset: 'sc:startOffset'
|
startOffset: 'sc:startOffset'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let allContext: (string | ContextValue)[]
|
||||||
|
export function getAllContext () {
|
||||||
|
if (allContext) return allContext
|
||||||
|
|
||||||
|
const processed = new Set<string>()
|
||||||
|
allContext = []
|
||||||
|
|
||||||
|
let staticContext: ContextValue = {}
|
||||||
|
|
||||||
|
for (const v of Object.values(contextStore)) {
|
||||||
|
for (const item of v) {
|
||||||
|
if (typeof item === 'string') {
|
||||||
|
if (!processed.has(item)) {
|
||||||
|
allContext.push(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
processed.add(item)
|
||||||
|
} else {
|
||||||
|
for (const subKey of Object.keys(item)) {
|
||||||
|
if (!processed.has(subKey)) {
|
||||||
|
staticContext = { ...staticContext, [subKey]: item[subKey] }
|
||||||
|
}
|
||||||
|
|
||||||
|
processed.add(subKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allContext = [ ...allContext, staticContext ]
|
||||||
|
|
||||||
|
return allContext
|
||||||
|
}
|
||||||
|
|
||||||
async function getContextData (type: ContextType, contextFilter: ContextFilter) {
|
async function getContextData (type: ContextType, contextFilter: ContextFilter) {
|
||||||
const contextData = contextFilter
|
const contextData = contextFilter
|
||||||
? await contextFilter(contextStore[type])
|
? await contextFilter(contextStore[type])
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import jsonld from 'jsonld'
|
import jsonld from 'jsonld'
|
||||||
|
|
||||||
const CACHE = {
|
const STATIC_CACHE = {
|
||||||
'https://w3id.org/security/v1': {
|
'https://w3id.org/security/v1': {
|
||||||
'@context': {
|
'@context': {
|
||||||
id: '@id',
|
id: '@id',
|
||||||
|
@ -53,19 +53,29 @@ const CACHE = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const localCache = new Map<string, any>()
|
||||||
|
|
||||||
const nodeDocumentLoader = (jsonld as any).documentLoaders.node();
|
const nodeDocumentLoader = (jsonld as any).documentLoaders.node();
|
||||||
|
|
||||||
/* eslint-disable no-import-assign */
|
/* eslint-disable no-import-assign */
|
||||||
(jsonld as any).documentLoader = (url) => {
|
(jsonld as any).documentLoader = async (url: string) => {
|
||||||
if (url in CACHE) {
|
if (url in STATIC_CACHE) {
|
||||||
return Promise.resolve({
|
return {
|
||||||
contextUrl: null,
|
contextUrl: null,
|
||||||
document: CACHE[url],
|
document: STATIC_CACHE[url],
|
||||||
documentUrl: url
|
documentUrl: url
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodeDocumentLoader(url)
|
if (localCache.has(url)) return localCache.get(url)
|
||||||
|
|
||||||
|
const remoteDoc = await nodeDocumentLoader(url)
|
||||||
|
|
||||||
|
if (localCache.size < 100) {
|
||||||
|
localCache.set(url, remoteDoc)
|
||||||
|
}
|
||||||
|
|
||||||
|
return remoteDoc
|
||||||
}
|
}
|
||||||
|
|
||||||
export { jsonld }
|
export { jsonld }
|
||||||
|
|
|
@ -1,20 +1,15 @@
|
||||||
import { CacheFileObject } from '@peertube/peertube-models'
|
import { CacheFileObject } from '@peertube/peertube-models'
|
||||||
import { exists, isDateValid } from '../misc.js'
|
import { MIMETYPES } from '@server/initializers/constants.js'
|
||||||
|
import validator from 'validator'
|
||||||
|
import { isDateValid } from '../misc.js'
|
||||||
import { isActivityPubUrlValid } from './misc.js'
|
import { isActivityPubUrlValid } from './misc.js'
|
||||||
import { isRemoteVideoUrlValid } from './videos.js'
|
|
||||||
|
|
||||||
function isCacheFileObjectValid (object: CacheFileObject) {
|
export function isCacheFileObjectValid (object: CacheFileObject) {
|
||||||
return exists(object) &&
|
if (!object || object.type !== 'CacheFile') return false
|
||||||
object.type === 'CacheFile' &&
|
|
||||||
(object.expires === null || isDateValid(object.expires)) &&
|
return (!object.expires || isDateValid(object.expires)) &&
|
||||||
isActivityPubUrlValid(object.object) &&
|
isActivityPubUrlValid(object.object) &&
|
||||||
(isRemoteVideoUrlValid(object.url) || isPlaylistRedundancyUrlValid(object.url))
|
(isRedundancyUrlVideoValid(object.url) || isPlaylistRedundancyUrlValid(object.url))
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export {
|
|
||||||
isCacheFileObjectValid
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -24,3 +19,15 @@ function isPlaylistRedundancyUrlValid (url: any) {
|
||||||
(url.mediaType || url.mimeType) === 'application/x-mpegURL' &&
|
(url.mediaType || url.mimeType) === 'application/x-mpegURL' &&
|
||||||
isActivityPubUrlValid(url.href)
|
isActivityPubUrlValid(url.href)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: compat with < 6.1, use isRemoteVideoUrlValid instead in 7.0
|
||||||
|
function isRedundancyUrlVideoValid (url: any) {
|
||||||
|
const size = url.size || url['_:size']
|
||||||
|
const fps = url.fps || url['_fps']
|
||||||
|
|
||||||
|
return MIMETYPES.AP_VIDEO.MIMETYPE_EXT[url.mediaType] &&
|
||||||
|
isActivityPubUrlValid(url.href) &&
|
||||||
|
validator.default.isInt(url.height + '', { min: 0 }) &&
|
||||||
|
validator.default.isInt(size + '', { min: 0 }) &&
|
||||||
|
(!fps || validator.default.isInt(fps + '', { min: -1 }))
|
||||||
|
}
|
||||||
|
|
|
@ -1,29 +1,25 @@
|
||||||
import validator from 'validator'
|
|
||||||
import { PlaylistElementObject, PlaylistObject } from '@peertube/peertube-models'
|
import { PlaylistElementObject, PlaylistObject } from '@peertube/peertube-models'
|
||||||
|
import validator from 'validator'
|
||||||
import { exists, isDateValid, isUUIDValid } from '../misc.js'
|
import { exists, isDateValid, isUUIDValid } from '../misc.js'
|
||||||
import { isVideoPlaylistNameValid } from '../video-playlists.js'
|
import { isVideoPlaylistNameValid } from '../video-playlists.js'
|
||||||
import { isActivityPubUrlValid } from './misc.js'
|
import { isActivityPubUrlValid } from './misc.js'
|
||||||
|
|
||||||
function isPlaylistObjectValid (object: PlaylistObject) {
|
export function isPlaylistObjectValid (object: PlaylistObject) {
|
||||||
return exists(object) &&
|
if (!object || object.type !== 'Playlist') return false
|
||||||
object.type === 'Playlist' &&
|
|
||||||
validator.default.isInt(object.totalItems + '') &&
|
// TODO: compat with < 6.1, remove in 7.0
|
||||||
|
if (!object.uuid && object['identifier']) object.uuid = object['identifier']
|
||||||
|
|
||||||
|
return validator.default.isInt(object.totalItems + '') &&
|
||||||
isVideoPlaylistNameValid(object.name) &&
|
isVideoPlaylistNameValid(object.name) &&
|
||||||
isUUIDValid(object.uuid) &&
|
isUUIDValid(object.uuid) &&
|
||||||
isDateValid(object.published) &&
|
isDateValid(object.published) &&
|
||||||
isDateValid(object.updated)
|
isDateValid(object.updated)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPlaylistElementObjectValid (object: PlaylistElementObject) {
|
export function isPlaylistElementObjectValid (object: PlaylistElementObject) {
|
||||||
return exists(object) &&
|
return exists(object) &&
|
||||||
object.type === 'PlaylistElement' &&
|
object.type === 'PlaylistElement' &&
|
||||||
validator.default.isInt(object.position + '') &&
|
validator.default.isInt(object.position + '') &&
|
||||||
isActivityPubUrlValid(object.url)
|
isActivityPubUrlValid(object.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export {
|
|
||||||
isPlaylistObjectValid,
|
|
||||||
isPlaylistElementObjectValid
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { hasAPPublic } from '@server/helpers/activity-pub-utils.js'
|
||||||
import validator from 'validator'
|
import validator from 'validator'
|
||||||
import { ACTIVITY_PUB } from '../../../initializers/constants.js'
|
|
||||||
import { exists, isArray, isDateValid } from '../misc.js'
|
import { exists, isArray, isDateValid } from '../misc.js'
|
||||||
import { isActivityPubUrlValid } from './misc.js'
|
import { isActivityPubUrlValid } from './misc.js'
|
||||||
|
|
||||||
|
@ -23,10 +23,7 @@ function sanitizeAndCheckVideoCommentObject (comment: any) {
|
||||||
isDateValid(comment.published) &&
|
isDateValid(comment.published) &&
|
||||||
isActivityPubUrlValid(comment.url) &&
|
isActivityPubUrlValid(comment.url) &&
|
||||||
isArray(comment.to) &&
|
isArray(comment.to) &&
|
||||||
(
|
(hasAPPublic(comment.to) || hasAPPublic(comment.cc)) // Only accept public comments
|
||||||
comment.to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ||
|
|
||||||
comment.cc.indexOf(ACTIVITY_PUB.PUBLIC) !== -1
|
|
||||||
) // Only accept public comments
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
@ -27,7 +27,7 @@ function sanitizeAndCheckVideoTorrentUpdateActivity (activity: any) {
|
||||||
sanitizeAndCheckVideoTorrentObject(activity.object)
|
sanitizeAndCheckVideoTorrentObject(activity.object)
|
||||||
}
|
}
|
||||||
|
|
||||||
function sanitizeAndCheckVideoTorrentObject (video: any) {
|
function sanitizeAndCheckVideoTorrentObject (video: VideoObject) {
|
||||||
if (!video || video.type !== 'Video') return false
|
if (!video || video.type !== 'Video') return false
|
||||||
|
|
||||||
if (!setValidRemoteTags(video)) {
|
if (!setValidRemoteTags(video)) {
|
||||||
|
@ -59,6 +59,9 @@ function sanitizeAndCheckVideoTorrentObject (video: any) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: compat with < 6.1, remove in 7.0
|
||||||
|
if (!video.uuid && video['identifier']) video.uuid = video['identifier']
|
||||||
|
|
||||||
// Default attributes
|
// Default attributes
|
||||||
if (!isVideoStateValid(video.state)) video.state = VideoState.PUBLISHED
|
if (!isVideoStateValid(video.state)) video.state = VideoState.PUBLISHED
|
||||||
if (!isBooleanValid(video.waitTranscoding)) video.waitTranscoding = false
|
if (!isBooleanValid(video.waitTranscoding)) video.waitTranscoding = false
|
||||||
|
|
|
@ -1,19 +1,26 @@
|
||||||
|
import { arrayify } from '@peertube/peertube-core-utils'
|
||||||
import { WatchActionObject } from '@peertube/peertube-models'
|
import { WatchActionObject } from '@peertube/peertube-models'
|
||||||
import { exists, isDateValid, isUUIDValid } from '../misc.js'
|
import { isDateValid, isUUIDValid } from '../misc.js'
|
||||||
import { isVideoTimeValid } from '../video-view.js'
|
import { isVideoTimeValid } from '../video-view.js'
|
||||||
import { isActivityPubVideoDurationValid, isObjectValid } from './misc.js'
|
import { isActivityPubVideoDurationValid, isObjectValid } from './misc.js'
|
||||||
|
|
||||||
function isWatchActionObjectValid (action: WatchActionObject) {
|
function isWatchActionObjectValid (action: WatchActionObject) {
|
||||||
return exists(action) &&
|
if (!action || action.type !== 'WatchAction') return false
|
||||||
action.type === 'WatchAction' &&
|
|
||||||
isObjectValid(action.id) &&
|
// TODO: compat with < 6.1, remove in 7.0
|
||||||
|
if (!action.uuid && action['identifier']) action.uuid = action['identifier']
|
||||||
|
|
||||||
|
if (action['_:actionStatus'] && !action.actionStatus) action.actionStatus = action['_:actionStatus']
|
||||||
|
if (action['_:watchSections'] && !action.watchSections) action.watchSections = arrayify(action['_:watchSections'])
|
||||||
|
|
||||||
|
return isObjectValid(action.id) &&
|
||||||
isActivityPubVideoDurationValid(action.duration) &&
|
isActivityPubVideoDurationValid(action.duration) &&
|
||||||
isDateValid(action.startTime) &&
|
isDateValid(action.startTime) &&
|
||||||
isDateValid(action.endTime) &&
|
isDateValid(action.endTime) &&
|
||||||
isLocationValid(action.location) &&
|
isLocationValid(action.location) &&
|
||||||
isUUIDValid(action.uuid) &&
|
isUUIDValid(action.uuid) &&
|
||||||
isObjectValid(action.object) &&
|
isObjectValid(action.object) &&
|
||||||
isWatchSectionsValid(action.watchSections)
|
areWatchSectionsValid(action.watchSections)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -34,8 +41,11 @@ function isLocationValid (location: any) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
function isWatchSectionsValid (sections: WatchActionObject['watchSections']) {
|
function areWatchSectionsValid (sections: WatchActionObject['watchSections']) {
|
||||||
return Array.isArray(sections) && sections.every(s => {
|
return Array.isArray(sections) && sections.every(s => {
|
||||||
|
// TODO: compat with < 6.1, remove in 7.0
|
||||||
|
if (s['_:endTimestamp'] && !s.endTimestamp) s.endTimestamp = s['_:endTimestamp']
|
||||||
|
|
||||||
return isVideoTimeValid(s.startTimestamp) && isVideoTimeValid(s.endTimestamp)
|
return isVideoTimeValid(s.startTimestamp) && isVideoTimeValid(s.endTimestamp)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ export function areVideoTagsValid (tags: string[]) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isVideoViewsValid (value: string) {
|
export function isVideoViewsValid (value: string | number) {
|
||||||
return exists(value) && validator.default.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.VIEWS)
|
return exists(value) && validator.default.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.VIEWS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,51 @@
|
||||||
|
import { omit } from '@peertube/peertube-core-utils'
|
||||||
import { sha256 } from '@peertube/peertube-node-utils'
|
import { sha256 } from '@peertube/peertube-node-utils'
|
||||||
import { createSign, createVerify } from 'crypto'
|
import { createSign, createVerify } from 'crypto'
|
||||||
import cloneDeep from 'lodash-es/cloneDeep.js'
|
import cloneDeep from 'lodash-es/cloneDeep.js'
|
||||||
import { MActor } from '../types/models/index.js'
|
import { MActor } from '../types/models/index.js'
|
||||||
|
import { getAllContext } from './activity-pub-utils.js'
|
||||||
|
import { jsonld } from './custom-jsonld-signature.js'
|
||||||
|
import { isArray } from './custom-validators/misc.js'
|
||||||
import { logger } from './logger.js'
|
import { logger } from './logger.js'
|
||||||
import { assertIsInWorkerThread } from './threads.js'
|
import { assertIsInWorkerThread } from './threads.js'
|
||||||
import { jsonld } from './custom-jsonld-signature.js'
|
|
||||||
|
|
||||||
export function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise<boolean> {
|
type ExpressRequest = { body: any }
|
||||||
if (signedDocument.signature.type === 'RsaSignature2017') {
|
|
||||||
return isJsonLDRSA2017Verified(fromActor, signedDocument)
|
export function compactJSONLDAndCheckSignature (fromActor: MActor, req: ExpressRequest): Promise<boolean> {
|
||||||
|
if (req.body.signature.type === 'RsaSignature2017') {
|
||||||
|
return compactJSONLDAndCheckRSA2017Signature(fromActor, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.warn('Unknown JSON LD signature %s.', signedDocument.signature.type, signedDocument)
|
logger.warn('Unknown JSON LD signature %s.', req.body.signature.type, req.body)
|
||||||
|
|
||||||
return Promise.resolve(false)
|
return Promise.resolve(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backward compatibility with "other" implementations
|
// Backward compatibility with "other" implementations
|
||||||
export async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument: any) {
|
export async function compactJSONLDAndCheckRSA2017Signature (fromActor: MActor, req: ExpressRequest) {
|
||||||
|
const compacted = await jsonldCompact(omit(req.body, [ 'signature' ]))
|
||||||
|
|
||||||
|
fixCompacted(req.body, compacted)
|
||||||
|
|
||||||
|
req.body = { ...compacted, signature: req.body.signature }
|
||||||
|
|
||||||
|
if (compacted['@include']) {
|
||||||
|
logger.warn('JSON-LD @include is not supported')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: compat with < 6.1, remove in 7.0
|
||||||
|
let safe = true
|
||||||
|
if (
|
||||||
|
(compacted.type === 'Create' && (compacted?.object?.type === 'WatchAction' || compacted?.object?.type === 'CacheFile')) ||
|
||||||
|
(compacted.type === 'Undo' && compacted?.object?.type === 'Create' && compacted?.object?.object.type === 'CacheFile')
|
||||||
|
) {
|
||||||
|
safe = false
|
||||||
|
}
|
||||||
|
|
||||||
const [ documentHash, optionsHash ] = await Promise.all([
|
const [ documentHash, optionsHash ] = await Promise.all([
|
||||||
createDocWithoutSignatureHash(signedDocument),
|
hashObject(compacted, safe),
|
||||||
createSignatureHash(signedDocument.signature)
|
createSignatureHash(req.body.signature, safe)
|
||||||
])
|
])
|
||||||
|
|
||||||
const toVerify = optionsHash + documentHash
|
const toVerify = optionsHash + documentHash
|
||||||
|
@ -28,7 +53,39 @@ export async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument
|
||||||
const verify = createVerify('RSA-SHA256')
|
const verify = createVerify('RSA-SHA256')
|
||||||
verify.update(toVerify, 'utf8')
|
verify.update(toVerify, 'utf8')
|
||||||
|
|
||||||
return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64')
|
return verify.verify(fromActor.publicKey, req.body.signature.signatureValue, 'base64')
|
||||||
|
}
|
||||||
|
|
||||||
|
function fixCompacted (original: any, compacted: any) {
|
||||||
|
if (!original || !compacted) return
|
||||||
|
|
||||||
|
for (const [ k, v ] of Object.entries(original)) {
|
||||||
|
if (k === '@context' || k === 'signature') continue
|
||||||
|
if (v === undefined || v === null) continue
|
||||||
|
|
||||||
|
const cv = compacted[k]
|
||||||
|
if (cv === undefined || cv === null) continue
|
||||||
|
|
||||||
|
if (typeof v === 'string') {
|
||||||
|
if (v === 'https://www.w3.org/ns/activitystreams#Public' && cv === 'as:Public') {
|
||||||
|
compacted[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isArray(v) && !isArray(cv)) {
|
||||||
|
compacted[k] = [ cv ]
|
||||||
|
|
||||||
|
for (let i = 0; i < v.length; i++) {
|
||||||
|
if (v[i] === 'https://www.w3.org/ns/activitystreams#Public' && cv[i] === 'as:Public') {
|
||||||
|
compacted[k][i] = v[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof v === 'object') {
|
||||||
|
fixCompacted(original[k], compacted[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function signJsonLDObject <T> (options: {
|
export async function signJsonLDObject <T> (options: {
|
||||||
|
@ -66,35 +123,40 @@ export async function signJsonLDObject <T> (options: {
|
||||||
// Private
|
// Private
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function hashObject (obj: any): Promise<any> {
|
async function hashObject (obj: any, safe: boolean): Promise<any> {
|
||||||
const res = await (jsonld as any).promises.normalize(obj, {
|
const res = await jsonldNormalize(obj, safe)
|
||||||
safe: false,
|
|
||||||
algorithm: 'URDNA2015',
|
|
||||||
format: 'application/n-quads'
|
|
||||||
})
|
|
||||||
|
|
||||||
return sha256(res)
|
return sha256(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSignatureHash (signature: any) {
|
function jsonldCompact (obj: any) {
|
||||||
const signatureCopy = cloneDeep(signature)
|
return (jsonld as any).promises.compact(obj, getAllContext())
|
||||||
Object.assign(signatureCopy, {
|
}
|
||||||
|
|
||||||
|
function jsonldNormalize (obj: any, safe: boolean) {
|
||||||
|
return (jsonld as any).promises.normalize(obj, {
|
||||||
|
safe,
|
||||||
|
algorithm: 'URDNA2015',
|
||||||
|
format: 'application/n-quads'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function createSignatureHash (signature: any, safe = true) {
|
||||||
|
return hashObject({
|
||||||
'@context': [
|
'@context': [
|
||||||
'https://w3id.org/security/v1',
|
'https://w3id.org/security/v1',
|
||||||
{ RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' }
|
{ RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' }
|
||||||
]
|
],
|
||||||
})
|
|
||||||
|
|
||||||
delete signatureCopy.type
|
...omit(signature, [ 'type', 'id', 'signatureValue' ])
|
||||||
delete signatureCopy.id
|
}, safe)
|
||||||
delete signatureCopy.signatureValue
|
|
||||||
|
|
||||||
return hashObject(signatureCopy)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDocWithoutSignatureHash (doc: any) {
|
function createDocWithoutSignatureHash (doc: any) {
|
||||||
const docWithoutSignature = cloneDeep(doc)
|
const docWithoutSignature = cloneDeep(doc)
|
||||||
delete docWithoutSignature.signature
|
delete docWithoutSignature.signature
|
||||||
|
|
||||||
return hashObject(docWithoutSignature)
|
return hashObject(docWithoutSignature, true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,51 +1,28 @@
|
||||||
import { Response } from 'express'
|
import { VideoPrivacy } from '@peertube/peertube-models'
|
||||||
import { forceNumber } from '@peertube/peertube-core-utils'
|
|
||||||
import { VideoPrivacy, VideoPrivacyType, VideoState, VideoStateType } from '@peertube/peertube-models'
|
|
||||||
import { CONFIG } from '@server/initializers/config.js'
|
import { CONFIG } from '@server/initializers/config.js'
|
||||||
import { isStreamingPlaylist, MStreamingPlaylistVideo, MVideo } from '@server/types/models/index.js'
|
import { isStreamingPlaylist, MStreamingPlaylistVideo, MVideo } from '@server/types/models/index.js'
|
||||||
|
import { Response } from 'express'
|
||||||
|
|
||||||
function getVideoWithAttributes (res: Response) {
|
export function getVideoWithAttributes (res: Response) {
|
||||||
return res.locals.videoAPI || res.locals.videoAll || res.locals.onlyVideo
|
return res.locals.videoAPI || res.locals.videoAll || res.locals.onlyVideo
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractVideo (videoOrPlaylist: MVideo | MStreamingPlaylistVideo) {
|
export function extractVideo (videoOrPlaylist: MVideo | MStreamingPlaylistVideo) {
|
||||||
return isStreamingPlaylist(videoOrPlaylist)
|
return isStreamingPlaylist(videoOrPlaylist)
|
||||||
? videoOrPlaylist.Video
|
? videoOrPlaylist.Video
|
||||||
: videoOrPlaylist
|
: videoOrPlaylist
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPrivacyForFederation (privacy: VideoPrivacyType) {
|
export function getPrivaciesForFederation () {
|
||||||
const castedPrivacy = forceNumber(privacy)
|
|
||||||
|
|
||||||
return castedPrivacy === VideoPrivacy.PUBLIC ||
|
|
||||||
(CONFIG.FEDERATION.VIDEOS.FEDERATE_UNLISTED === true && castedPrivacy === VideoPrivacy.UNLISTED)
|
|
||||||
}
|
|
||||||
|
|
||||||
function isStateForFederation (state: VideoStateType) {
|
|
||||||
const castedState = forceNumber(state)
|
|
||||||
|
|
||||||
return castedState === VideoState.PUBLISHED || castedState === VideoState.WAITING_FOR_LIVE || castedState === VideoState.LIVE_ENDED
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPrivaciesForFederation () {
|
|
||||||
return (CONFIG.FEDERATION.VIDEOS.FEDERATE_UNLISTED === true)
|
return (CONFIG.FEDERATION.VIDEOS.FEDERATE_UNLISTED === true)
|
||||||
? [ { privacy: VideoPrivacy.PUBLIC }, { privacy: VideoPrivacy.UNLISTED } ]
|
? [ { privacy: VideoPrivacy.PUBLIC }, { privacy: VideoPrivacy.UNLISTED } ]
|
||||||
: [ { privacy: VideoPrivacy.PUBLIC } ]
|
: [ { privacy: VideoPrivacy.PUBLIC } ]
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExtFromMimetype (mimeTypes: { [id: string]: string | string[] }, mimeType: string) {
|
export function getExtFromMimetype (mimeTypes: { [id: string]: string | string[] }, mimeType: string) {
|
||||||
const value = mimeTypes[mimeType]
|
const value = mimeTypes[mimeType]
|
||||||
|
|
||||||
if (Array.isArray(value)) return value[0]
|
if (Array.isArray(value)) return value[0]
|
||||||
|
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
|
||||||
getVideoWithAttributes,
|
|
||||||
extractVideo,
|
|
||||||
getExtFromMimetype,
|
|
||||||
isStateForFederation,
|
|
||||||
isPrivacyForFederation,
|
|
||||||
getPrivaciesForFederation
|
|
||||||
}
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ import { cpus } from 'os'
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const LAST_MIGRATION_VERSION = 830
|
const LAST_MIGRATION_VERSION = 835
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -775,7 +775,6 @@ const ACTIVITY_PUB = {
|
||||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
|
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
|
||||||
],
|
],
|
||||||
ACCEPT_HEADER: 'application/activity+json, application/ld+json',
|
ACCEPT_HEADER: 'application/activity+json, application/ld+json',
|
||||||
PUBLIC: 'https://www.w3.org/ns/activitystreams#Public',
|
|
||||||
COLLECTION_ITEMS_PER_PAGE: 10,
|
COLLECTION_ITEMS_PER_PAGE: 10,
|
||||||
FETCH_PAGE_LIMIT: 2000,
|
FETCH_PAGE_LIMIT: 2000,
|
||||||
MAX_RECURSION_COMMENTS: 100,
|
MAX_RECURSION_COMMENTS: 100,
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import * as Sequelize from 'sequelize'
|
||||||
|
|
||||||
|
async function up (utils: {
|
||||||
|
transaction: Sequelize.Transaction
|
||||||
|
queryInterface: Sequelize.QueryInterface
|
||||||
|
sequelize: Sequelize.Sequelize
|
||||||
|
}): Promise<void> {
|
||||||
|
const { transaction } = utils
|
||||||
|
|
||||||
|
{
|
||||||
|
await utils.queryInterface.changeColumn('videoSource', 'size', {
|
||||||
|
type: Sequelize.BIGINT,
|
||||||
|
allowNull: true
|
||||||
|
}, { transaction })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function down (options) {
|
||||||
|
throw new Error('Not implemented.')
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
down, up
|
||||||
|
}
|
|
@ -1,17 +1,17 @@
|
||||||
import { ActivityAudience } from '@peertube/peertube-models'
|
import { ActivityAudience } from '@peertube/peertube-models'
|
||||||
import { ACTIVITY_PUB } from '../../initializers/constants.js'
|
import { getAPPublicValue } from '@server/helpers/activity-pub-utils.js'
|
||||||
import { MActorFollowersUrl } from '../../types/models/index.js'
|
import { MActorFollowersUrl } from '../../types/models/index.js'
|
||||||
|
|
||||||
function getAudience (actorSender: MActorFollowersUrl, isPublic = true) {
|
export function getAudience (actorSender: MActorFollowersUrl, isPublic = true) {
|
||||||
return buildAudience([ actorSender.followersUrl ], isPublic)
|
return buildAudience([ actorSender.followersUrl ], isPublic)
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildAudience (followerUrls: string[], isPublic = true) {
|
export function buildAudience (followerUrls: string[], isPublic = true) {
|
||||||
let to: string[] = []
|
let to: string[] = []
|
||||||
let cc: string[] = []
|
let cc: string[] = []
|
||||||
|
|
||||||
if (isPublic) {
|
if (isPublic) {
|
||||||
to = [ ACTIVITY_PUB.PUBLIC ]
|
to = [ getAPPublicValue() ]
|
||||||
cc = followerUrls
|
cc = followerUrls
|
||||||
} else { // Unlisted
|
} else { // Unlisted
|
||||||
to = []
|
to = []
|
||||||
|
@ -21,14 +21,6 @@ function buildAudience (followerUrls: string[], isPublic = true) {
|
||||||
return { to, cc }
|
return { to, cc }
|
||||||
}
|
}
|
||||||
|
|
||||||
function audiencify<T> (object: T, audience: ActivityAudience) {
|
export function audiencify<T> (object: T, audience: ActivityAudience) {
|
||||||
return { ...audience, ...object }
|
return { ...audience, ...object }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export {
|
|
||||||
buildAudience,
|
|
||||||
getAudience,
|
|
||||||
audiencify
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { Transaction } from 'sequelize'
|
||||||
import { MActorId, MVideoRedundancy, MVideoWithAllFiles } from '@server/types/models/index.js'
|
import { MActorId, MVideoRedundancy, MVideoWithAllFiles } from '@server/types/models/index.js'
|
||||||
import { CacheFileObject, VideoStreamingPlaylistType } from '@peertube/peertube-models'
|
import { CacheFileObject, VideoStreamingPlaylistType } from '@peertube/peertube-models'
|
||||||
import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy.js'
|
import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy.js'
|
||||||
|
import { exists } from '@server/helpers/custom-validators/misc.js'
|
||||||
|
|
||||||
async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId, t: Transaction) {
|
async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId, t: Transaction) {
|
||||||
const redundancyModel = await VideoRedundancyModel.loadByUrl(cacheFileObject.id, t)
|
const redundancyModel = await VideoRedundancyModel.loadByUrl(cacheFileObject.id, t)
|
||||||
|
@ -65,11 +66,15 @@ function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = cacheFileObject.url
|
const url = cacheFileObject.url
|
||||||
|
const urlFPS = exists(url.fps) // TODO: compat with < 6.1, remove in 7.0
|
||||||
|
? url.fps
|
||||||
|
: url['_:fps']
|
||||||
|
|
||||||
const videoFile = video.VideoFiles.find(f => {
|
const videoFile = video.VideoFiles.find(f => {
|
||||||
return f.resolution === url.height && f.fps === url.fps
|
return f.resolution === url.height && f.fps === urlFPS
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!videoFile) throw new Error(`Cannot find video file ${url.height} ${url.fps} of video ${video.url}`)
|
if (!videoFile) throw new Error(`Cannot find video file ${url.height} ${urlFPS} of video ${video.url}`)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
expiresOn: cacheFileObject.expires ? new Date(cacheFileObject.expires) : null,
|
expiresOn: cacheFileObject.expires ? new Date(cacheFileObject.expires) : null,
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Activity } from '@peertube/peertube-models'
|
||||||
import { StatsManager } from '../stat-manager.js'
|
import { StatsManager } from '../stat-manager.js'
|
||||||
import { processActivities } from './process/index.js'
|
import { processActivities } from './process/index.js'
|
||||||
|
|
||||||
class InboxManager {
|
export class InboxManager {
|
||||||
|
|
||||||
private static instance: InboxManager
|
private static instance: InboxManager
|
||||||
private readonly inboxQueue: PQueue
|
private readonly inboxQueue: PQueue
|
||||||
|
@ -39,9 +39,3 @@ class InboxManager {
|
||||||
return this.instance || (this.instance = new this())
|
return this.instance || (this.instance = new this())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export {
|
|
||||||
InboxManager
|
|
||||||
}
|
|
||||||
|
|
|
@ -145,7 +145,7 @@ async function buildElementsDBAttributes (elementUrls: string[], playlist: MVide
|
||||||
try {
|
try {
|
||||||
const { elementObject } = await fetchRemotePlaylistElement(elementUrl)
|
const { elementObject } = await fetchRemotePlaylistElement(elementUrl)
|
||||||
|
|
||||||
const { video } = await getOrCreateAPVideo({ videoObject: { id: elementObject.url }, fetchType: 'only-video' })
|
const { video } = await getOrCreateAPVideo({ videoObject: { id: elementObject.url }, fetchType: 'only-video-and-blacklist' })
|
||||||
|
|
||||||
elementsToCreate.push(playlistElementObjectToDBAttributes(elementObject, playlist, video))
|
elementsToCreate.push(playlistElementObjectToDBAttributes(elementObject, playlist, video))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { ACTIVITY_PUB } from '@server/initializers/constants.js'
|
|
||||||
import { VideoPlaylistModel } from '@server/models/video/video-playlist.js'
|
|
||||||
import { VideoPlaylistElementModel } from '@server/models/video/video-playlist-element.js'
|
|
||||||
import { MVideoId, MVideoPlaylistId } from '@server/types/models/index.js'
|
|
||||||
import { AttributesOnly } from '@peertube/peertube-typescript-utils'
|
|
||||||
import { PlaylistElementObject, PlaylistObject, VideoPlaylistPrivacy } from '@peertube/peertube-models'
|
import { PlaylistElementObject, PlaylistObject, VideoPlaylistPrivacy } from '@peertube/peertube-models'
|
||||||
|
import { AttributesOnly } from '@peertube/peertube-typescript-utils'
|
||||||
|
import { hasAPPublic } from '@server/helpers/activity-pub-utils.js'
|
||||||
|
import { VideoPlaylistElementModel } from '@server/models/video/video-playlist-element.js'
|
||||||
|
import { VideoPlaylistModel } from '@server/models/video/video-playlist.js'
|
||||||
|
import { MVideoId, MVideoPlaylistId } from '@server/types/models/index.js'
|
||||||
|
|
||||||
function playlistObjectToDBAttributes (playlistObject: PlaylistObject, to: string[]) {
|
export function playlistObjectToDBAttributes (playlistObject: PlaylistObject, to: string[]) {
|
||||||
const privacy = to.includes(ACTIVITY_PUB.PUBLIC)
|
const privacy = hasAPPublic(to)
|
||||||
? VideoPlaylistPrivacy.PUBLIC
|
? VideoPlaylistPrivacy.PUBLIC
|
||||||
: VideoPlaylistPrivacy.UNLISTED
|
: VideoPlaylistPrivacy.UNLISTED
|
||||||
|
|
||||||
|
@ -23,7 +23,11 @@ function playlistObjectToDBAttributes (playlistObject: PlaylistObject, to: strin
|
||||||
} as AttributesOnly<VideoPlaylistModel>
|
} as AttributesOnly<VideoPlaylistModel>
|
||||||
}
|
}
|
||||||
|
|
||||||
function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObject, videoPlaylist: MVideoPlaylistId, video: MVideoId) {
|
export function playlistElementObjectToDBAttributes (
|
||||||
|
elementObject: PlaylistElementObject,
|
||||||
|
videoPlaylist: MVideoPlaylistId,
|
||||||
|
video: MVideoId
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
position: elementObject.position,
|
position: elementObject.position,
|
||||||
url: elementObject.id,
|
url: elementObject.id,
|
||||||
|
@ -33,8 +37,3 @@ function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObje
|
||||||
videoId: video.id
|
videoId: video.id
|
||||||
} as AttributesOnly<VideoPlaylistElementModel>
|
} as AttributesOnly<VideoPlaylistElementModel>
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
|
||||||
playlistObjectToDBAttributes,
|
|
||||||
playlistElementObjectToDBAttributes
|
|
||||||
}
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ import { createOrUpdateLocalVideoViewer } from '../local-video-viewer.js'
|
||||||
import { createOrUpdateVideoPlaylist } from '../playlists/index.js'
|
import { createOrUpdateVideoPlaylist } from '../playlists/index.js'
|
||||||
import { forwardVideoRelatedActivity } from '../send/shared/send-utils.js'
|
import { forwardVideoRelatedActivity } from '../send/shared/send-utils.js'
|
||||||
import { resolveThread } from '../video-comments.js'
|
import { resolveThread } from '../video-comments.js'
|
||||||
import { getOrCreateAPVideo } from '../videos/index.js'
|
import { canVideoBeFederated, getOrCreateAPVideo } from '../videos/index.js'
|
||||||
|
|
||||||
async function processCreateActivity (options: APProcessorOptions<ActivityCreate<ActivityCreateObject>>) {
|
async function processCreateActivity (options: APProcessorOptions<ActivityCreate<ActivityCreateObject>>) {
|
||||||
const { activity, byActor } = options
|
const { activity, byActor } = options
|
||||||
|
@ -87,6 +87,11 @@ async function processCreateCacheFile (
|
||||||
|
|
||||||
const { video } = await getOrCreateAPVideo({ videoObject: cacheFile.object })
|
const { video } = await getOrCreateAPVideo({ videoObject: cacheFile.object })
|
||||||
|
|
||||||
|
if (video.isOwned() && !canVideoBeFederated(video)) {
|
||||||
|
logger.warn(`Do not process create cache file ${cacheFile.object} on a video that cannot be federated`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
await sequelizeTypescript.transaction(async t => {
|
await sequelizeTypescript.transaction(async t => {
|
||||||
return createOrUpdateCacheFile(cacheFile, video, byActor, t)
|
return createOrUpdateCacheFile(cacheFile, video, byActor, t)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { VideoModel } from '@server/models/video/video.js'
|
|
||||||
import { ActivityDislike } from '@peertube/peertube-models'
|
import { ActivityDislike } from '@peertube/peertube-models'
|
||||||
|
import { logger } from '@server/helpers/logger.js'
|
||||||
|
import { VideoModel } from '@server/models/video/video.js'
|
||||||
import { retryTransactionWrapper } from '../../../helpers/database-utils.js'
|
import { retryTransactionWrapper } from '../../../helpers/database-utils.js'
|
||||||
import { sequelizeTypescript } from '../../../initializers/database.js'
|
import { sequelizeTypescript } from '../../../initializers/database.js'
|
||||||
import { AccountVideoRateModel } from '../../../models/account/account-video-rate.js'
|
import { AccountVideoRateModel } from '../../../models/account/account-video-rate.js'
|
||||||
import { APProcessorOptions } from '../../../types/activitypub-processor.model.js'
|
import { APProcessorOptions } from '../../../types/activitypub-processor.model.js'
|
||||||
import { MActorSignature } from '../../../types/models/index.js'
|
import { MActorSignature } from '../../../types/models/index.js'
|
||||||
import { federateVideoIfNeeded, maybeGetOrCreateAPVideo } from '../videos/index.js'
|
import { canVideoBeFederated, federateVideoIfNeeded, maybeGetOrCreateAPVideo } from '../videos/index.js'
|
||||||
|
|
||||||
async function processDislikeActivity (options: APProcessorOptions<ActivityDislike>) {
|
async function processDislikeActivity (options: APProcessorOptions<ActivityDislike>) {
|
||||||
const { activity, byActor } = options
|
const { activity, byActor } = options
|
||||||
|
@ -21,14 +22,19 @@ export {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function processDislike (activity: ActivityDislike, byActor: MActorSignature) {
|
async function processDislike (activity: ActivityDislike, byActor: MActorSignature) {
|
||||||
const dislikeObject = activity.object
|
const videoUrl = activity.object
|
||||||
const byAccount = byActor.Account
|
const byAccount = byActor.Account
|
||||||
|
|
||||||
if (!byAccount) throw new Error('Cannot create dislike with the non account actor ' + byActor.url)
|
if (!byAccount) throw new Error('Cannot create dislike with the non account actor ' + byActor.url)
|
||||||
|
|
||||||
const { video: onlyVideo } = await maybeGetOrCreateAPVideo({ videoObject: dislikeObject, fetchType: 'only-video' })
|
const { video: onlyVideo } = await maybeGetOrCreateAPVideo({ videoObject: videoUrl, fetchType: 'only-video-and-blacklist' })
|
||||||
if (!onlyVideo?.isOwned()) return
|
if (!onlyVideo?.isOwned()) return
|
||||||
|
|
||||||
|
if (!canVideoBeFederated(onlyVideo)) {
|
||||||
|
logger.warn(`Do not process dislike on video ${videoUrl} that cannot be federated`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return sequelizeTypescript.transaction(async t => {
|
return sequelizeTypescript.transaction(async t => {
|
||||||
const video = await VideoModel.loadFull(onlyVideo.id, t)
|
const video = await VideoModel.loadFull(onlyVideo.id, t)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { ActivityLike } from '@peertube/peertube-models'
|
import { ActivityLike } from '@peertube/peertube-models'
|
||||||
|
import { logger } from '@server/helpers/logger.js'
|
||||||
import { VideoModel } from '@server/models/video/video.js'
|
import { VideoModel } from '@server/models/video/video.js'
|
||||||
import { retryTransactionWrapper } from '../../../helpers/database-utils.js'
|
import { retryTransactionWrapper } from '../../../helpers/database-utils.js'
|
||||||
import { sequelizeTypescript } from '../../../initializers/database.js'
|
import { sequelizeTypescript } from '../../../initializers/database.js'
|
||||||
|
@ -6,7 +7,7 @@ import { getAPId } from '../../../lib/activitypub/activity.js'
|
||||||
import { AccountVideoRateModel } from '../../../models/account/account-video-rate.js'
|
import { AccountVideoRateModel } from '../../../models/account/account-video-rate.js'
|
||||||
import { APProcessorOptions } from '../../../types/activitypub-processor.model.js'
|
import { APProcessorOptions } from '../../../types/activitypub-processor.model.js'
|
||||||
import { MActorSignature } from '../../../types/models/index.js'
|
import { MActorSignature } from '../../../types/models/index.js'
|
||||||
import { federateVideoIfNeeded, maybeGetOrCreateAPVideo } from '../videos/index.js'
|
import { canVideoBeFederated, federateVideoIfNeeded, maybeGetOrCreateAPVideo } from '../videos/index.js'
|
||||||
|
|
||||||
async function processLikeActivity (options: APProcessorOptions<ActivityLike>) {
|
async function processLikeActivity (options: APProcessorOptions<ActivityLike>) {
|
||||||
const { activity, byActor } = options
|
const { activity, byActor } = options
|
||||||
|
@ -28,9 +29,14 @@ async function processLikeVideo (byActor: MActorSignature, activity: ActivityLik
|
||||||
const byAccount = byActor.Account
|
const byAccount = byActor.Account
|
||||||
if (!byAccount) throw new Error('Cannot create like with the non account actor ' + byActor.url)
|
if (!byAccount) throw new Error('Cannot create like with the non account actor ' + byActor.url)
|
||||||
|
|
||||||
const { video: onlyVideo } = await maybeGetOrCreateAPVideo({ videoObject: videoUrl, fetchType: 'only-video' })
|
const { video: onlyVideo } = await maybeGetOrCreateAPVideo({ videoObject: videoUrl, fetchType: 'only-video-and-blacklist' })
|
||||||
if (!onlyVideo?.isOwned()) return
|
if (!onlyVideo?.isOwned()) return
|
||||||
|
|
||||||
|
if (!canVideoBeFederated(onlyVideo)) {
|
||||||
|
logger.warn(`Do not process like on video ${videoUrl} that cannot be federated`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return sequelizeTypescript.transaction(async t => {
|
return sequelizeTypescript.transaction(async t => {
|
||||||
const video = await VideoModel.loadFull(onlyVideo.id, t)
|
const video = await VideoModel.loadFull(onlyVideo.id, t)
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { APActorUpdater } from '../actors/updater.js'
|
||||||
import { createOrUpdateCacheFile } from '../cache-file.js'
|
import { createOrUpdateCacheFile } from '../cache-file.js'
|
||||||
import { createOrUpdateVideoPlaylist } from '../playlists/index.js'
|
import { createOrUpdateVideoPlaylist } from '../playlists/index.js'
|
||||||
import { forwardVideoRelatedActivity } from '../send/shared/send-utils.js'
|
import { forwardVideoRelatedActivity } from '../send/shared/send-utils.js'
|
||||||
import { APVideoUpdater, getOrCreateAPVideo } from '../videos/index.js'
|
import { APVideoUpdater, canVideoBeFederated, getOrCreateAPVideo } from '../videos/index.js'
|
||||||
|
|
||||||
async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate<ActivityUpdateObject>>) {
|
async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate<ActivityUpdateObject>>) {
|
||||||
const { activity, byActor } = options
|
const { activity, byActor } = options
|
||||||
|
@ -93,6 +93,11 @@ async function processUpdateCacheFile (
|
||||||
|
|
||||||
const { video } = await getOrCreateAPVideo({ videoObject: cacheFileObject.object })
|
const { video } = await getOrCreateAPVideo({ videoObject: cacheFileObject.object })
|
||||||
|
|
||||||
|
if (video.isOwned() && !canVideoBeFederated(video)) {
|
||||||
|
logger.warn(`Do not process update cache file on video ${activity.object} that cannot be federated`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
await sequelizeTypescript.transaction(async t => {
|
await sequelizeTypescript.transaction(async t => {
|
||||||
await createOrUpdateCacheFile(cacheFileObject, video, byActor, t)
|
await createOrUpdateCacheFile(cacheFileObject, video, byActor, t)
|
||||||
})
|
})
|
||||||
|
|
|
@ -24,7 +24,7 @@ async function processCreateView (activity: ActivityView, byActor: MActorSignatu
|
||||||
|
|
||||||
const { video } = await getOrCreateAPVideo({
|
const { video } = await getOrCreateAPVideo({
|
||||||
videoObject,
|
videoObject,
|
||||||
fetchType: 'only-video',
|
fetchType: 'only-video-and-blacklist',
|
||||||
allowRefresh: false
|
allowRefresh: false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -32,8 +32,8 @@ async function processCreateView (activity: ActivityView, byActor: MActorSignatu
|
||||||
video,
|
video,
|
||||||
viewerId: activity.id,
|
viewerId: activity.id,
|
||||||
|
|
||||||
viewerExpires: activity.expires
|
viewerExpires: getExpires(activity)
|
||||||
? new Date(activity.expires)
|
? new Date(getExpires(activity))
|
||||||
: undefined,
|
: undefined,
|
||||||
viewerResultCounter: getViewerResultCounter(activity)
|
viewerResultCounter: getViewerResultCounter(activity)
|
||||||
})
|
})
|
||||||
|
@ -49,10 +49,15 @@ async function processCreateView (activity: ActivityView, byActor: MActorSignatu
|
||||||
function getViewerResultCounter (activity: ActivityView) {
|
function getViewerResultCounter (activity: ActivityView) {
|
||||||
const result = activity.result
|
const result = activity.result
|
||||||
|
|
||||||
if (!activity.expires || result?.interactionType !== 'WatchAction' || result?.type !== 'InteractionCounter') return undefined
|
if (!getExpires(activity) || result?.interactionType !== 'WatchAction' || result?.type !== 'InteractionCounter') return undefined
|
||||||
|
|
||||||
const counter = parseInt(result.userInteractionCount + '')
|
const counter = parseInt(result.userInteractionCount + '')
|
||||||
if (isNaN(counter)) return undefined
|
if (isNaN(counter)) return undefined
|
||||||
|
|
||||||
return counter
|
return counter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: compat with < 6.1, remove in 7.0
|
||||||
|
function getExpires (activity: ActivityView) {
|
||||||
|
return activity.expires || activity['expiration'] as string
|
||||||
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions<Act
|
||||||
View: processViewActivity
|
View: processViewActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processActivities (
|
export async function processActivities (
|
||||||
activities: Activity[],
|
activities: Activity[],
|
||||||
options: {
|
options: {
|
||||||
signatureActor?: MActorSignature
|
signatureActor?: MActorSignature
|
||||||
|
@ -86,7 +86,3 @@ async function processActivities (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
|
||||||
processActivities
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import { Transaction } from 'sequelize'
|
|
||||||
import { getServerActor } from '@server/models/application/application.js'
|
|
||||||
import {
|
import {
|
||||||
ActivityAudience,
|
ActivityAudience,
|
||||||
ActivityCreate,
|
ActivityCreate,
|
||||||
|
@ -9,19 +7,24 @@ import {
|
||||||
VideoPlaylistPrivacy,
|
VideoPlaylistPrivacy,
|
||||||
VideoPrivacy
|
VideoPrivacy
|
||||||
} from '@peertube/peertube-models'
|
} from '@peertube/peertube-models'
|
||||||
|
import { AccountModel } from '@server/models/account/account.js'
|
||||||
|
import { getServerActor } from '@server/models/application/application.js'
|
||||||
|
import { VideoModel } from '@server/models/video/video.js'
|
||||||
|
import { Transaction } from 'sequelize'
|
||||||
import { logger, loggerTagsFactory } from '../../../helpers/logger.js'
|
import { logger, loggerTagsFactory } from '../../../helpers/logger.js'
|
||||||
import { VideoCommentModel } from '../../../models/video/video-comment.js'
|
import { VideoCommentModel } from '../../../models/video/video-comment.js'
|
||||||
import {
|
import {
|
||||||
MActorLight,
|
MActorLight,
|
||||||
MCommentOwnerVideo,
|
MCommentOwnerVideo,
|
||||||
MLocalVideoViewerWithWatchSections,
|
MLocalVideoViewerWithWatchSections,
|
||||||
MVideoAccountLight,
|
|
||||||
MVideoAP,
|
MVideoAP,
|
||||||
|
MVideoAccountLight,
|
||||||
MVideoPlaylistFull,
|
MVideoPlaylistFull,
|
||||||
MVideoRedundancyFileVideo,
|
MVideoRedundancyFileVideo,
|
||||||
MVideoRedundancyStreamingPlaylistVideo
|
MVideoRedundancyStreamingPlaylistVideo
|
||||||
} from '../../../types/models/index.js'
|
} from '../../../types/models/index.js'
|
||||||
import { audiencify, getAudience } from '../audience.js'
|
import { audiencify, getAudience } from '../audience.js'
|
||||||
|
import { canVideoBeFederated } from '../videos/federate.js'
|
||||||
import {
|
import {
|
||||||
broadcastToActors,
|
broadcastToActors,
|
||||||
broadcastToFollowers,
|
broadcastToFollowers,
|
||||||
|
@ -32,12 +35,11 @@ import {
|
||||||
sendVideoRelatedActivity,
|
sendVideoRelatedActivity,
|
||||||
unicastTo
|
unicastTo
|
||||||
} from './shared/index.js'
|
} from './shared/index.js'
|
||||||
import { AccountModel } from '@server/models/account/account.js'
|
|
||||||
|
|
||||||
const lTags = loggerTagsFactory('ap', 'create')
|
const lTags = loggerTagsFactory('ap', 'create')
|
||||||
|
|
||||||
async function sendCreateVideo (video: MVideoAP, transaction: Transaction) {
|
export async function sendCreateVideo (video: MVideoAP, transaction: Transaction) {
|
||||||
if (!video.hasPrivacyForFederation()) return undefined
|
if (!canVideoBeFederated(video)) return undefined
|
||||||
|
|
||||||
logger.info('Creating job to send video creation of %s.', video.url, lTags(video.uuid))
|
logger.info('Creating job to send video creation of %s.', video.url, lTags(video.uuid))
|
||||||
|
|
||||||
|
@ -56,7 +58,7 @@ async function sendCreateVideo (video: MVideoAP, transaction: Transaction) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendCreateCacheFile (
|
export async function sendCreateCacheFile (
|
||||||
byActor: MActorLight,
|
byActor: MActorLight,
|
||||||
video: MVideoAccountLight,
|
video: MVideoAccountLight,
|
||||||
fileRedundancy: MVideoRedundancyStreamingPlaylistVideo | MVideoRedundancyFileVideo
|
fileRedundancy: MVideoRedundancyStreamingPlaylistVideo | MVideoRedundancyFileVideo
|
||||||
|
@ -72,7 +74,7 @@ async function sendCreateCacheFile (
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendCreateWatchAction (stats: MLocalVideoViewerWithWatchSections, transaction: Transaction) {
|
export async function sendCreateWatchAction (stats: MLocalVideoViewerWithWatchSections, transaction: Transaction) {
|
||||||
logger.info('Creating job to send create watch action %s.', stats.url, lTags(stats.uuid))
|
logger.info('Creating job to send create watch action %s.', stats.url, lTags(stats.uuid))
|
||||||
|
|
||||||
const byActor = await getServerActor()
|
const byActor = await getServerActor()
|
||||||
|
@ -84,7 +86,7 @@ async function sendCreateWatchAction (stats: MLocalVideoViewerWithWatchSections,
|
||||||
return sendVideoActivityToOrigin(activityBuilder, { byActor, video: stats.Video, transaction, contextType: 'WatchAction' })
|
return sendVideoActivityToOrigin(activityBuilder, { byActor, video: stats.Video, transaction, contextType: 'WatchAction' })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, transaction: Transaction) {
|
export async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, transaction: Transaction) {
|
||||||
if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
|
if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
|
||||||
|
|
||||||
logger.info('Creating job to send create video playlist of %s.', playlist.url, lTags(playlist.uuid))
|
logger.info('Creating job to send create video playlist of %s.', playlist.url, lTags(playlist.uuid))
|
||||||
|
@ -109,11 +111,20 @@ async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, transactio
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendCreateVideoComment (comment: MCommentOwnerVideo, transaction: Transaction) {
|
export async function sendCreateVideoComment (comment: MCommentOwnerVideo, transaction: Transaction) {
|
||||||
logger.info('Creating job to send comment %s.', comment.url)
|
|
||||||
|
|
||||||
const isOrigin = comment.Video.isOwned()
|
const isOrigin = comment.Video.isOwned()
|
||||||
|
|
||||||
|
if (isOrigin) {
|
||||||
|
const videoWithBlacklist = await VideoModel.loadWithBlacklist(comment.Video.id)
|
||||||
|
|
||||||
|
if (!canVideoBeFederated(videoWithBlacklist)) {
|
||||||
|
logger.debug(`Do not send comment ${comment.url} on a video that cannot be federated`)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('Creating job to send comment %s.', comment.url)
|
||||||
|
|
||||||
const byActor = comment.Account.Actor
|
const byActor = comment.Account.Actor
|
||||||
const videoAccount = await AccountModel.load(comment.Video.VideoChannel.Account.id, transaction)
|
const videoAccount = await AccountModel.load(comment.Video.VideoChannel.Account.id, transaction)
|
||||||
|
|
||||||
|
@ -179,7 +190,7 @@ async function sendCreateVideoComment (comment: MCommentOwnerVideo, transaction:
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildCreateActivity <T extends ActivityCreateObject> (
|
export function buildCreateActivity <T extends ActivityCreateObject> (
|
||||||
url: string,
|
url: string,
|
||||||
byActor: MActorLight,
|
byActor: MActorLight,
|
||||||
object: T,
|
object: T,
|
||||||
|
@ -201,16 +212,7 @@ function buildCreateActivity <T extends ActivityCreateObject> (
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
// Private
|
||||||
export {
|
|
||||||
sendCreateVideo,
|
|
||||||
buildCreateActivity,
|
|
||||||
sendCreateVideoComment,
|
|
||||||
sendCreateVideoPlaylist,
|
|
||||||
sendCreateCacheFile,
|
|
||||||
sendCreateWatchAction
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function sendVideoRelatedCreateActivity (options: {
|
async function sendVideoRelatedCreateActivity (options: {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { Transaction } from 'sequelize'
|
|
||||||
import { getServerActor } from '@server/models/application/application.js'
|
|
||||||
import { ActivityAudience, ActivityUpdate, ActivityUpdateObject, VideoPlaylistPrivacy, VideoPrivacy } from '@peertube/peertube-models'
|
import { ActivityAudience, ActivityUpdate, ActivityUpdateObject, VideoPlaylistPrivacy, VideoPrivacy } from '@peertube/peertube-models'
|
||||||
|
import { getServerActor } from '@server/models/application/application.js'
|
||||||
|
import { Transaction } from 'sequelize'
|
||||||
import { logger } from '../../../helpers/logger.js'
|
import { logger } from '../../../helpers/logger.js'
|
||||||
import { AccountModel } from '../../../models/account/account.js'
|
import { AccountModel } from '../../../models/account/account.js'
|
||||||
import { VideoModel } from '../../../models/video/video.js'
|
|
||||||
import { VideoShareModel } from '../../../models/video/video-share.js'
|
import { VideoShareModel } from '../../../models/video/video-share.js'
|
||||||
|
import { VideoModel } from '../../../models/video/video.js'
|
||||||
import {
|
import {
|
||||||
MAccountDefault,
|
MAccountDefault,
|
||||||
MActor,
|
MActor,
|
||||||
|
@ -16,11 +16,12 @@ import {
|
||||||
} from '../../../types/models/index.js'
|
} from '../../../types/models/index.js'
|
||||||
import { audiencify, getAudience } from '../audience.js'
|
import { audiencify, getAudience } from '../audience.js'
|
||||||
import { getUpdateActivityPubUrl } from '../url.js'
|
import { getUpdateActivityPubUrl } from '../url.js'
|
||||||
|
import { canVideoBeFederated } from '../videos/federate.js'
|
||||||
import { getActorsInvolvedInVideo } from './shared/index.js'
|
import { getActorsInvolvedInVideo } from './shared/index.js'
|
||||||
import { broadcastToFollowers, sendVideoRelatedActivity } from './shared/send-utils.js'
|
import { broadcastToFollowers, sendVideoRelatedActivity } from './shared/send-utils.js'
|
||||||
|
|
||||||
async function sendUpdateVideo (videoArg: MVideoAPLight, transaction: Transaction, overriddenByActor?: MActor) {
|
export async function sendUpdateVideo (videoArg: MVideoAPLight, transaction: Transaction, overriddenByActor?: MActor) {
|
||||||
if (!videoArg.hasPrivacyForFederation()) return undefined
|
if (!canVideoBeFederated(videoArg)) return undefined
|
||||||
|
|
||||||
const video = await videoArg.lightAPToFullAP(transaction)
|
const video = await videoArg.lightAPToFullAP(transaction)
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ async function sendUpdateVideo (videoArg: MVideoAPLight, transaction: Transactio
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefault, transaction: Transaction) {
|
export async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefault, transaction: Transaction) {
|
||||||
const byActor = accountOrChannel.Actor
|
const byActor = accountOrChannel.Actor
|
||||||
|
|
||||||
logger.info('Creating job to update actor %s.', byActor.url)
|
logger.info('Creating job to update actor %s.', byActor.url)
|
||||||
|
@ -77,7 +78,7 @@ async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefa
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVideoRedundancyVideo) {
|
export async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVideoRedundancyVideo) {
|
||||||
logger.info('Creating job to update cache file %s.', redundancyModel.url)
|
logger.info('Creating job to update cache file %s.', redundancyModel.url)
|
||||||
|
|
||||||
const associatedVideo = redundancyModel.getVideo()
|
const associatedVideo = redundancyModel.getVideo()
|
||||||
|
@ -98,7 +99,7 @@ async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVide
|
||||||
return sendVideoRelatedActivity(activityBuilder, { byActor, video, contextType: 'CacheFile' })
|
return sendVideoRelatedActivity(activityBuilder, { byActor, video, contextType: 'CacheFile' })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, transaction: Transaction) {
|
export async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, transaction: Transaction) {
|
||||||
if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
|
if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
|
||||||
|
|
||||||
const byActor = videoPlaylist.OwnerAccount.Actor
|
const byActor = videoPlaylist.OwnerAccount.Actor
|
||||||
|
@ -127,14 +128,7 @@ async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, trans
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
// Private
|
||||||
export {
|
|
||||||
sendUpdateActor,
|
|
||||||
sendUpdateVideo,
|
|
||||||
sendUpdateCacheFile,
|
|
||||||
sendUpdateVideoPlaylist
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function buildUpdateActivity (
|
function buildUpdateActivity (
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
import { Transaction } from 'sequelize'
|
|
||||||
import { ACTIVITY_PUB } from '@server/initializers/constants.js'
|
|
||||||
import { ActorModel } from '@server/models/actor/actor.js'
|
|
||||||
import { VideoModel } from '@server/models/video/video.js'
|
|
||||||
import { VideoShareModel } from '@server/models/video/video-share.js'
|
|
||||||
import { MActorFollowersUrl, MActorUrl, MCommentOwner, MCommentOwnerVideo, MVideoId } from '@server/types/models/index.js'
|
|
||||||
import { ActivityAudience } from '@peertube/peertube-models'
|
import { ActivityAudience } from '@peertube/peertube-models'
|
||||||
|
import { getAPPublicValue } from '@server/helpers/activity-pub-utils.js'
|
||||||
|
import { ActorModel } from '@server/models/actor/actor.js'
|
||||||
|
import { VideoShareModel } from '@server/models/video/video-share.js'
|
||||||
|
import { VideoModel } from '@server/models/video/video.js'
|
||||||
|
import { MActorFollowersUrl, MActorUrl, MCommentOwner, MCommentOwnerVideo, MVideoId } from '@server/types/models/index.js'
|
||||||
|
import { Transaction } from 'sequelize'
|
||||||
|
|
||||||
function getOriginVideoAudience (accountActor: MActorUrl, actorsInvolvedInVideo: MActorFollowersUrl[] = []): ActivityAudience {
|
export function getOriginVideoAudience (accountActor: MActorUrl, actorsInvolvedInVideo: MActorFollowersUrl[] = []): ActivityAudience {
|
||||||
return {
|
return {
|
||||||
to: [ accountActor.url ],
|
to: [ accountActor.url ],
|
||||||
cc: actorsInvolvedInVideo.map(a => a.followersUrl)
|
cc: actorsInvolvedInVideo.map(a => a.followersUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVideoCommentAudience (
|
export function getVideoCommentAudience (
|
||||||
videoComment: MCommentOwnerVideo,
|
videoComment: MCommentOwnerVideo,
|
||||||
threadParentComments: MCommentOwner[],
|
threadParentComments: MCommentOwner[],
|
||||||
actorsInvolvedInVideo: MActorFollowersUrl[],
|
actorsInvolvedInVideo: MActorFollowersUrl[],
|
||||||
isOrigin = false
|
isOrigin = false
|
||||||
): ActivityAudience {
|
): ActivityAudience {
|
||||||
const to = [ ACTIVITY_PUB.PUBLIC ]
|
const to = [ getAPPublicValue() ]
|
||||||
const cc: string[] = []
|
const cc: string[] = []
|
||||||
|
|
||||||
// Owner of the video we comment
|
// Owner of the video we comment
|
||||||
|
@ -43,14 +43,14 @@ function getVideoCommentAudience (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAudienceFromFollowersOf (actorsInvolvedInObject: MActorFollowersUrl[]): ActivityAudience {
|
export function getAudienceFromFollowersOf (actorsInvolvedInObject: MActorFollowersUrl[]): ActivityAudience {
|
||||||
return {
|
return {
|
||||||
to: [ ACTIVITY_PUB.PUBLIC ].concat(actorsInvolvedInObject.map(a => a.followersUrl)),
|
to: [ getAPPublicValue() ].concat(actorsInvolvedInObject.map(a => a.followersUrl)),
|
||||||
cc: []
|
cc: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getActorsInvolvedInVideo (video: MVideoId, t: Transaction) {
|
export async function getActorsInvolvedInVideo (video: MVideoId, t: Transaction) {
|
||||||
const actors = await VideoShareModel.listActorIdsAndFollowerUrlsByShare(video.id, t)
|
const actors = await VideoShareModel.listActorIdsAndFollowerUrlsByShare(video.id, t)
|
||||||
|
|
||||||
const alreadyLoadedActor = (video as VideoModel).VideoChannel?.Account?.Actor
|
const alreadyLoadedActor = (video as VideoModel).VideoChannel?.Account?.Actor
|
||||||
|
@ -63,12 +63,3 @@ async function getActorsInvolvedInVideo (video: MVideoId, t: Transaction) {
|
||||||
|
|
||||||
return actors
|
return actors
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export {
|
|
||||||
getOriginVideoAudience,
|
|
||||||
getActorsInvolvedInVideo,
|
|
||||||
getAudienceFromFollowersOf,
|
|
||||||
getVideoCommentAudience
|
|
||||||
}
|
|
||||||
|
|
|
@ -258,7 +258,6 @@ function unicastTo (options: {
|
||||||
export {
|
export {
|
||||||
broadcastToFollowers,
|
broadcastToFollowers,
|
||||||
unicastTo,
|
unicastTo,
|
||||||
forwardActivity,
|
|
||||||
broadcastToActors,
|
broadcastToActors,
|
||||||
sendVideoActivityToOrigin,
|
sendVideoActivityToOrigin,
|
||||||
forwardVideoRelatedActivity,
|
forwardVideoRelatedActivity,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
import { getServerActor } from '@server/models/application/application.js'
|
||||||
import Bluebird from 'bluebird'
|
import Bluebird from 'bluebird'
|
||||||
import { Transaction } from 'sequelize'
|
import { Transaction } from 'sequelize'
|
||||||
import { getServerActor } from '@server/models/application/application.js'
|
|
||||||
import { logger, loggerTagsFactory } from '../../helpers/logger.js'
|
import { logger, loggerTagsFactory } from '../../helpers/logger.js'
|
||||||
import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants.js'
|
import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants.js'
|
||||||
import { VideoShareModel } from '../../models/video/video-share.js'
|
import { VideoShareModel } from '../../models/video/video-share.js'
|
||||||
|
@ -12,16 +12,7 @@ import { checkUrlsSameHost, getLocalVideoAnnounceActivityPubUrl } from './url.js
|
||||||
|
|
||||||
const lTags = loggerTagsFactory('share')
|
const lTags = loggerTagsFactory('share')
|
||||||
|
|
||||||
async function shareVideoByServerAndChannel (video: MVideoAccountLight, t: Transaction) {
|
export async function changeVideoChannelShare (
|
||||||
if (!video.hasPrivacyForFederation()) return undefined
|
|
||||||
|
|
||||||
return Promise.all([
|
|
||||||
shareByServer(video, t),
|
|
||||||
shareByVideoChannel(video, t)
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
async function changeVideoChannelShare (
|
|
||||||
video: MVideoAccountLight,
|
video: MVideoAccountLight,
|
||||||
oldVideoChannel: MChannelActorLight,
|
oldVideoChannel: MChannelActorLight,
|
||||||
t: Transaction
|
t: Transaction
|
||||||
|
@ -36,7 +27,7 @@ async function changeVideoChannelShare (
|
||||||
await shareByVideoChannel(video, t)
|
await shareByVideoChannel(video, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addVideoShares (shareUrls: string[], video: MVideoId) {
|
export async function addVideoShares (shareUrls: string[], video: MVideoId) {
|
||||||
await Bluebird.map(shareUrls, async shareUrl => {
|
await Bluebird.map(shareUrls, async shareUrl => {
|
||||||
try {
|
try {
|
||||||
await addVideoShare(shareUrl, video)
|
await addVideoShare(shareUrl, video)
|
||||||
|
@ -46,12 +37,44 @@ async function addVideoShares (shareUrls: string[], video: MVideoId) {
|
||||||
}, { concurrency: CRAWL_REQUEST_CONCURRENCY })
|
}, { concurrency: CRAWL_REQUEST_CONCURRENCY })
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export async function shareByServer (video: MVideo, t: Transaction) {
|
||||||
changeVideoChannelShare,
|
const serverActor = await getServerActor()
|
||||||
addVideoShares,
|
|
||||||
shareVideoByServerAndChannel
|
const serverShareUrl = getLocalVideoAnnounceActivityPubUrl(serverActor, video)
|
||||||
|
const [ serverShare ] = await VideoShareModel.findOrCreate({
|
||||||
|
defaults: {
|
||||||
|
actorId: serverActor.id,
|
||||||
|
videoId: video.id,
|
||||||
|
url: serverShareUrl
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
url: serverShareUrl
|
||||||
|
},
|
||||||
|
transaction: t
|
||||||
|
})
|
||||||
|
|
||||||
|
return sendVideoAnnounce(serverActor, serverShare, video, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function shareByVideoChannel (video: MVideoAccountLight, t: Transaction) {
|
||||||
|
const videoChannelShareUrl = getLocalVideoAnnounceActivityPubUrl(video.VideoChannel.Actor, video)
|
||||||
|
const [ videoChannelShare ] = await VideoShareModel.findOrCreate({
|
||||||
|
defaults: {
|
||||||
|
actorId: video.VideoChannel.actorId,
|
||||||
|
videoId: video.id,
|
||||||
|
url: videoChannelShareUrl
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
url: videoChannelShareUrl
|
||||||
|
},
|
||||||
|
transaction: t
|
||||||
|
})
|
||||||
|
|
||||||
|
return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Private
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function addVideoShare (shareUrl: string, video: MVideoId) {
|
async function addVideoShare (shareUrl: string, video: MVideoId) {
|
||||||
|
@ -74,42 +97,6 @@ async function addVideoShare (shareUrl: string, video: MVideoId) {
|
||||||
await VideoShareModel.upsert(entry)
|
await VideoShareModel.upsert(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function shareByServer (video: MVideo, t: Transaction) {
|
|
||||||
const serverActor = await getServerActor()
|
|
||||||
|
|
||||||
const serverShareUrl = getLocalVideoAnnounceActivityPubUrl(serverActor, video)
|
|
||||||
const [ serverShare ] = await VideoShareModel.findOrCreate({
|
|
||||||
defaults: {
|
|
||||||
actorId: serverActor.id,
|
|
||||||
videoId: video.id,
|
|
||||||
url: serverShareUrl
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
url: serverShareUrl
|
|
||||||
},
|
|
||||||
transaction: t
|
|
||||||
})
|
|
||||||
|
|
||||||
return sendVideoAnnounce(serverActor, serverShare, video, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function shareByVideoChannel (video: MVideoAccountLight, t: Transaction) {
|
|
||||||
const videoChannelShareUrl = getLocalVideoAnnounceActivityPubUrl(video.VideoChannel.Actor, video)
|
|
||||||
const [ videoChannelShare ] = await VideoShareModel.findOrCreate({
|
|
||||||
defaults: {
|
|
||||||
actorId: video.VideoChannel.actorId,
|
|
||||||
videoId: video.id,
|
|
||||||
url: videoChannelShareUrl
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
url: videoChannelShareUrl
|
|
||||||
},
|
|
||||||
transaction: t
|
|
||||||
})
|
|
||||||
|
|
||||||
return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function undoShareByVideoChannel (video: MVideo, oldVideoChannel: MChannelActorLight, t: Transaction) {
|
async function undoShareByVideoChannel (video: MVideo, oldVideoChannel: MChannelActorLight, t: Transaction) {
|
||||||
// Load old share
|
// Load old share
|
||||||
const oldShare = await VideoShareModel.load(oldVideoChannel.actorId, video.id, t)
|
const oldShare = await VideoShareModel.load(oldVideoChannel.actorId, video.id, t)
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { Hooks } from '../plugins/hooks.js'
|
||||||
import { fetchAP } from './activity.js'
|
import { fetchAP } from './activity.js'
|
||||||
import { getOrCreateAPActor } from './actors/index.js'
|
import { getOrCreateAPActor } from './actors/index.js'
|
||||||
import { checkUrlsSameHost } from './url.js'
|
import { checkUrlsSameHost } from './url.js'
|
||||||
import { getOrCreateAPVideo } from './videos/index.js'
|
import { canVideoBeFederated, getOrCreateAPVideo } from './videos/index.js'
|
||||||
|
|
||||||
type ResolveThreadParams = {
|
type ResolveThreadParams = {
|
||||||
url: string
|
url: string
|
||||||
|
@ -92,8 +92,8 @@ async function tryToResolveThreadFromVideo (params: ResolveThreadParams) {
|
||||||
const syncParam = { rates: true, shares: true, comments: false, refreshVideo: false }
|
const syncParam = { rates: true, shares: true, comments: false, refreshVideo: false }
|
||||||
const { video } = await getOrCreateAPVideo({ videoObject: url, syncParam })
|
const { video } = await getOrCreateAPVideo({ videoObject: url, syncParam })
|
||||||
|
|
||||||
if (video.isOwned() && !video.hasPrivacyForFederation()) {
|
if (video.isOwned() && !canVideoBeFederated(video)) {
|
||||||
throw new Error('Cannot resolve thread of video with privacy that is not compatible with federation')
|
throw new Error('Cannot resolve thread of video that is not compatible with federation')
|
||||||
}
|
}
|
||||||
|
|
||||||
let resultComment: MCommentOwnerVideo
|
let resultComment: MCommentOwnerVideo
|
||||||
|
|
|
@ -1,29 +1,53 @@
|
||||||
|
import { forceNumber } from '@peertube/peertube-core-utils'
|
||||||
|
import { VideoPrivacy, VideoPrivacyType, VideoState, VideoStateType } from '@peertube/peertube-models'
|
||||||
|
import { CONFIG } from '@server/initializers/config.js'
|
||||||
|
import { MVideoAPLight, MVideoWithBlacklistRights } from '@server/types/models/index.js'
|
||||||
import { Transaction } from 'sequelize'
|
import { Transaction } from 'sequelize'
|
||||||
import { MVideoAP, MVideoAPLight } from '@server/types/models/index.js'
|
|
||||||
import { sendCreateVideo, sendUpdateVideo } from '../send/index.js'
|
import { sendCreateVideo, sendUpdateVideo } from '../send/index.js'
|
||||||
import { shareVideoByServerAndChannel } from '../share.js'
|
import { shareByServer, shareByVideoChannel } from '../share.js'
|
||||||
|
|
||||||
async function federateVideoIfNeeded (videoArg: MVideoAPLight, isNewVideo: boolean, transaction?: Transaction) {
|
export async function federateVideoIfNeeded (videoArg: MVideoAPLight, isNewVideo: boolean, transaction?: Transaction) {
|
||||||
const video = videoArg as MVideoAP
|
if (!canVideoBeFederated(videoArg, isNewVideo)) return
|
||||||
|
|
||||||
if (
|
const video = await videoArg.lightAPToFullAP(transaction)
|
||||||
// Check this is not a blacklisted video, or unfederated blacklisted video
|
|
||||||
(video.isBlacklisted() === false || (isNewVideo === false && video.VideoBlacklist.unfederated === false)) &&
|
|
||||||
// Check the video is public/unlisted and published
|
|
||||||
video.hasPrivacyForFederation() && video.hasStateForFederation()
|
|
||||||
) {
|
|
||||||
const video = await videoArg.lightAPToFullAP(transaction)
|
|
||||||
|
|
||||||
if (isNewVideo) {
|
if (isNewVideo) {
|
||||||
// Now we'll add the video's meta data to our followers
|
// Now we'll add the video's meta data to our followers
|
||||||
await sendCreateVideo(video, transaction)
|
await sendCreateVideo(video, transaction)
|
||||||
await shareVideoByServerAndChannel(video, transaction)
|
|
||||||
} else {
|
await Promise.all([
|
||||||
await sendUpdateVideo(video, transaction)
|
shareByServer(video, transaction),
|
||||||
}
|
shareByVideoChannel(video, transaction)
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
await sendUpdateVideo(video, transaction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export function canVideoBeFederated (video: MVideoWithBlacklistRights, isNewVideo = false) {
|
||||||
federateVideoIfNeeded
|
// Check this is not a blacklisted video
|
||||||
|
if (video.isBlacklisted() === true) {
|
||||||
|
if (isNewVideo === false) return false
|
||||||
|
if (video.VideoBlacklist.unfederated === true) return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the video is public/unlisted and published
|
||||||
|
return isPrivacyForFederation(video.privacy) && isStateForFederation(video.state)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNewVideoPrivacyForFederation (currentPrivacy: VideoPrivacyType, newPrivacy: VideoPrivacyType) {
|
||||||
|
return !isPrivacyForFederation(currentPrivacy) && isPrivacyForFederation(newPrivacy)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPrivacyForFederation (privacy: VideoPrivacyType) {
|
||||||
|
const castedPrivacy = forceNumber(privacy)
|
||||||
|
|
||||||
|
return castedPrivacy === VideoPrivacy.PUBLIC ||
|
||||||
|
(CONFIG.FEDERATION.VIDEOS.FEDERATE_UNLISTED === true && castedPrivacy === VideoPrivacy.UNLISTED)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isStateForFederation (state: VideoStateType) {
|
||||||
|
const castedState = forceNumber(state)
|
||||||
|
|
||||||
|
return castedState === VideoState.PUBLISHED || castedState === VideoState.WAITING_FOR_LIVE || castedState === VideoState.LIVE_ENDED
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
|
import { APObjectId } from '@peertube/peertube-models'
|
||||||
import { retryTransactionWrapper } from '@server/helpers/database-utils.js'
|
import { retryTransactionWrapper } from '@server/helpers/database-utils.js'
|
||||||
import { logger } from '@server/helpers/logger.js'
|
import { logger } from '@server/helpers/logger.js'
|
||||||
import { JobQueue } from '@server/lib/job-queue/index.js'
|
import { JobQueue } from '@server/lib/job-queue/index.js'
|
||||||
import { loadVideoByUrl, VideoLoadByUrlType } from '@server/lib/model-loaders/index.js'
|
import { loadVideoByUrl, VideoLoadByUrlType } from '@server/lib/model-loaders/index.js'
|
||||||
import { MVideoAccountLightBlacklistAllFiles, MVideoImmutable, MVideoThumbnail } from '@server/types/models/index.js'
|
import {
|
||||||
import { APObjectId } from '@peertube/peertube-models'
|
MVideoAccountLightBlacklistAllFiles,
|
||||||
|
MVideoImmutable,
|
||||||
|
MVideoThumbnail,
|
||||||
|
MVideoThumbnailBlacklist
|
||||||
|
} from '@server/types/models/index.js'
|
||||||
import { getAPId } from '../activity.js'
|
import { getAPId } from '../activity.js'
|
||||||
import { refreshVideoIfNeeded } from './refresh.js'
|
import { refreshVideoIfNeeded } from './refresh.js'
|
||||||
import { APVideoCreator, fetchRemoteVideo, SyncParam, syncVideoExternalAttributes } from './shared/index.js'
|
import { APVideoCreator, fetchRemoteVideo, SyncParam, syncVideoExternalAttributes } from './shared/index.js'
|
||||||
|
@ -24,23 +29,25 @@ type GetVideoParamAll = {
|
||||||
type GetVideoParamImmutable = {
|
type GetVideoParamImmutable = {
|
||||||
videoObject: APObjectId
|
videoObject: APObjectId
|
||||||
syncParam?: SyncParam
|
syncParam?: SyncParam
|
||||||
fetchType: 'only-immutable-attributes'
|
fetchType: 'unsafe-only-immutable-attributes'
|
||||||
allowRefresh: false
|
allowRefresh: false
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetVideoParamOther = {
|
type GetVideoParamOther = {
|
||||||
videoObject: APObjectId
|
videoObject: APObjectId
|
||||||
syncParam?: SyncParam
|
syncParam?: SyncParam
|
||||||
fetchType?: 'all' | 'only-video'
|
fetchType?: 'all' | 'only-video-and-blacklist'
|
||||||
allowRefresh?: boolean
|
allowRefresh?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getOrCreateAPVideo (options: GetVideoParamAll): GetVideoResult<MVideoAccountLightBlacklistAllFiles>
|
export function getOrCreateAPVideo (options: GetVideoParamAll): GetVideoResult<MVideoAccountLightBlacklistAllFiles>
|
||||||
export function getOrCreateAPVideo (options: GetVideoParamImmutable): GetVideoResult<MVideoImmutable>
|
export function getOrCreateAPVideo (options: GetVideoParamImmutable): GetVideoResult<MVideoImmutable>
|
||||||
export function getOrCreateAPVideo (options: GetVideoParamOther): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail>
|
export function getOrCreateAPVideo (
|
||||||
|
options: GetVideoParamOther
|
||||||
|
): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnailBlacklist>
|
||||||
export async function getOrCreateAPVideo (
|
export async function getOrCreateAPVideo (
|
||||||
options: GetVideoParamAll | GetVideoParamImmutable | GetVideoParamOther
|
options: GetVideoParamAll | GetVideoParamImmutable | GetVideoParamOther
|
||||||
): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail | MVideoImmutable> {
|
): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnailBlacklist | MVideoImmutable> {
|
||||||
// Default params
|
// Default params
|
||||||
const syncParam = options.syncParam || { rates: true, shares: true, comments: true, refreshVideo: false }
|
const syncParam = options.syncParam || { rates: true, shares: true, comments: true, refreshVideo: false }
|
||||||
const fetchType = options.fetchType || 'all'
|
const fetchType = options.fetchType || 'all'
|
||||||
|
@ -52,7 +59,7 @@ export async function getOrCreateAPVideo (
|
||||||
|
|
||||||
if (videoFromDatabase) {
|
if (videoFromDatabase) {
|
||||||
if (allowRefresh === true) {
|
if (allowRefresh === true) {
|
||||||
// Typings ensure allowRefresh === false in only-immutable-attributes fetch type
|
// Typings ensure allowRefresh === false in unsafe-only-immutable-attributes fetch type
|
||||||
videoFromDatabase = await scheduleRefresh(videoFromDatabase as MVideoThumbnail, fetchType, syncParam)
|
videoFromDatabase = await scheduleRefresh(videoFromDatabase as MVideoThumbnail, fetchType, syncParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +94,9 @@ export async function getOrCreateAPVideo (
|
||||||
|
|
||||||
export function maybeGetOrCreateAPVideo (options: GetVideoParamAll): GetVideoResult<MVideoAccountLightBlacklistAllFiles>
|
export function maybeGetOrCreateAPVideo (options: GetVideoParamAll): GetVideoResult<MVideoAccountLightBlacklistAllFiles>
|
||||||
export function maybeGetOrCreateAPVideo (options: GetVideoParamImmutable): GetVideoResult<MVideoImmutable>
|
export function maybeGetOrCreateAPVideo (options: GetVideoParamImmutable): GetVideoResult<MVideoImmutable>
|
||||||
export function maybeGetOrCreateAPVideo (options: GetVideoParamOther): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail>
|
export function maybeGetOrCreateAPVideo (
|
||||||
|
options: GetVideoParamOther
|
||||||
|
): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnailBlacklist>
|
||||||
export async function maybeGetOrCreateAPVideo (options: GetVideoParamAll | GetVideoParamImmutable | GetVideoParamOther) {
|
export async function maybeGetOrCreateAPVideo (options: GetVideoParamAll | GetVideoParamImmutable | GetVideoParamOther) {
|
||||||
try {
|
try {
|
||||||
const result = await getOrCreateAPVideo(options as any)
|
const result = await getOrCreateAPVideo(options as any)
|
||||||
|
|
|
@ -11,13 +11,14 @@ import {
|
||||||
VideoPrivacy,
|
VideoPrivacy,
|
||||||
VideoStreamingPlaylistType
|
VideoStreamingPlaylistType
|
||||||
} from '@peertube/peertube-models'
|
} from '@peertube/peertube-models'
|
||||||
|
import { hasAPPublic } from '@server/helpers/activity-pub-utils.js'
|
||||||
import { isAPVideoFileUrlMetadataObject } from '@server/helpers/custom-validators/activitypub/videos.js'
|
import { isAPVideoFileUrlMetadataObject } from '@server/helpers/custom-validators/activitypub/videos.js'
|
||||||
import { isArray } from '@server/helpers/custom-validators/misc.js'
|
import { isArray } from '@server/helpers/custom-validators/misc.js'
|
||||||
import { isVideoFileInfoHashValid } from '@server/helpers/custom-validators/videos.js'
|
import { isVideoFileInfoHashValid } from '@server/helpers/custom-validators/videos.js'
|
||||||
import { generateImageFilename } from '@server/helpers/image-utils.js'
|
import { generateImageFilename } from '@server/helpers/image-utils.js'
|
||||||
import { logger } from '@server/helpers/logger.js'
|
import { logger } from '@server/helpers/logger.js'
|
||||||
import { getExtFromMimetype } from '@server/helpers/video.js'
|
import { getExtFromMimetype } from '@server/helpers/video.js'
|
||||||
import { ACTIVITY_PUB, MIMETYPES, P2P_MEDIA_LOADER_PEER_VERSION, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '@server/initializers/constants.js'
|
import { MIMETYPES, P2P_MEDIA_LOADER_PEER_VERSION, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '@server/initializers/constants.js'
|
||||||
import { generateTorrentFileName } from '@server/lib/paths.js'
|
import { generateTorrentFileName } from '@server/lib/paths.js'
|
||||||
import { VideoCaptionModel } from '@server/models/video/video-caption.js'
|
import { VideoCaptionModel } from '@server/models/video/video-caption.js'
|
||||||
import { VideoFileModel } from '@server/models/video/video-file.js'
|
import { VideoFileModel } from '@server/models/video/video-file.js'
|
||||||
|
@ -191,7 +192,7 @@ export function getStoryboardAttributeFromObject (video: MVideoId, videoObject:
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getVideoAttributesFromObject (videoChannel: MChannelId, videoObject: VideoObject, to: string[] = []) {
|
export function getVideoAttributesFromObject (videoChannel: MChannelId, videoObject: VideoObject, to: string[] = []) {
|
||||||
const privacy = to.includes(ACTIVITY_PUB.PUBLIC)
|
const privacy = hasAPPublic(to)
|
||||||
? VideoPrivacy.PUBLIC
|
? VideoPrivacy.PUBLIC
|
||||||
: VideoPrivacy.UNLISTED
|
: VideoPrivacy.UNLISTED
|
||||||
|
|
||||||
|
|
|
@ -6,57 +6,57 @@ import {
|
||||||
MVideoFullLight,
|
MVideoFullLight,
|
||||||
MVideoId,
|
MVideoId,
|
||||||
MVideoImmutable,
|
MVideoImmutable,
|
||||||
MVideoThumbnail
|
MVideoThumbnailBlacklist
|
||||||
} from '@server/types/models/index.js'
|
} from '@server/types/models/index.js'
|
||||||
import { getOrCreateAPVideo } from '../activitypub/videos/get.js'
|
import { getOrCreateAPVideo } from '../activitypub/videos/get.js'
|
||||||
|
|
||||||
type VideoLoadType = 'for-api' | 'all' | 'only-video' | 'id' | 'none' | 'only-immutable-attributes'
|
type VideoLoadType = 'for-api' | 'all' | 'only-video-and-blacklist' | 'id' | 'none' | 'unsafe-only-immutable-attributes'
|
||||||
|
|
||||||
function loadVideo (id: number | string, fetchType: 'for-api', userId?: number): Promise<MVideoFormattableDetails>
|
function loadVideo (id: number | string, fetchType: 'for-api', userId?: number): Promise<MVideoFormattableDetails>
|
||||||
function loadVideo (id: number | string, fetchType: 'all', userId?: number): Promise<MVideoFullLight>
|
function loadVideo (id: number | string, fetchType: 'all', userId?: number): Promise<MVideoFullLight>
|
||||||
function loadVideo (id: number | string, fetchType: 'only-immutable-attributes'): Promise<MVideoImmutable>
|
function loadVideo (id: number | string, fetchType: 'unsafe-only-immutable-attributes'): Promise<MVideoImmutable>
|
||||||
function loadVideo (id: number | string, fetchType: 'only-video', userId?: number): Promise<MVideoThumbnail>
|
function loadVideo (id: number | string, fetchType: 'only-video-and-blacklist', userId?: number): Promise<MVideoThumbnailBlacklist>
|
||||||
function loadVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Promise<MVideoId>
|
function loadVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Promise<MVideoId>
|
||||||
function loadVideo (
|
function loadVideo (
|
||||||
id: number | string,
|
id: number | string,
|
||||||
fetchType: VideoLoadType,
|
fetchType: VideoLoadType,
|
||||||
userId?: number
|
userId?: number
|
||||||
): Promise<MVideoFullLight | MVideoThumbnail | MVideoId | MVideoImmutable>
|
): Promise<MVideoFullLight | MVideoThumbnailBlacklist | MVideoId | MVideoImmutable>
|
||||||
function loadVideo (
|
function loadVideo (
|
||||||
id: number | string,
|
id: number | string,
|
||||||
fetchType: VideoLoadType,
|
fetchType: VideoLoadType,
|
||||||
userId?: number
|
userId?: number
|
||||||
): Promise<MVideoFullLight | MVideoThumbnail | MVideoId | MVideoImmutable> {
|
): Promise<MVideoFullLight | MVideoThumbnailBlacklist | MVideoId | MVideoImmutable> {
|
||||||
|
|
||||||
if (fetchType === 'for-api') return VideoModel.loadForGetAPI({ id, userId })
|
if (fetchType === 'for-api') return VideoModel.loadForGetAPI({ id, userId })
|
||||||
|
|
||||||
if (fetchType === 'all') return VideoModel.loadFull(id, undefined, userId)
|
if (fetchType === 'all') return VideoModel.loadFull(id, undefined, userId)
|
||||||
|
|
||||||
if (fetchType === 'only-immutable-attributes') return VideoModel.loadImmutableAttributes(id)
|
if (fetchType === 'unsafe-only-immutable-attributes') return VideoModel.loadImmutableAttributes(id)
|
||||||
|
|
||||||
if (fetchType === 'only-video') return VideoModel.load(id)
|
if (fetchType === 'only-video-and-blacklist') return VideoModel.loadWithBlacklist(id)
|
||||||
|
|
||||||
if (fetchType === 'id' || fetchType === 'none') return VideoModel.loadOnlyId(id)
|
if (fetchType === 'id' || fetchType === 'none') return VideoModel.loadOnlyId(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
type VideoLoadByUrlType = 'all' | 'only-video' | 'only-immutable-attributes'
|
type VideoLoadByUrlType = 'all' | 'only-video-and-blacklist' | 'unsafe-only-immutable-attributes'
|
||||||
|
|
||||||
function loadVideoByUrl (url: string, fetchType: 'all'): Promise<MVideoAccountLightBlacklistAllFiles>
|
function loadVideoByUrl (url: string, fetchType: 'all'): Promise<MVideoAccountLightBlacklistAllFiles>
|
||||||
function loadVideoByUrl (url: string, fetchType: 'only-immutable-attributes'): Promise<MVideoImmutable>
|
function loadVideoByUrl (url: string, fetchType: 'unsafe-only-immutable-attributes'): Promise<MVideoImmutable>
|
||||||
function loadVideoByUrl (url: string, fetchType: 'only-video'): Promise<MVideoThumbnail>
|
function loadVideoByUrl (url: string, fetchType: 'only-video-and-blacklist'): Promise<MVideoThumbnailBlacklist>
|
||||||
function loadVideoByUrl (
|
function loadVideoByUrl (
|
||||||
url: string,
|
url: string,
|
||||||
fetchType: VideoLoadByUrlType
|
fetchType: VideoLoadByUrlType
|
||||||
): Promise<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail | MVideoImmutable>
|
): Promise<MVideoAccountLightBlacklistAllFiles | MVideoThumbnailBlacklist | MVideoImmutable>
|
||||||
function loadVideoByUrl (
|
function loadVideoByUrl (
|
||||||
url: string,
|
url: string,
|
||||||
fetchType: VideoLoadByUrlType
|
fetchType: VideoLoadByUrlType
|
||||||
): Promise<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail | MVideoImmutable> {
|
): Promise<MVideoAccountLightBlacklistAllFiles | MVideoThumbnailBlacklist | MVideoImmutable> {
|
||||||
if (fetchType === 'all') return VideoModel.loadByUrlAndPopulateAccountAndFiles(url)
|
if (fetchType === 'all') return VideoModel.loadByUrlAndPopulateAccountAndFiles(url)
|
||||||
|
|
||||||
if (fetchType === 'only-immutable-attributes') return VideoModel.loadByUrlImmutableAttributes(url)
|
if (fetchType === 'unsafe-only-immutable-attributes') return VideoModel.loadByUrlImmutableAttributes(url)
|
||||||
|
|
||||||
if (fetchType === 'only-video') return VideoModel.loadByUrl(url)
|
if (fetchType === 'only-video-and-blacklist') return VideoModel.loadByUrlWithBlacklist(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadOrCreateVideoIfAllowedForUser (videoUrl: string) {
|
async function loadOrCreateVideoIfAllowedForUser (videoUrl: string) {
|
||||||
|
@ -64,7 +64,7 @@ async function loadOrCreateVideoIfAllowedForUser (videoUrl: string) {
|
||||||
try {
|
try {
|
||||||
const res = await getOrCreateAPVideo({
|
const res = await getOrCreateAPVideo({
|
||||||
videoObject: videoUrl,
|
videoObject: videoUrl,
|
||||||
fetchType: 'only-immutable-attributes',
|
fetchType: 'unsafe-only-immutable-attributes',
|
||||||
allowRefresh: false
|
allowRefresh: false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -78,10 +78,8 @@ async function loadOrCreateVideoIfAllowedForUser (videoUrl: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
type VideoLoadType,
|
loadOrCreateVideoIfAllowedForUser, loadVideo,
|
||||||
type VideoLoadByUrlType,
|
|
||||||
|
|
||||||
loadVideo,
|
|
||||||
loadVideoByUrl,
|
loadVideoByUrl,
|
||||||
loadOrCreateVideoIfAllowedForUser
|
type VideoLoadByUrlType,
|
||||||
|
type VideoLoadType
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue