mirror of https://github.com/Chocobozzz/PeerTube
Compare commits
7 Commits
871384533b
...
f23f3ebc7c
Author | SHA1 | Date |
---|---|---|
Chocobozzz | f23f3ebc7c | |
Chocobozzz | f28a007be5 | |
Chocobozzz | 4e09837aa0 | |
Chocobozzz | fde6b32ca5 | |
spf | 63654cb475 | |
Puryx | dcd3a0ee46 | |
Puryx | db32f70799 |
|
@ -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
|
@ -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="<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 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="<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 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="<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 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="<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 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="<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 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
|
@ -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
|
@ -0,0 +1,113 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
{
|
||||
"Update": "as:Create"
|
||||
}
|
||||
],
|
||||
"id": "https://example.com/users/bob/statuses/107928807471117876/activity",
|
||||
"type": "Update",
|
||||
"actor": "https://example.com/users/bob",
|
||||
"published": "2022-03-09T21:55:07Z",
|
||||
"to": "https://www.w3.org/ns/activitystreams#Public",
|
||||
"cc": "https://example.com/users/bob/followers",
|
||||
"object": {
|
||||
"@context": {
|
||||
"id": {
|
||||
"@id": "as:attributedTo",
|
||||
"@type": "@id"
|
||||
},
|
||||
"Person": "as:Note",
|
||||
"following": {
|
||||
"@id": "as:cc",
|
||||
"@type": "@id"
|
||||
},
|
||||
"followers": {
|
||||
"@id": "as:cc",
|
||||
"@type": "@id"
|
||||
},
|
||||
"inbox": {
|
||||
"@id": "as:cc",
|
||||
"@type": "@id"
|
||||
},
|
||||
"sharedInbox": {
|
||||
"@id": "as:cc",
|
||||
"@type": "@id"
|
||||
},
|
||||
"outbox": {
|
||||
"@id": "as:cc",
|
||||
"@type": "@id"
|
||||
},
|
||||
"preferredUsername": "@type",
|
||||
"bob": "as:Note",
|
||||
"name": "@type",
|
||||
"BEING TAKEN OVER": "as:Note",
|
||||
"summary": "@type",
|
||||
"THIS ACCOUNT IS BEING TAKEN OVER BY AN ATTACKER": "as:Note",
|
||||
"url": {
|
||||
"@id": "as:attributedTo",
|
||||
"@type": "@id"
|
||||
},
|
||||
"publicKey": {
|
||||
"@id": "as:replies",
|
||||
"@type": "@id"
|
||||
},
|
||||
"ostatus": "http://ostatus.org#"
|
||||
},
|
||||
"id": "https://example.com/users/bob",
|
||||
"type": "Person",
|
||||
"following": "https://example.com/users/bob/followers",
|
||||
"followers": "https://example.com/users/bob/followers",
|
||||
"inbox": "https://example.com/users/bob/followers",
|
||||
"sharedInbox": "https://example.com/users/bob/followers",
|
||||
"outbox": "https://example.com/users/bob/followers",
|
||||
"preferredUsername": "bob",
|
||||
"name": "BEING TAKEN OVER",
|
||||
"summary": "THIS ACCOUNT IS BEING TAKEN OVER BY AN ATTACKER",
|
||||
"url": "https://example.com/users/bob",
|
||||
"published": "2022-03-09T21:55:07Z",
|
||||
"publicKey": {
|
||||
"@context": {
|
||||
"id": "@id",
|
||||
"owner": {
|
||||
"@reverse": "as:replies",
|
||||
"@type": "@id"
|
||||
},
|
||||
"publicKeyPem": "@type",
|
||||
"-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAL2hdo3culcPqz6y7AT0rlE5hgiNceL4\n28VkreQP2rSecXgeMZnjeW42GExS73F71pGMkx7b9svVK4IfPTlMN2ECAwEAAQ==\n-----END PUBLIC KEY-----\n": "as:Collection"
|
||||
},
|
||||
"id": "https://example.com/users/bob/statuses/107928807471117876/replies",
|
||||
"owner": "https://example.com/users/bob/statuses/107928807471117876",
|
||||
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAL2hdo3culcPqz6y7AT0rlE5hgiNceL4\n28VkreQP2rSecXgeMZnjeW42GExS73F71pGMkx7b9svVK4IfPTlMN2ECAwEAAQ==\n-----END PUBLIC KEY-----\n",
|
||||
"as:first": {
|
||||
"type": "CollectionPage",
|
||||
"items": [],
|
||||
"next": "https://example.com/users/bob/statuses/107928807471117876/replies?only_other_accounts=true&page=true",
|
||||
"partOf": "https://example.com/users/bob/statuses/107928807471117876/replies"
|
||||
}
|
||||
},
|
||||
"@id": "https://example.com/users/bob/statuses/107928807471117876",
|
||||
"ostatus:atomUri": "https://example.com/users/bob/statuses/107928807471117876",
|
||||
"ostatus:conversation": "tag:example.com,2022-03-09:objectId=15:objectType=Conversation",
|
||||
"as:content": [
|
||||
"<p>hello world</p>",
|
||||
{
|
||||
"@value": "<p>hello world</p>",
|
||||
"@language": "en"
|
||||
}
|
||||
],
|
||||
"as:sensitive": false,
|
||||
"as:to": {
|
||||
"@id": "https://www.w3.org/ns/activitystreams#Public"
|
||||
},
|
||||
"as:url": {
|
||||
"@id": "https://example.com/@bob/107928807471117876"
|
||||
}
|
||||
},
|
||||
"signature": {
|
||||
"type": "RsaSignature2017",
|
||||
"creator": "https://example.com/users/bob#main-key",
|
||||
"created": "2022-03-09T21:57:25Z",
|
||||
"signatureValue": "WculK0LelTQ0MvGwU9TPoq5pFzFfGYRDCJqjZ232/Udj4CHqDTGOSw5UTDLShqBOyycCkbZGrQwXG+dpyDpQLSe1UVPZ5TPQtc/9XtI57WlS2nMNpdvRuxGnnb2btPdesXZ7n3pCxo0zjaXrJMe0mqQh5QJO22mahb4bDwwmfTHgbD3nmkD+fBfGi+UV2qWwqr+jlV4L4JqNkh0gWljF5KTePLRRZCuWiQ/FAt7c67636cdIPf7fR+usjuZltTQyLZKEGuK8VUn2Gkfsx5qns7Vcjvlz1JqlAjyO8HPBbzTTHzUG2nUOIgC3PojCSWv6mNTmRGoLZzOscCAYQA6cKw=="
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuuYyoyfsRkYnXRotMsId\nW3euBDDfiv9oVqOxUVC7bhel8KednIMrMCRWFAkgJhbrlzbIkjVr68o1MP9qLcn7\nCmH/BXHp7yhuFTr4byjdJKpwB+/i2jNEsvDH5jR8WTAeTCe0x/QHg21V3F7dSI5m\nCCZ/1dSIyOXLRTWVlfDlm3rE4ntlCo+US3/7oSWbg/4/4qEnt1HC32kvklgScxua\n4LR5ATdoXa5bFoopPWhul7MJ6NyWCyQyScUuGdlj8EN4kmKQJvphKHrI9fvhgOuG\nTvhTR1S5InA4azSSchY0tXEEw/VNxraeX0KPjbgr6DPcwhPd/m0nhVDq0zVyVBBD\nMwIDAQAB\n-----END PUBLIC KEY-----\n",
|
||||
"privateKey": "-----BEGIN PRIVATE KEY-----\nMIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAvaF2jdy6Vw+rPrLs\nBPSuUTmGCI1x4vjbxWSt5A/atJ5xeB4xmeN5bjYYTFLvcXvWkYyTHtv2y9Urgh89\nOUw3YQIDAQABAkAWF4BrSILA78dgd5G9hg/k0JHH30qcSae42GDVx+8PyY5LTW/k\n2luohqd2aFbVl/64eV8wU4FaTqhuPRAXJKYRAiEA45eyOxOpnCMlO4OuTEItmDE0\n8i5pdasWI+YbFxAJVI0CIQDVTMP43JcgjtGxU7s6eTYGTH1T1LHi8MxZj0q33/C7\nJQIgFJYcIQveQ6lKLN/0XCGATkvlJiLclzAqiIS/3o4syeECIQCHrtpmvyPfkRow\n3BuYmaxVG2kJ3538x8KmIfGcv/ZphQIgXOULuGECR0nnOFwcZ9arqIWp7BE825da\nskc6vNULKBA=\n-----END PRIVATE KEY-----"
|
||||
}
|
||||
|
||||
|
|
@ -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,11 +76,24 @@ 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
|
||||
})
|
||||
|
||||
it('Should compact JSONLD input when checking JSONLD signature', async function () {
|
||||
const keys = readJsonSync(buildAbsoluteFixturePath('./ap-json/peertube/keys-updated.json'))
|
||||
const signedBody = readJsonSync(buildAbsoluteFixturePath('./ap-json/peertube/announce-updated.json'))
|
||||
|
||||
const fromActor = { publicKey: keys.publicKey }
|
||||
const req = { body: signedBody }
|
||||
const result = await compactJSONLDAndCheckSignature(fromActor as any, req)
|
||||
|
||||
expect(req.body.type).to.equal('Create')
|
||||
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('Should succeed with a valid PeerTube signature', async function () {
|
||||
const keys = readJsonSync(buildAbsoluteFixturePath('./ap-json/peertube/keys.json'))
|
||||
const body = readJsonSync(buildAbsoluteFixturePath('./ap-json/peertube/announce-without-context.json'))
|
||||
|
@ -91,7 +108,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
|
||||
})
|
||||
|
|
|
@ -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,7 +186,10 @@ 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({
|
||||
|
@ -205,15 +224,19 @@ 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'
|
||||
}
|
||||
}),
|
||||
|
||||
View: buildContext({
|
||||
|
@ -233,13 +256,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])
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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 }))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -258,7 +258,6 @@ function unicastTo (options: {
|
|||
export {
|
||||
broadcastToFollowers,
|
||||
unicastTo,
|
||||
forwardActivity,
|
||||
broadcastToActors,
|
||||
sendVideoActivityToOrigin,
|
||||
forwardVideoRelatedActivity,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
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'
|
||||
|
@ -18,7 +18,7 @@ async function checkSignature (req: Request, res: Response, next: NextFunction)
|
|||
// Forwarded activity
|
||||
const bodyActor = req.body.actor
|
||||
const bodyActorId = getAPId(bodyActor)
|
||||
if (bodyActorId && bodyActorId !== actor.url) {
|
||||
if (bodyActorId && bodyActorId !== actor.url || bodyActorId === actor.url) {
|
||||
const jsonLDSignatureChecked = await checkJsonLDSignature(req, res)
|
||||
if (jsonLDSignatureChecked !== true) return
|
||||
}
|
||||
|
@ -54,9 +54,8 @@ function executeIfActivityPub (req: Request, res: Response, next: NextFunction)
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
checkSignature,
|
||||
executeIfActivityPub,
|
||||
checkHttpSignature
|
||||
checkHttpSignature, checkSignature,
|
||||
executeIfActivityPub
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -123,7 +122,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 +140,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)
|
||||
|
|
Loading…
Reference in New Issue