Compare commits

...

9 commits

Author SHA1 Message Date
Chocobozzz b8635c2606
Compact json-ld AP objects 2024-04-25 11:21:55 +02:00
Chocobozzz 712f7d18e6
Update superagent
To fix vulnerability of formidable
2024-04-25 11:01:10 +02:00
Chocobozzz 47ae6e880d
Fix AP actor follows count 2024-04-25 10:53:53 +02:00
Chocobozzz 9244620f37
Fix view explanation 2024-04-25 09:33:05 +02:00
Chocobozzz 4e09837aa0
Fix player subtitles on iOS 2024-04-24 15:57:24 +02:00
Chocobozzz fde6b32ca5
Update translations 2024-04-24 14:57:00 +02:00
spf 63654cb475
Translated using Weblate (French (France) (fr_FR))
Currently translated at 93.7% (2218 of 2367 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/fr_FR/
2024-04-24 14:52:22 +02:00
Puryx dcd3a0ee46
Translated using Weblate (Romanian)
Currently translated at 19.1% (262 of 1367 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/ro/
2024-04-24 14:52:22 +02:00
Puryx db32f70799
Translated using Weblate (Romanian)
Currently translated at 18.8% (258 of 1367 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/ro/
2024-04-24 14:52:22 +02:00
72 changed files with 1779 additions and 3681 deletions

View file

@ -309,7 +309,7 @@ export class VideoStatsComponent implements OnInit {
{
label: $localize`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`,

View file

@ -361,7 +361,10 @@ export class PeerTubePlayer {
getVideojsOptions (): videojs.PlayerOptions {
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 = {

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

View file

@ -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">
<file source-language="en-US" datatype="plaintext" original="ng2.template" target-language="ro">
<body>
@ -106,33 +106,33 @@
<context context-type="linenumber">5</context>
</context-group>
</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>
<target/>
<target state="translated">Primul</target>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/pagination/pagination.d.ts</context>
<context context-type="linenumber">14</context>
</context-group>
</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>
<target/>
<target state="translated">Anterior</target>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/pagination/pagination.d.ts</context>
<context context-type="linenumber">24</context>
</context-group>
</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>
<target/>
<target state="translated">Următorul</target>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/pagination/pagination.d.ts</context>
<context context-type="linenumber">44</context>
</context-group>
</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>
<target/>
<target state="translated">Ultimul</target>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/pagination/pagination.d.ts</context>
<context context-type="linenumber">53</context>
@ -146,9 +146,9 @@
<context context-type="linenumber">7</context>
</context-group>
</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>
<target/>
<target state="translated">Adaugă ore</target>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/timepicker/timepicker.d.ts</context>
<context context-type="linenumber">9</context>
@ -170,17 +170,17 @@
<context context-type="linenumber">15</context>
</context-group>
</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>
<target/>
<target state="translated">Scade ore</target>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/timepicker/timepicker.d.ts</context>
<context context-type="linenumber">23</context>
</context-group>
</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>
<target/>
<target state="translated">Adaugă minute</target>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/timepicker/timepicker.d.ts</context>
<context context-type="linenumber">32</context>
@ -202,17 +202,17 @@
<context context-type="linenumber">37</context>
</context-group>
</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>
<target/>
<target state="translated">Scade minute</target>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/timepicker/timepicker.d.ts</context>
<context context-type="linenumber">45</context>
</context-group>
</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>
<target/>
<target state="translated">Adaugă secunde</target>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/timepicker/timepicker.d.ts</context>
<context context-type="linenumber">54</context>
@ -234,9 +234,9 @@
<context context-type="linenumber">59</context>
</context-group>
</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>
<target/>
<target state="translated">Scade secunde</target>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/@ng-bootstrap/ng-bootstrap/timepicker/timepicker.d.ts</context>
<context context-type="linenumber">67</context>
@ -298,17 +298,17 @@
<context context-type="linenumber">32</context>
</context-group>
</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="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.videoBlacklist.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> has been blacklisted </source>
<target/>
<target state="translated">Videoul tău  <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.videoBlacklist.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> a fost blocat. </target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/users/user-notifications.component.html</context>
<context context-type="linenumber">40</context>
</context-group>
</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="&lt;a&gt;"/>A new video abuse<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> has been created on video <x id="START_LINK_1" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.videoAbuse.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> </source>
<target/>
<target state="translated"><x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>A new video abuse<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> has been created on video <x id="START_LINK_1" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.videoAbuse.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> </target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/users/user-notifications.component.html</context>
<context context-type="linenumber">48</context>
@ -438,9 +438,9 @@
<context context-type="linenumber">42</context>
</context-group>
</trans-unit>
<trans-unit id="1394835141143590910" datatype="html">
<trans-unit id="1394835141143590910" datatype="html" xml:space="preserve">
<source>Start at</source>
<target/>
<target state="translated">Începe la</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/video-playlist/video-add-to-playlist.component.html</context>
<context context-type="linenumber">17</context>
@ -454,9 +454,9 @@
<context context-type="linenumber">75</context>
</context-group>
</trans-unit>
<trans-unit id="5964984095397511808" datatype="html">
<trans-unit id="5964984095397511808" datatype="html" xml:space="preserve">
<source>Stop at</source>
<target/>
<target state="translated">Oprește la</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/video-playlist/video-add-to-playlist.component.html</context>
<context context-type="linenumber">31</context>
@ -766,17 +766,17 @@
<context context-type="linenumber">26</context>
</context-group>
</trans-unit>
<trans-unit id="8558962068274430520" datatype="html">
<trans-unit id="8558962068274430520" datatype="html" xml:space="preserve">
<source>Unfederate the video</source>
<target/>
<target state="translated">Decuplați videoclipul</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/video/modals/video-blacklist.component.html</context>
<context context-type="linenumber">23</context>
</context-group>
</trans-unit>
<trans-unit id="7539427273132299890" datatype="html">
<trans-unit id="7539427273132299890" datatype="html" xml:space="preserve">
<source>Unlisted</source>
<target/>
<target state="translated">Nelistat</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/video/video-miniature.component.html</context>
<context context-type="linenumber">6</context>
@ -802,9 +802,9 @@
<context context-type="linenumber">7</context>
</context-group>
</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>
<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 context-type="sourcefile">src/app/shared/video/video-miniature.component.html</context>
<context context-type="linenumber">23</context>
@ -818,9 +818,9 @@
<context context-type="linenumber">41</context>
</context-group>
</trans-unit>
<trans-unit id="3514509630940272440" datatype="html">
<trans-unit id="3514509630940272440" datatype="html" xml:space="preserve">
<source>Sensitive</source>
<target/>
<target state="translated">Sensitiv</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/video/video-miniature.component.html</context>
<context context-type="linenumber">45</context>
@ -874,9 +874,9 @@
<context context-type="linenumber">100</context>
</context-group>
</trans-unit>
<trans-unit id="5263519165976128456" datatype="html">
<trans-unit id="5263519165976128456" datatype="html" xml:space="preserve">
<source>Edit starts/stops at</source>
<target/>
<target state="translated">Editarea începe/oprește la</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/video-playlist/video-playlist-element-miniature.component.html</context>
<context context-type="linenumber">50</context>
@ -1182,9 +1182,9 @@
<context context-type="linenumber">17</context>
</context-group>
</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="&lt;br/&gt;"/><x id="LINE_BREAK" ctype="lb" equiv-text="&lt;br/&gt;"/> 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="&lt;br/&gt;"/><x id="LINE_BREAK" ctype="lb" equiv-text="&lt;br/&gt;"/>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 context-type="sourcefile">src/app/shared/user-subscription/remote-subscribe.component.html</context>
<context context-type="linenumber">26</context>
@ -1198,9 +1198,9 @@
<context context-type="linenumber">5</context>
</context-group>
</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="&lt;div&gt;"/>Default NSFW/sensitive videos policy<x id="CLOSE_TAG_DIV" ctype="x-div" equiv-text="&lt;/div&gt;"/> <x id="START_TAG_DIV_1" ctype="x-div" equiv-text="&lt;div&gt;"/>can be redefined by the users<x id="CLOSE_TAG_DIV" ctype="x-div" equiv-text="&lt;/div&gt;"/> </source>
<target/>
<target state="translated"><x id="START_TAG_DIV" ctype="x-div" equiv-text="&lt;div&gt;"/>Predefinit NSFW/sensitive videoclipuri regulamentului<x id="CLOSE_TAG_DIV" ctype="x-div" equiv-text="&lt;/div&gt;"/><x id="START_TAG_DIV_1" ctype="x-div" equiv-text="&lt;div&gt;"/>poate fii redefinit de<x id="CLOSE_TAG_DIV" ctype="x-div" equiv-text="&lt;/div&gt;"/>. </target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/instance/instance-features-table.component.html</context>
<context context-type="linenumber">11</context>
@ -1266,9 +1266,9 @@
<context context-type="linenumber">139</context>
</context-group>
</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="&lt;ng-container&gt;"/>(<x id="INTERPOLATION" equiv-text="{{ dailyUserVideoQuota | bytes: 0 }}"/> per day)<x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;/ng-container&gt;"/> </source>
<target/>
<target state="translated">Nelimitat<x id="START_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;ng-container&gt;"/><x id="INTERPOLATION" equiv-text="{{ dailyUserVideoQuota | bytes: 0 }}"/>pe zi<x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;/ng-container&gt;"/>/ </target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/instance/instance-features-table.component.html</context>
<context context-type="linenumber">59</context>
@ -1306,9 +1306,9 @@
<context context-type="linenumber">77</context>
</context-group>
</trans-unit>
<trans-unit id="7683705529753923369" datatype="html">
<trans-unit id="7683705529753923369" datatype="html" xml:space="preserve">
<source>Player</source>
<target/>
<target state="translated">Jucător</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/instance/instance-features-table.component.html</context>
<context context-type="linenumber">85</context>
@ -1334,9 +1334,9 @@
<context context-type="linenumber">7</context>
</context-group>
</trans-unit>
<trans-unit id="8630916846096019339" datatype="html">
<trans-unit id="8630916846096019339" datatype="html" xml:space="preserve">
<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 context-type="sourcefile">src/app/shared/instance/instance-features-table.component.html</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

View file

@ -4228,11 +4228,11 @@
</context-group>
<context-group purpose="location">
<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 purpose="location">
<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 purpose="location">
<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>
<context-group purpose="location">
<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>
</trans-unit>
<trans-unit id="6693349469471580292" datatype="html">
<source>Delete file</source>
<context-group purpose="location">
<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>
</trans-unit>
<trans-unit id="7016764388104297354" datatype="html">
<source>File removed.</source>
<context-group purpose="location">
<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>
</trans-unit>
<trans-unit id="925076027211452339" datatype="html">
<source>Are you sure you want to delete the original file of this video?</source>
<context-group purpose="location">
<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>
</trans-unit>
<trans-unit id="3014914668468316940" datatype="html">
<source>Delete original file</source>
<context-group purpose="location">
<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>
</trans-unit>
<trans-unit id="6647462936549531405" datatype="html">
<source>Original file removed.</source>
<context-group purpose="location">
<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>
</trans-unit>
<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>
<context-group purpose="location">
<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>
</trans-unit>
<trans-unit id="5779580280418408097" datatype="html">
<source>Deleted {count, plural, =1 {1 video} other {<x id="count"/> videos}}.</source>
<context-group purpose="location">
<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>
</trans-unit>
<trans-unit id="9164541937317586242" datatype="html">
<source>Unblocked {count, plural, =1 {1 video} other {<x id="count"/> videos}}.</source>
<context-group purpose="location">
<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>
</trans-unit>
<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>
<context-group purpose="location">
<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>
</trans-unit>
<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>
<context-group purpose="location">
<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>
</trans-unit>
<trans-unit id="1571742433738679426" datatype="html">
<source>Files were removed.</source>
<context-group purpose="location">
<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>
</trans-unit>
<trans-unit id="7054344823477412274" datatype="html">
<source>Transcoding jobs created.</source>
<context-group purpose="location">
<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>
</trans-unit>
<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

View file

@ -236,7 +236,7 @@
"pngjs": "^7.0.0",
"proxy": "^2.1.1",
"socket.io-client": "^4.5.4",
"supertest": "^6.0.1",
"supertest": "^7.0.0",
"swagger-cli": "^4.0.2",
"tsc-watch": "^6.0.0",
"tsx": "^4.7.1",

View file

@ -3,7 +3,7 @@
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
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 { 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 { readJsonSync } from 'fs-extra/esm'
import cloneDeep from 'lodash-es/cloneDeep.js'
@ -24,6 +24,10 @@ function fakeFilter () {
return (data: any) => Promise.resolve(data)
}
function fakeExpressReq (body: any) {
return { body }
}
describe('Test activity pub helpers', 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 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
})
@ -43,7 +47,7 @@ describe('Test activity pub helpers', function () {
const publicKey = readJsonSync(buildAbsoluteFixturePath('./ap-json/mastodon/bad-public-key.json')).publicKey
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
})
@ -53,7 +57,7 @@ describe('Test activity pub helpers', function () {
const publicKey = readJsonSync(buildAbsoluteFixturePath('./ap-json/mastodon/public-key.json')).publicKey
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
})
@ -72,7 +76,7 @@ describe('Test activity pub helpers', function () {
})
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
})
@ -91,7 +95,7 @@ describe('Test activity pub helpers', function () {
})
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
})

View file

@ -1,9 +1,9 @@
import { ContextType } from '@peertube/peertube-models'
import { ACTIVITY_PUB, REMOTE_SCHEME } from '@server/initializers/constants.js'
import { isArray } from './custom-validators/misc.js'
import { buildDigest } from './peertube-crypto.js'
import type { signJsonLDObject } from './peertube-jsonld.js'
import { doJSONRequest } from './requests.js'
import { isArray } from './custom-validators/misc.js'
export type ContextFilter = <T> (arg: T) => Promise<T>
@ -49,6 +49,18 @@ export async function getApplicationActorOfHost (host: string) {
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
// ---------------------------------------------------------------------------
@ -58,7 +70,6 @@ type ContextValue = { [ id: string ]: (string | { '@type': string, '@id': string
const contextStore: { [ id in ContextType ]: (string | { [ id: string ]: string })[] } = {
Video: buildContext({
Hashtag: 'as:Hashtag',
uuid: 'sc:identifier',
category: 'sc:category',
licence: 'sc:license',
subtitleLanguage: 'sc:subtitleLanguage',
@ -99,6 +110,11 @@ const contextStore: { [ id in ContextType ]: (string | { [ id: string ]: string
'@id': 'pt:aspectRatio'
},
uuid: {
'@type': 'sc:identifier',
'@id': 'pt:uuid'
},
originallyPublishedAt: 'sc:datePublished',
uploadDate: 'sc:uploadDate',
@ -170,12 +186,23 @@ const contextStore: { [ id in ContextType ]: (string | { [ id: string ]: string
'@type': 'sc:Number',
'@id': 'pt:stopTimestamp'
},
uuid: 'sc:identifier'
uuid: {
'@type': 'sc:identifier',
'@id': 'pt:uuid'
}
}),
CacheFile: buildContext({
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({
@ -205,15 +232,21 @@ const contextStore: { [ id in ContextType ]: (string | { [ id: string ]: string
'@type': 'sc:Number',
'@id': 'pt:startTimestamp'
},
stopTimestamp: {
endTimestamp: {
'@type': 'sc:Number',
'@id': 'pt:stopTimestamp'
'@id': 'pt:endTimestamp'
},
watchSection: {
'@type': 'sc:Number',
'@id': 'pt:stopTimestamp'
uuid: {
'@type': 'sc:identifier',
'@id': 'pt:uuid'
},
uuid: 'sc:identifier'
actionStatus: 'sc:actionStatus',
watchSections: {
'@type': '@id',
'@id': 'pt:watchSections'
},
addressRegion: 'sc:addressRegion',
addressCountry: 'sc:addressCountry'
}),
View: buildContext({
@ -233,13 +266,46 @@ const contextStore: { [ id in ContextType ]: (string | { [ id: string ]: string
Rate: buildContext(),
Chapters: buildContext({
name: 'sc:name',
hasPart: 'sc:hasPart',
endOffset: 'sc:endOffset',
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) {
const contextData = contextFilter
? await contextFilter(contextStore[type])

View file

@ -1,6 +1,6 @@
import jsonld from 'jsonld'
const CACHE = {
const STATIC_CACHE = {
'https://w3id.org/security/v1': {
'@context': {
id: '@id',
@ -53,19 +53,29 @@ const CACHE = {
}
}
const localCache = new Map<string, any>()
const nodeDocumentLoader = (jsonld as any).documentLoaders.node();
/* eslint-disable no-import-assign */
(jsonld as any).documentLoader = (url) => {
if (url in CACHE) {
return Promise.resolve({
(jsonld as any).documentLoader = async (url: string) => {
if (url in STATIC_CACHE) {
return {
contextUrl: null,
document: CACHE[url],
document: STATIC_CACHE[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 }

View file

@ -1,20 +1,15 @@
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 { isRemoteVideoUrlValid } from './videos.js'
function isCacheFileObjectValid (object: CacheFileObject) {
return exists(object) &&
object.type === 'CacheFile' &&
(object.expires === null || isDateValid(object.expires)) &&
export function isCacheFileObjectValid (object: CacheFileObject) {
if (!object || object.type !== 'CacheFile') return false
return (!object.expires || isDateValid(object.expires)) &&
isActivityPubUrlValid(object.object) &&
(isRemoteVideoUrlValid(object.url) || isPlaylistRedundancyUrlValid(object.url))
}
// ---------------------------------------------------------------------------
export {
isCacheFileObjectValid
(isRedundancyUrlVideoValid(object.url) || isPlaylistRedundancyUrlValid(object.url))
}
// ---------------------------------------------------------------------------
@ -24,3 +19,15 @@ function isPlaylistRedundancyUrlValid (url: any) {
(url.mediaType || url.mimeType) === 'application/x-mpegURL' &&
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 }))
}

View file

@ -1,29 +1,25 @@
import validator from 'validator'
import { PlaylistElementObject, PlaylistObject } from '@peertube/peertube-models'
import validator from 'validator'
import { exists, isDateValid, isUUIDValid } from '../misc.js'
import { isVideoPlaylistNameValid } from '../video-playlists.js'
import { isActivityPubUrlValid } from './misc.js'
function isPlaylistObjectValid (object: PlaylistObject) {
return exists(object) &&
object.type === 'Playlist' &&
validator.default.isInt(object.totalItems + '') &&
export function isPlaylistObjectValid (object: PlaylistObject) {
if (!object || object.type !== 'Playlist') return false
// 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) &&
isUUIDValid(object.uuid) &&
isDateValid(object.published) &&
isDateValid(object.updated)
}
function isPlaylistElementObjectValid (object: PlaylistElementObject) {
export function isPlaylistElementObjectValid (object: PlaylistElementObject) {
return exists(object) &&
object.type === 'PlaylistElement' &&
validator.default.isInt(object.position + '') &&
isActivityPubUrlValid(object.url)
}
// ---------------------------------------------------------------------------
export {
isPlaylistObjectValid,
isPlaylistElementObjectValid
}

View file

@ -1,5 +1,5 @@
import { hasAPPublic } from '@server/helpers/activity-pub-utils.js'
import validator from 'validator'
import { ACTIVITY_PUB } from '../../../initializers/constants.js'
import { exists, isArray, isDateValid } from '../misc.js'
import { isActivityPubUrlValid } from './misc.js'
@ -23,10 +23,7 @@ function sanitizeAndCheckVideoCommentObject (comment: any) {
isDateValid(comment.published) &&
isActivityPubUrlValid(comment.url) &&
isArray(comment.to) &&
(
comment.to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ||
comment.cc.indexOf(ACTIVITY_PUB.PUBLIC) !== -1
) // Only accept public comments
(hasAPPublic(comment.to) || hasAPPublic(comment.cc)) // Only accept public comments
}
// ---------------------------------------------------------------------------

View file

@ -27,7 +27,7 @@ function sanitizeAndCheckVideoTorrentUpdateActivity (activity: any) {
sanitizeAndCheckVideoTorrentObject(activity.object)
}
function sanitizeAndCheckVideoTorrentObject (video: any) {
function sanitizeAndCheckVideoTorrentObject (video: VideoObject) {
if (!video || video.type !== 'Video') return false
if (!setValidRemoteTags(video)) {
@ -59,6 +59,9 @@ function sanitizeAndCheckVideoTorrentObject (video: any) {
return false
}
// TODO: compat with < 6.1, remove in 7.0
if (!video.uuid && video['identifier']) video.uuid = video['identifier']
// Default attributes
if (!isVideoStateValid(video.state)) video.state = VideoState.PUBLISHED
if (!isBooleanValid(video.waitTranscoding)) video.waitTranscoding = false

View file

@ -1,19 +1,26 @@
import { arrayify } from '@peertube/peertube-core-utils'
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 { isActivityPubVideoDurationValid, isObjectValid } from './misc.js'
function isWatchActionObjectValid (action: WatchActionObject) {
return exists(action) &&
action.type === 'WatchAction' &&
isObjectValid(action.id) &&
if (!action || action.type !== 'WatchAction') return false
// 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) &&
isDateValid(action.startTime) &&
isDateValid(action.endTime) &&
isLocationValid(action.location) &&
isUUIDValid(action.uuid) &&
isObjectValid(action.object) &&
isWatchSectionsValid(action.watchSections)
areWatchSectionsValid(action.watchSections)
}
// ---------------------------------------------------------------------------
@ -34,8 +41,11 @@ function isLocationValid (location: any) {
return true
}
function isWatchSectionsValid (sections: WatchActionObject['watchSections']) {
function areWatchSectionsValid (sections: WatchActionObject['watchSections']) {
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)
})
}

View file

@ -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)
}

View file

@ -1,26 +1,51 @@
import { omit } from '@peertube/peertube-core-utils'
import { sha256 } from '@peertube/peertube-node-utils'
import { createSign, createVerify } from 'crypto'
import cloneDeep from 'lodash-es/cloneDeep.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 { assertIsInWorkerThread } from './threads.js'
import { jsonld } from './custom-jsonld-signature.js'
export function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise<boolean> {
if (signedDocument.signature.type === 'RsaSignature2017') {
return isJsonLDRSA2017Verified(fromActor, signedDocument)
type ExpressRequest = { body: any }
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)
}
// 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([
createDocWithoutSignatureHash(signedDocument),
createSignatureHash(signedDocument.signature)
hashObject(compacted, safe),
createSignatureHash(req.body.signature, safe)
])
const toVerify = optionsHash + documentHash
@ -28,7 +53,39 @@ export async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument
const verify = createVerify('RSA-SHA256')
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: {
@ -66,35 +123,40 @@ export async function signJsonLDObject <T> (options: {
// Private
// ---------------------------------------------------------------------------
async function hashObject (obj: any): Promise<any> {
const res = await (jsonld as any).promises.normalize(obj, {
safe: false,
algorithm: 'URDNA2015',
format: 'application/n-quads'
})
async function hashObject (obj: any, safe: boolean): Promise<any> {
const res = await jsonldNormalize(obj, safe)
return sha256(res)
}
function createSignatureHash (signature: any) {
const signatureCopy = cloneDeep(signature)
Object.assign(signatureCopy, {
function jsonldCompact (obj: any) {
return (jsonld as any).promises.compact(obj, getAllContext())
}
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': [
'https://w3id.org/security/v1',
{ RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' }
]
})
],
delete signatureCopy.type
delete signatureCopy.id
delete signatureCopy.signatureValue
return hashObject(signatureCopy)
...omit(signature, [ 'type', 'id', 'signatureValue' ])
}, safe)
}
function createDocWithoutSignatureHash (doc: any) {
const docWithoutSignature = cloneDeep(doc)
delete docWithoutSignature.signature
return hashObject(docWithoutSignature)
return hashObject(docWithoutSignature, true)
}

View file

@ -774,7 +774,6 @@ const ACTIVITY_PUB = {
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
],
ACCEPT_HEADER: 'application/activity+json, application/ld+json',
PUBLIC: 'https://www.w3.org/ns/activitystreams#Public',
COLLECTION_ITEMS_PER_PAGE: 10,
FETCH_PAGE_LIMIT: 2000,
MAX_RECURSION_COMMENTS: 100,

View file

@ -1,17 +1,17 @@
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'
function getAudience (actorSender: MActorFollowersUrl, isPublic = true) {
export function getAudience (actorSender: MActorFollowersUrl, isPublic = true) {
return buildAudience([ actorSender.followersUrl ], isPublic)
}
function buildAudience (followerUrls: string[], isPublic = true) {
export function buildAudience (followerUrls: string[], isPublic = true) {
let to: string[] = []
let cc: string[] = []
if (isPublic) {
to = [ ACTIVITY_PUB.PUBLIC ]
to = [ getAPPublicValue() ]
cc = followerUrls
} else { // Unlisted
to = []
@ -21,14 +21,6 @@ function buildAudience (followerUrls: string[], isPublic = true) {
return { to, cc }
}
function audiencify<T> (object: T, audience: ActivityAudience) {
export function audiencify<T> (object: T, audience: ActivityAudience) {
return { ...audience, ...object }
}
// ---------------------------------------------------------------------------
export {
buildAudience,
getAudience,
audiencify
}

View file

@ -2,6 +2,7 @@ import { Transaction } from 'sequelize'
import { MActorId, MVideoRedundancy, MVideoWithAllFiles } from '@server/types/models/index.js'
import { CacheFileObject, VideoStreamingPlaylistType } from '@peertube/peertube-models'
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) {
const redundancyModel = await VideoRedundancyModel.loadByUrl(cacheFileObject.id, t)
@ -65,11 +66,15 @@ function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject
}
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 => {
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 {
expiresOn: cacheFileObject.expires ? new Date(cacheFileObject.expires) : null,

View file

@ -6,7 +6,7 @@ import { Activity } from '@peertube/peertube-models'
import { StatsManager } from '../stat-manager.js'
import { processActivities } from './process/index.js'
class InboxManager {
export class InboxManager {
private static instance: InboxManager
private readonly inboxQueue: PQueue
@ -39,9 +39,3 @@ class InboxManager {
return this.instance || (this.instance = new this())
}
}
// ---------------------------------------------------------------------------
export {
InboxManager
}

View file

@ -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 { 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[]) {
const privacy = to.includes(ACTIVITY_PUB.PUBLIC)
export function playlistObjectToDBAttributes (playlistObject: PlaylistObject, to: string[]) {
const privacy = hasAPPublic(to)
? VideoPlaylistPrivacy.PUBLIC
: VideoPlaylistPrivacy.UNLISTED
@ -23,7 +23,11 @@ function playlistObjectToDBAttributes (playlistObject: PlaylistObject, to: strin
} as AttributesOnly<VideoPlaylistModel>
}
function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObject, videoPlaylist: MVideoPlaylistId, video: MVideoId) {
export function playlistElementObjectToDBAttributes (
elementObject: PlaylistElementObject,
videoPlaylist: MVideoPlaylistId,
video: MVideoId
) {
return {
position: elementObject.position,
url: elementObject.id,
@ -33,8 +37,3 @@ function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObje
videoId: video.id
} as AttributesOnly<VideoPlaylistElementModel>
}
export {
playlistObjectToDBAttributes,
playlistElementObjectToDBAttributes
}

View file

@ -32,8 +32,8 @@ async function processCreateView (activity: ActivityView, byActor: MActorSignatu
video,
viewerId: activity.id,
viewerExpires: activity.expires
? new Date(activity.expires)
viewerExpires: getExpires(activity)
? new Date(getExpires(activity))
: undefined,
viewerResultCounter: getViewerResultCounter(activity)
})
@ -49,10 +49,15 @@ async function processCreateView (activity: ActivityView, byActor: MActorSignatu
function getViewerResultCounter (activity: ActivityView) {
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 + '')
if (isNaN(counter)) return undefined
return counter
}
// TODO: compat with < 6.1, remove in 7.0
function getExpires (activity: ActivityView) {
return activity.expires || activity['expiration'] as string
}

View file

@ -34,7 +34,7 @@ const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions<Act
View: processViewActivity
}
async function processActivities (
export async function processActivities (
activities: Activity[],
options: {
signatureActor?: MActorSignature
@ -86,7 +86,3 @@ async function processActivities (
}
}
}
export {
processActivities
}

View file

@ -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 { 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 {
to: [ accountActor.url ],
cc: actorsInvolvedInVideo.map(a => a.followersUrl)
}
}
function getVideoCommentAudience (
export function getVideoCommentAudience (
videoComment: MCommentOwnerVideo,
threadParentComments: MCommentOwner[],
actorsInvolvedInVideo: MActorFollowersUrl[],
isOrigin = false
): ActivityAudience {
const to = [ ACTIVITY_PUB.PUBLIC ]
const to = [ getAPPublicValue() ]
const cc: string[] = []
// Owner of the video we comment
@ -43,14 +43,14 @@ function getVideoCommentAudience (
}
}
function getAudienceFromFollowersOf (actorsInvolvedInObject: MActorFollowersUrl[]): ActivityAudience {
export function getAudienceFromFollowersOf (actorsInvolvedInObject: MActorFollowersUrl[]): ActivityAudience {
return {
to: [ ACTIVITY_PUB.PUBLIC ].concat(actorsInvolvedInObject.map(a => a.followersUrl)),
to: [ getAPPublicValue() ].concat(actorsInvolvedInObject.map(a => a.followersUrl)),
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 alreadyLoadedActor = (video as VideoModel).VideoChannel?.Account?.Actor
@ -63,12 +63,3 @@ async function getActorsInvolvedInVideo (video: MVideoId, t: Transaction) {
return actors
}
// ---------------------------------------------------------------------------
export {
getOriginVideoAudience,
getActorsInvolvedInVideo,
getAudienceFromFollowersOf,
getVideoCommentAudience
}

View file

@ -258,7 +258,6 @@ function unicastTo (options: {
export {
broadcastToFollowers,
unicastTo,
forwardActivity,
broadcastToActors,
sendVideoActivityToOrigin,
forwardVideoRelatedActivity,

View file

@ -11,13 +11,14 @@ import {
VideoPrivacy,
VideoStreamingPlaylistType
} from '@peertube/peertube-models'
import { hasAPPublic } from '@server/helpers/activity-pub-utils.js'
import { isAPVideoFileUrlMetadataObject } from '@server/helpers/custom-validators/activitypub/videos.js'
import { isArray } from '@server/helpers/custom-validators/misc.js'
import { isVideoFileInfoHashValid } from '@server/helpers/custom-validators/videos.js'
import { generateImageFilename } from '@server/helpers/image-utils.js'
import { logger } from '@server/helpers/logger.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 { VideoCaptionModel } from '@server/models/video/video-caption.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[] = []) {
const privacy = to.includes(ACTIVITY_PUB.PUBLIC)
const privacy = hasAPPublic(to)
? VideoPrivacy.PUBLIC
: VideoPrivacy.UNLISTED

View file

@ -1,14 +1,14 @@
import { NextFunction, Request, Response } from 'express'
import { ActivityDelete, ActivityPubSignature, HttpStatusCode } from '@peertube/peertube-models'
import { isActorDeleteActivityValid } from '@server/helpers/custom-validators/activitypub/actor.js'
import { getAPId } from '@server/lib/activitypub/activity.js'
import { wrapWithSpanAndContext } from '@server/lib/opentelemetry/tracing.js'
import { ActivityDelete, ActivityPubSignature, HttpStatusCode } from '@peertube/peertube-models'
import { NextFunction, Request, Response } from 'express'
import { logger } from '../helpers/logger.js'
import { isHTTPSignatureVerified, parseHTTPSignature } from '../helpers/peertube-crypto.js'
import { ACCEPT_HEADERS, ACTIVITY_PUB, HTTP_SIGNATURE } from '../initializers/constants.js'
import { getOrCreateAPActor, loadActorUrlOrGetFromWebfinger } from '../lib/activitypub/actors/index.js'
async function checkSignature (req: Request, res: Response, next: NextFunction) {
export async function checkSignature (req: Request, res: Response, next: NextFunction) {
try {
const httpSignatureChecked = await checkHttpSignature(req, res)
if (httpSignatureChecked !== true) return
@ -39,7 +39,7 @@ async function checkSignature (req: Request, res: Response, next: NextFunction)
}
}
function executeIfActivityPub (req: Request, res: Response, next: NextFunction) {
export function executeIfActivityPub (req: Request, res: Response, next: NextFunction) {
const accepted = req.accepts(ACCEPT_HEADERS)
if (accepted === false || ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS.includes(accepted) === false) {
// Bypass this route
@ -52,13 +52,7 @@ function executeIfActivityPub (req: Request, res: Response, next: NextFunction)
}
// ---------------------------------------------------------------------------
export {
checkSignature,
executeIfActivityPub,
checkHttpSignature
}
// Private
// ---------------------------------------------------------------------------
async function checkHttpSignature (req: Request, res: Response) {
@ -123,7 +117,7 @@ async function checkHttpSignature (req: Request, res: Response) {
async function checkJsonLDSignature (req: Request, res: Response) {
// Lazy load the module as it's quite big with json.ld dependency
const { isJsonLDSignatureVerified } = await import('../helpers/peertube-jsonld.js')
const { compactJSONLDAndCheckSignature } = await import('../helpers/peertube-jsonld.js')
return wrapWithSpanAndContext('peertube.activitypub.JSONLDSignature', async () => {
const signatureObject: ActivityPubSignature = req.body.signature
@ -141,7 +135,7 @@ async function checkJsonLDSignature (req: Request, res: Response) {
logger.debug('Checking JsonLD signature of actor %s...', creator)
const actor = await getOrCreateAPActor(creator)
const verified = await isJsonLDSignatureVerified(actor, req.body)
const verified = await compactJSONLDAndCheckSignature(actor, req)
if (verified !== true) {
logger.warn('Signature not verified.', req.body)

View file

@ -710,7 +710,7 @@ export class ActorFollowModel extends SequelizeModel<ActorFollowModel> {
data: followers.map(f => ({ selectionUrl: f.selectionUrl, createdAt: f.createdAt })) as { selectionUrl: string, createdAt: string }[],
total: selectTotal
? parseInt(resDataTotal?.dataTotal?.[0]?.total || 0, 10)
? parseInt(resDataTotal?.[0]?.total || 0, 10)
: undefined
}
}

View file

@ -5701,15 +5701,14 @@ formdata-polyfill@^4.0.10:
dependencies:
fetch-blob "^3.1.2"
formidable@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/formidable/-/formidable-2.1.2.tgz#fa973a2bec150e4ce7cac15589d7a25fc30ebd89"
integrity sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==
formidable@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/formidable/-/formidable-3.5.1.tgz#9360a23a656f261207868b1484624c4c8d06ee1a"
integrity sha512-WJWKelbRHN41m5dumb0/k8TeAx7Id/y3a+Z7QfhxP/htI9Js5zYaEDtG8uMgG0vM0lOlqnmjE99/kfpOYi/0Og==
dependencies:
dezalgo "^1.0.4"
hexoid "^1.0.0"
once "^1.4.0"
qs "^6.11.0"
forwarded@0.2.0:
version "0.2.0"
@ -9787,29 +9786,29 @@ subarg@^1.0.0:
dependencies:
minimist "^1.1.0"
superagent@^8.1.2:
version "8.1.2"
resolved "https://registry.yarnpkg.com/superagent/-/superagent-8.1.2.tgz#03cb7da3ec8b32472c9d20f6c2a57c7f3765f30b"
integrity sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==
superagent@^9.0.1:
version "9.0.1"
resolved "https://registry.yarnpkg.com/superagent/-/superagent-9.0.1.tgz#660773036c03728a1a88649a5d7e15d89b1d6961"
integrity sha512-CcRSdb/P2oUVaEpQ87w9Obsl+E9FruRd6b2b7LdiBtJoyMr2DQt7a89anAfiX/EL59j9b2CbRFvf2S91DhuCww==
dependencies:
component-emitter "^1.3.0"
cookiejar "^2.1.4"
debug "^4.3.4"
fast-safe-stringify "^2.1.1"
form-data "^4.0.0"
formidable "^2.1.2"
formidable "^3.5.1"
methods "^1.1.2"
mime "2.6.0"
qs "^6.11.0"
semver "^7.3.8"
supertest@^6.0.1:
version "6.3.4"
resolved "https://registry.yarnpkg.com/supertest/-/supertest-6.3.4.tgz#2145c250570c2ea5d337db3552dbfb78a2286218"
integrity sha512-erY3HFDG0dPnhw4U+udPfrzXa4xhSG+n4rxfRuZWCUvjFWwKl+OxWf/7zk50s84/fAAs7vf5QAb9uRa0cCykxw==
supertest@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/supertest/-/supertest-7.0.0.tgz#cac53b3d6872a0b317980b2b0cfa820f09cd7634"
integrity sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==
dependencies:
methods "^1.1.2"
superagent "^8.1.2"
superagent "^9.0.1"
supports-color@8.1.1, supports-color@^8.1.1:
version "8.1.1"