1
0
Fork 0
mirror of https://github.com/Chocobozzz/PeerTube.git synced 2025-10-03 09:49:20 +02:00

Support raw strings for AP to/cc

This commit is contained in:
Chocobozzz 2025-05-09 11:36:21 +02:00
parent 5138e28ff8
commit 8d3bc24c4b
No known key found for this signature in database
GPG key ID: 583A612D890159BE
9 changed files with 76 additions and 66 deletions

View file

@ -1,4 +1,4 @@
export function findCommonElement <T> (array1: T[], array2: T[]) {
export function findCommonElement<T> (array1: T[], array2: T[]) {
for (const a of array1) {
for (const b of array2) {
if (a === b) return a
@ -9,13 +9,14 @@ export function findCommonElement <T> (array1: T[], array2: T[]) {
}
// Avoid conflict with other toArray() functions
export function arrayify <T> (element: T | T[]) {
export function arrayify<T> (element: T | T[]) {
if (Array.isArray(element)) return element
if (element === undefined || element === null) return []
return [ element ]
}
export function unarray <T> (element: T | T[]) {
export function unarray<T> (element: T | T[]) {
if (Array.isArray(element)) {
if (element.length === 0) return undefined
@ -26,24 +27,24 @@ export function unarray <T> (element: T | T[]) {
}
// Avoid conflict with other uniq() functions
export function uniqify <T> (elements: T[]) {
export function uniqify<T> (elements: T[]) {
return Array.from(new Set(elements))
}
// Thanks: https://stackoverflow.com/a/12646864
export function shuffle <T> (elements: T[]) {
export function shuffle<T> (elements: T[]) {
const shuffled = [ ...elements ]
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const j = Math.floor(Math.random() * (i + 1))
[ shuffled[i], shuffled[j] ] = [ shuffled[j], shuffled[i] ]
;[ shuffled[i], shuffled[j] ] = [ shuffled[j], shuffled[i] ]
}
return shuffled
}
export function sortBy <T> (obj: T[], key1: string, key2?: string): T[] {
export function sortBy<T> (obj: T[], key1: string, key2?: string): T[] {
return obj.sort((a, b) => {
const elem1 = key2 ? a[key1][key2] : a[key1]
const elem2 = key2 ? b[key1][key2] : b[key1]
@ -54,7 +55,7 @@ export function sortBy <T> (obj: T[], key1: string, key2?: string): T[] {
})
}
export function maxBy <T> (arr: T[], property: keyof T) {
export function maxBy<T> (arr: T[], property: keyof T) {
let result: T
for (const obj of arr) {
@ -64,7 +65,7 @@ export function maxBy <T> (arr: T[], property: keyof T) {
return result
}
export function minBy <T> (arr: T[], property: keyof T) {
export function minBy<T> (arr: T[], property: keyof T) {
let result: T
for (const obj of arr) {

View file

@ -12,46 +12,53 @@ import {
} from './objects/index.js'
export type ActivityUpdateObject =
Extract<ActivityObject, VideoObject | CacheFileObject | PlaylistObject | ActivityPubActor | string> | ActivityPubActor
| Extract<ActivityObject, VideoObject | CacheFileObject | PlaylistObject | ActivityPubActor | string>
| ActivityPubActor
// Cannot Extract from Activity because of circular reference
export type ActivityUndoObject =
ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate<CacheFileObject | string> | ActivityAnnounce
| ActivityFollow
| ActivityLike
| ActivityDislike
| ActivityCreate<CacheFileObject | string>
| ActivityAnnounce
export type ActivityCreateObject =
Extract<ActivityObject, VideoObject | CacheFileObject | WatchActionObject | VideoCommentObject | PlaylistObject | string>
export type ActivityCreateObject = Extract<
ActivityObject,
VideoObject | CacheFileObject | WatchActionObject | VideoCommentObject | PlaylistObject | string
>
export type Activity =
ActivityCreate<ActivityCreateObject> |
ActivityUpdate<ActivityUpdateObject> |
ActivityDelete |
ActivityFollow |
ActivityAccept |
ActivityAnnounce |
ActivityUndo<ActivityUndoObject> |
ActivityLike |
ActivityReject |
ActivityView |
ActivityDislike |
ActivityFlag |
ActivityApproveReply |
ActivityRejectReply
| ActivityCreate<ActivityCreateObject>
| ActivityUpdate<ActivityUpdateObject>
| ActivityDelete
| ActivityFollow
| ActivityAccept
| ActivityAnnounce
| ActivityUndo<ActivityUndoObject>
| ActivityLike
| ActivityReject
| ActivityView
| ActivityDislike
| ActivityFlag
| ActivityApproveReply
| ActivityRejectReply
export type ActivityType =
'Create' |
'Update' |
'Delete' |
'Follow' |
'Accept' |
'Announce' |
'Undo' |
'Like' |
'Reject' |
'View' |
'Dislike' |
'Flag' |
'ApproveReply' |
'RejectReply'
| 'Create'
| 'Update'
| 'Delete'
| 'Follow'
| 'Accept'
| 'Announce'
| 'Undo'
| 'Like'
| 'Reject'
| 'View'
| 'Dislike'
| 'Flag'
| 'ApproveReply'
| 'RejectReply'
export interface ActivityAudience {
to: string[]
@ -61,19 +68,19 @@ export interface ActivityAudience {
export interface BaseActivity {
'@context'?: any[]
id: string
to?: string[]
cc?: string[]
to?: string[] | string
cc?: string[] | string
actor: string | ActivityPubActor
type: ActivityType
signature?: ActivityPubSignature
}
export interface ActivityCreate <T extends ActivityCreateObject> extends BaseActivity {
export interface ActivityCreate<T extends ActivityCreateObject> extends BaseActivity {
type: 'Create'
object: T
}
export interface ActivityUpdate <T extends ActivityUpdateObject> extends BaseActivity {
export interface ActivityUpdate<T extends ActivityUpdateObject> extends BaseActivity {
type: 'Update'
object: T
}
@ -115,7 +122,7 @@ export interface ActivityAnnounce extends BaseActivity {
object: APObjectId
}
export interface ActivityUndo <T extends ActivityUndoObject> extends BaseActivity {
export interface ActivityUndo<T extends ActivityUndoObject> extends BaseActivity {
type: 'Undo'
object: T
}

View file

@ -1,3 +1,4 @@
import { arrayify } from '@peertube/peertube-core-utils'
import { ContextType } from '@peertube/peertube-models'
import { ACTIVITY_PUB, REMOTE_SCHEME } from '@server/initializers/constants.js'
import { isArray } from './custom-validators/misc.js'
@ -53,12 +54,10 @@ export function getAPPublicValue (): 'https://www.w3.org/ns/activitystreams#Publ
return 'https://www.w3.org/ns/activitystreams#Public'
}
export function hasAPPublic (toOrCC: string[]) {
if (!isArray(toOrCC)) return false
export function hasAPPublic (toOrCC: string[] | string) {
const publicValue = getAPPublicValue()
return toOrCC.some(f => f === 'as:Public' || publicValue)
return arrayify(toOrCC).some(f => f === 'as:Public' || publicValue)
}
// ---------------------------------------------------------------------------

View file

@ -35,6 +35,7 @@ export function isBaseActivityValid (activity: any, type: string) {
export function isUrlCollectionValid (collection: any) {
return collection === undefined ||
(typeof collection === 'string' && isActivityPubUrlValid(collection)) ||
(Array.isArray(collection) && collection.every(t => isActivityPubUrlValid(t)))
}

View file

@ -1,8 +1,8 @@
import { ActivityTombstoneObject, VideoCommentObject } from '@peertube/peertube-models'
import { hasAPPublic } from '@server/helpers/activity-pub-utils.js'
import validator from 'validator'
import { exists, isArray, isDateValid } from '../misc.js'
import { exists, isDateValid } from '../misc.js'
import { isActivityPubUrlValid } from './misc.js'
import { ActivityTombstoneObject, VideoCommentObject } from '@peertube/peertube-models'
function sanitizeAndCheckVideoCommentObject (comment: VideoCommentObject | ActivityTombstoneObject) {
if (!comment) return false
@ -23,7 +23,6 @@ function sanitizeAndCheckVideoCommentObject (comment: VideoCommentObject | Activ
isActivityPubUrlValid(comment.inReplyTo) &&
isDateValid(comment.published) &&
isActivityPubUrlValid(comment.url) &&
isArray(comment.to) &&
(!exists(comment.replyApproval) || isActivityPubUrlValid(comment.replyApproval)) &&
(hasAPPublic(comment.to) || hasAPPublic(comment.cc)) // Only accept public comments
}

View file

@ -1,9 +1,9 @@
import { sanitizeAndCheckActorObject } from '@server/helpers/custom-validators/activitypub/actor.js'
import { logger } from '@server/helpers/logger.js'
import { ActivityPubActor, ActivityPubOrderedCollection } from '@peertube/peertube-models'
import { sanitizeAndCheckActorObject } from '@server/helpers/custom-validators/activitypub/actor.js'
import { isUrlValid } from '@server/helpers/custom-validators/activitypub/misc.js'
import { logger } from '@server/helpers/logger.js'
import { fetchAP } from '../../activity.js'
import { checkUrlsSameHost } from '../../url.js'
import { isUrlValid } from '@server/helpers/custom-validators/activitypub/misc.js'
export async function fetchRemoteActor (
actorUrl: string,
@ -17,7 +17,7 @@ export async function fetchRemoteActor (
logger.debug('Remote actor JSON is not valid.', { actorJSON: body })
// Retry with the public key owner
if (canRefetchPublicKeyOwner && hasPublicKeyOwner(body)) {
if (canRefetchPublicKeyOwner && hasPublicKeyOwner(actorUrl, body)) {
logger.debug('Retrying with public key owner ' + body.publicKey.owner)
return fetchRemoteActor(body.publicKey.owner, false)
@ -63,6 +63,6 @@ async function fetchActorTotalItems (url: string) {
}
}
function hasPublicKeyOwner (actor: ActivityPubActor) {
return isUrlValid(actor?.publicKey?.owner)
function hasPublicKeyOwner (actorUrl: string, actor: ActivityPubActor) {
return isUrlValid(actor?.publicKey?.owner) && checkUrlsSameHost(actorUrl, actor.publicKey.owner)
}

View file

@ -27,6 +27,7 @@ import { sendReplyApproval } from '../send/send-reply-approval.js'
import { forwardVideoRelatedActivity } from '../send/shared/send-utils.js'
import { resolveThread } from '../video-comments.js'
import { canVideoBeFederated, getOrCreateAPVideo } from '../videos/index.js'
import { arrayify } from '@peertube/peertube-core-utils'
async function processCreateActivity (options: APProcessorOptions<ActivityCreate<ActivityCreateObject>>) {
const { activity, byActor } = options
@ -186,5 +187,5 @@ async function processCreatePlaylist (
const byAccount = byActor.Account
if (!byAccount) throw new Error('Cannot create video playlist with the non account actor ' + byActor.url)
await createOrUpdateVideoPlaylist({ playlistObject, contextUrl: byActor.url, to: activity.to })
await createOrUpdateVideoPlaylist({ playlistObject, contextUrl: byActor.url, to: arrayify(activity.to) })
}

View file

@ -1,3 +1,4 @@
import { arrayify } from '@peertube/peertube-core-utils'
import {
ActivityPubActor,
ActivityPubActorType,
@ -78,7 +79,7 @@ async function processUpdateVideo (activity: ActivityUpdate<VideoObject | string
if (created) return
const updater = new APVideoUpdater(videoObject, video)
return updater.update(activity.to)
return updater.update(arrayify(activity.to))
}
async function processUpdateCacheFile (
@ -127,5 +128,5 @@ async function processUpdatePlaylist (
const byAccount = byActor.Account
if (!byAccount) throw new Error('Cannot update video playlist with the non account actor ' + byActor.url)
await createOrUpdateVideoPlaylist({ playlistObject, contextUrl: byActor.url, to: activity.to })
await createOrUpdateVideoPlaylist({ playlistObject, contextUrl: byActor.url, to: arrayify(activity.to) })
}

View file

@ -17,6 +17,7 @@ import {
} from '../../../../types/models/index.js'
import { JobQueue } from '../../../job-queue/index.js'
import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getOriginVideoAudience } from './audience-utils.js'
import { arrayify } from '@peertube/peertube-core-utils'
async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: {
byActor: MActorLight
@ -102,8 +103,8 @@ async function forwardActivity (
) {
logger.info('Forwarding activity %s.', activity.id)
const to = activity.to || []
const cc = activity.cc || []
const to = arrayify(activity.to)
const cc = arrayify(activity.cc)
const followersUrls = additionalFollowerUrls
for (const dest of to.concat(cc)) {