mirror of https://github.com/Chocobozzz/PeerTube
Add ability to search a video with an URL
parent
22a16e36f6
commit
f6eebcb336
|
@ -4,7 +4,7 @@
|
|||
#search-video {
|
||||
@include peertube-input-text($search-input-width);
|
||||
margin-right: 15px;
|
||||
padding-right: 25px; // For the search icon
|
||||
padding-right: 40px; // For the search icon
|
||||
|
||||
&::placeholder {
|
||||
color: #000;
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
export * from './user-subscription.service'
|
||||
export * from './subscribe-button.component'
|
||||
export * from './subscribe-button.component'
|
||||
|
|
|
@ -17,11 +17,6 @@ export class VideoChannelService {
|
|||
|
||||
videoChannelLoaded = new ReplaySubject<VideoChannel>(1)
|
||||
|
||||
constructor (
|
||||
private authHttp: HttpClient,
|
||||
private restExtractor: RestExtractor
|
||||
) {}
|
||||
|
||||
static extractVideoChannels (result: ResultList<VideoChannelServer>) {
|
||||
const videoChannels: VideoChannel[] = []
|
||||
|
||||
|
@ -32,6 +27,11 @@ export class VideoChannelService {
|
|||
return { data: videoChannels, total: result.total }
|
||||
}
|
||||
|
||||
constructor (
|
||||
private authHttp: HttpClient,
|
||||
private restExtractor: RestExtractor
|
||||
) { }
|
||||
|
||||
getVideoChannel (videoChannelName: string) {
|
||||
return this.authHttp.get<VideoChannel>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName)
|
||||
.pipe(
|
||||
|
|
|
@ -13,6 +13,8 @@ import {
|
|||
videosSearchSortValidator
|
||||
} from '../../middlewares'
|
||||
import { VideosSearchQuery } from '../../../shared/models/search'
|
||||
import { getOrCreateAccountAndVideoAndChannel } from '../../lib/activitypub'
|
||||
import { logger } from '../../helpers/logger'
|
||||
|
||||
const searchRouter = express.Router()
|
||||
|
||||
|
@ -33,9 +35,16 @@ export { searchRouter }
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function searchVideos (req: express.Request, res: express.Response) {
|
||||
function searchVideos (req: express.Request, res: express.Response) {
|
||||
const query: VideosSearchQuery = req.query
|
||||
if (query.search.startsWith('http://') || query.search.startsWith('https://')) {
|
||||
return searchVideoUrl(query.search, res)
|
||||
}
|
||||
|
||||
return searchVideosDB(query, res)
|
||||
}
|
||||
|
||||
async function searchVideosDB (query: VideosSearchQuery, res: express.Response) {
|
||||
const options = Object.assign(query, {
|
||||
includeLocalVideos: true,
|
||||
nsfw: buildNSFWFilter(res, query.nsfw)
|
||||
|
@ -44,3 +53,27 @@ async function searchVideos (req: express.Request, res: express.Response) {
|
|||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||
}
|
||||
|
||||
async function searchVideoUrl (url: string, res: express.Response) {
|
||||
let video: VideoModel
|
||||
|
||||
try {
|
||||
const syncParam = {
|
||||
likes: false,
|
||||
dislikes: false,
|
||||
shares: false,
|
||||
comments: false,
|
||||
thumbnail: true
|
||||
}
|
||||
|
||||
const res = await getOrCreateAccountAndVideoAndChannel(url, syncParam)
|
||||
video = res ? res.video : undefined
|
||||
} catch (err) {
|
||||
logger.info('Cannot search remote video %s.', url)
|
||||
}
|
||||
|
||||
return res.json({
|
||||
total: video ? 1 : 0,
|
||||
data: video ? [ video.toFormattedJSON() ] : []
|
||||
})
|
||||
}
|
||||
|
|
|
@ -112,6 +112,7 @@ const JOB_TTL: { [ id in JobType ]: number } = {
|
|||
'email': 60000 * 10 // 10 minutes
|
||||
}
|
||||
const BROADCAST_CONCURRENCY = 10 // How many requests in parallel we do in activitypub-http-broadcast job
|
||||
const CRAWL_REQUEST_CONCURRENCY = 5 // How many requests in parallel to fetch remote data (likes, shares...)
|
||||
const JOB_REQUEST_TIMEOUT = 3000 // 3 seconds
|
||||
const JOB_COMPLETED_LIFETIME = 60000 * 60 * 24 * 2 // 2 days
|
||||
|
||||
|
@ -643,6 +644,7 @@ export {
|
|||
STATIC_DOWNLOAD_PATHS,
|
||||
RATES_LIMIT,
|
||||
VIDEO_EXT_MIMETYPE,
|
||||
CRAWL_REQUEST_CONCURRENCY,
|
||||
JOB_COMPLETED_LIFETIME,
|
||||
VIDEO_IMPORT_STATES,
|
||||
VIDEO_VIEW_LIFETIME,
|
||||
|
|
|
@ -177,7 +177,8 @@ async function addFetchOutboxJob (actor: ActorModel) {
|
|||
}
|
||||
|
||||
const payload = {
|
||||
uris: [ actor.outboxUrl ]
|
||||
uri: actor.outboxUrl,
|
||||
type: 'activity' as 'activity'
|
||||
}
|
||||
|
||||
return JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })
|
||||
|
@ -248,6 +249,7 @@ function saveActorAndServerAndModelIfNotExist (
|
|||
} else if (actorCreated.type === 'Group') { // Video channel
|
||||
actorCreated.VideoChannel = await saveVideoChannel(actorCreated, result, ownerActor, t)
|
||||
actorCreated.VideoChannel.Actor = actorCreated
|
||||
actorCreated.VideoChannel.Account = ownerActor.Account
|
||||
}
|
||||
|
||||
return actorCreated
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { ACTIVITY_PUB, JOB_REQUEST_TIMEOUT } from '../../initializers'
|
||||
import { doRequest } from '../../helpers/requests'
|
||||
import { logger } from '../../helpers/logger'
|
||||
import Bluebird = require('bluebird')
|
||||
|
||||
async function crawlCollectionPage <T> (uri: string, handler: (items: T[]) => Promise<any>) {
|
||||
async function crawlCollectionPage <T> (uri: string, handler: (items: T[]) => Promise<any> | Bluebird<any>) {
|
||||
logger.info('Crawling ActivityPub data on %s.', uri)
|
||||
|
||||
const options = {
|
||||
|
|
|
@ -24,10 +24,8 @@ export {
|
|||
|
||||
async function processVideoShare (actorAnnouncer: ActorModel, activity: ActivityAnnounce) {
|
||||
const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id
|
||||
let video: VideoModel
|
||||
|
||||
const res = await getOrCreateAccountAndVideoAndChannel(objectUri)
|
||||
video = res.video
|
||||
const { video } = await getOrCreateAccountAndVideoAndChannel(objectUri)
|
||||
|
||||
return sequelizeTypescript.transaction(async t => {
|
||||
// Add share entry
|
||||
|
|
|
@ -23,7 +23,7 @@ async function processCreateActivity (activity: ActivityCreate) {
|
|||
} else if (activityType === 'Dislike') {
|
||||
return retryTransactionWrapper(processCreateDislike, actor, activity)
|
||||
} else if (activityType === 'Video') {
|
||||
return processCreateVideo(actor, activity)
|
||||
return processCreateVideo(activity)
|
||||
} else if (activityType === 'Flag') {
|
||||
return retryTransactionWrapper(processCreateVideoAbuse, actor, activityObject as VideoAbuseObject)
|
||||
} else if (activityType === 'Note') {
|
||||
|
@ -42,13 +42,10 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function processCreateVideo (
|
||||
actor: ActorModel,
|
||||
activity: ActivityCreate
|
||||
) {
|
||||
async function processCreateVideo (activity: ActivityCreate) {
|
||||
const videoToCreateData = activity.object as VideoTorrentObject
|
||||
|
||||
const { video } = await getOrCreateAccountAndVideoAndChannel(videoToCreateData, actor)
|
||||
const { video } = await getOrCreateAccountAndVideoAndChannel(videoToCreateData)
|
||||
|
||||
return video
|
||||
}
|
||||
|
|
|
@ -2,12 +2,13 @@ import { VideoCommentObject } from '../../../shared/models/activitypub/objects/v
|
|||
import { sanitizeAndCheckVideoCommentObject } from '../../helpers/custom-validators/activitypub/video-comments'
|
||||
import { logger } from '../../helpers/logger'
|
||||
import { doRequest } from '../../helpers/requests'
|
||||
import { ACTIVITY_PUB } from '../../initializers'
|
||||
import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers'
|
||||
import { ActorModel } from '../../models/activitypub/actor'
|
||||
import { VideoModel } from '../../models/video/video'
|
||||
import { VideoCommentModel } from '../../models/video/video-comment'
|
||||
import { getOrCreateActorAndServerAndModel } from './actor'
|
||||
import { getOrCreateAccountAndVideoAndChannel } from './videos'
|
||||
import * as Bluebird from 'bluebird'
|
||||
|
||||
async function videoCommentActivityObjectToDBAttributes (video: VideoModel, actor: ActorModel, comment: VideoCommentObject) {
|
||||
let originCommentId: number = null
|
||||
|
@ -38,9 +39,9 @@ async function videoCommentActivityObjectToDBAttributes (video: VideoModel, acto
|
|||
}
|
||||
|
||||
async function addVideoComments (commentUrls: string[], instance: VideoModel) {
|
||||
for (const commentUrl of commentUrls) {
|
||||
await addVideoComment(instance, commentUrl)
|
||||
}
|
||||
return Bluebird.map(commentUrls, commentUrl => {
|
||||
return addVideoComment(instance, commentUrl)
|
||||
}, { concurrency: CRAWL_REQUEST_CONCURRENCY })
|
||||
}
|
||||
|
||||
async function addVideoComment (videoInstance: VideoModel, commentUrl: string) {
|
||||
|
|
|
@ -11,7 +11,7 @@ import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos
|
|||
import { retryTransactionWrapper } from '../../helpers/database-utils'
|
||||
import { logger } from '../../helpers/logger'
|
||||
import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests'
|
||||
import { ACTIVITY_PUB, CONFIG, REMOTE_SCHEME, sequelizeTypescript, VIDEO_MIMETYPE_EXT } from '../../initializers'
|
||||
import { ACTIVITY_PUB, CONFIG, CRAWL_REQUEST_CONCURRENCY, REMOTE_SCHEME, sequelizeTypescript, VIDEO_MIMETYPE_EXT } from '../../initializers'
|
||||
import { AccountVideoRateModel } from '../../models/account/account-video-rate'
|
||||
import { ActorModel } from '../../models/activitypub/actor'
|
||||
import { TagModel } from '../../models/video/tag'
|
||||
|
@ -26,6 +26,8 @@ import { sendCreateVideo, sendUpdateVideo } from './send'
|
|||
import { shareVideoByServerAndChannel } from './index'
|
||||
import { isArray } from '../../helpers/custom-validators/misc'
|
||||
import { VideoCaptionModel } from '../../models/video/video-caption'
|
||||
import { JobQueue } from '../job-queue'
|
||||
import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub-http-fetcher'
|
||||
|
||||
async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) {
|
||||
// If the video is not private and published, we federate it
|
||||
|
@ -178,10 +180,10 @@ function getOrCreateVideoChannel (videoObject: VideoTorrentObject) {
|
|||
return getOrCreateActorAndServerAndModel(channel.id)
|
||||
}
|
||||
|
||||
async function getOrCreateVideo (videoObject: VideoTorrentObject, channelActor: ActorModel) {
|
||||
async function getOrCreateVideo (videoObject: VideoTorrentObject, channelActor: ActorModel, waitThumbnail = false) {
|
||||
logger.debug('Adding remote video %s.', videoObject.id)
|
||||
|
||||
return sequelizeTypescript.transaction(async t => {
|
||||
const videoCreated: VideoModel = await sequelizeTypescript.transaction(async t => {
|
||||
const sequelizeOptions = {
|
||||
transaction: t
|
||||
}
|
||||
|
@ -191,10 +193,6 @@ async function getOrCreateVideo (videoObject: VideoTorrentObject, channelActor:
|
|||
const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoObject, videoObject.to)
|
||||
const video = VideoModel.build(videoData)
|
||||
|
||||
// Don't block on remote HTTP request (we are in a transaction!)
|
||||
generateThumbnailFromUrl(video, videoObject.icon)
|
||||
.catch(err => logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err }))
|
||||
|
||||
const videoCreated = await video.save(sequelizeOptions)
|
||||
|
||||
// Process files
|
||||
|
@ -222,68 +220,100 @@ async function getOrCreateVideo (videoObject: VideoTorrentObject, channelActor:
|
|||
videoCreated.VideoChannel = channelActor.VideoChannel
|
||||
return videoCreated
|
||||
})
|
||||
|
||||
const p = generateThumbnailFromUrl(videoCreated, videoObject.icon)
|
||||
.catch(err => logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err }))
|
||||
|
||||
if (waitThumbnail === true) await p
|
||||
|
||||
return videoCreated
|
||||
}
|
||||
|
||||
async function getOrCreateAccountAndVideoAndChannel (videoObject: VideoTorrentObject | string, actor?: ActorModel) {
|
||||
type SyncParam = {
|
||||
likes: boolean,
|
||||
dislikes: boolean,
|
||||
shares: boolean,
|
||||
comments: boolean,
|
||||
thumbnail: boolean
|
||||
}
|
||||
async function getOrCreateAccountAndVideoAndChannel (
|
||||
videoObject: VideoTorrentObject | string,
|
||||
syncParam: SyncParam = { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true }
|
||||
) {
|
||||
const videoUrl = typeof videoObject === 'string' ? videoObject : videoObject.id
|
||||
|
||||
const videoFromDatabase = await VideoModel.loadByUrlAndPopulateAccount(videoUrl)
|
||||
if (videoFromDatabase) {
|
||||
return {
|
||||
video: videoFromDatabase,
|
||||
actor: videoFromDatabase.VideoChannel.Account.Actor,
|
||||
channelActor: videoFromDatabase.VideoChannel.Actor
|
||||
}
|
||||
}
|
||||
if (videoFromDatabase) return { video: videoFromDatabase }
|
||||
|
||||
videoObject = await fetchRemoteVideo(videoUrl)
|
||||
if (!videoObject) throw new Error('Cannot fetch remote video with url: ' + videoUrl)
|
||||
const fetchedVideo = await fetchRemoteVideo(videoUrl)
|
||||
if (!fetchedVideo) throw new Error('Cannot fetch remote video with url: ' + videoUrl)
|
||||
|
||||
if (!actor) {
|
||||
const actorObj = videoObject.attributedTo.find(a => a.type === 'Person')
|
||||
if (!actorObj) throw new Error('Cannot find associated actor to video ' + videoObject.url)
|
||||
|
||||
actor = await getOrCreateActorAndServerAndModel(actorObj.id)
|
||||
}
|
||||
|
||||
const channelActor = await getOrCreateVideoChannel(videoObject)
|
||||
|
||||
const video = await retryTransactionWrapper(getOrCreateVideo, videoObject, channelActor)
|
||||
const channelActor = await getOrCreateVideoChannel(fetchedVideo)
|
||||
const video = await retryTransactionWrapper(getOrCreateVideo, fetchedVideo, channelActor, syncParam.thumbnail)
|
||||
|
||||
// Process outside the transaction because we could fetch remote data
|
||||
logger.info('Adding likes of video %s.', video.uuid)
|
||||
await crawlCollectionPage<string>(videoObject.likes, (items) => createRates(items, video, 'like'))
|
||||
|
||||
logger.info('Adding dislikes of video %s.', video.uuid)
|
||||
await crawlCollectionPage<string>(videoObject.dislikes, (items) => createRates(items, video, 'dislike'))
|
||||
logger.info('Adding likes/dislikes/shares/comments of video %s.', video.uuid)
|
||||
|
||||
logger.info('Adding shares of video %s.', video.uuid)
|
||||
await crawlCollectionPage<string>(videoObject.shares, (items) => addVideoShares(items, video))
|
||||
const jobPayloads: ActivitypubHttpFetcherPayload[] = []
|
||||
|
||||
logger.info('Adding comments of video %s.', video.uuid)
|
||||
await crawlCollectionPage<string>(videoObject.comments, (items) => addVideoComments(items, video))
|
||||
if (syncParam.likes === true) {
|
||||
await crawlCollectionPage<string>(fetchedVideo.likes, items => createRates(items, video, 'like'))
|
||||
.catch(err => logger.error('Cannot add likes of video %s.', video.uuid, { err }))
|
||||
} else {
|
||||
jobPayloads.push({ uri: fetchedVideo.likes, videoId: video.id, type: 'video-likes' as 'video-likes' })
|
||||
}
|
||||
|
||||
return { actor, channelActor, video }
|
||||
if (syncParam.dislikes === true) {
|
||||
await crawlCollectionPage<string>(fetchedVideo.dislikes, items => createRates(items, video, 'dislike'))
|
||||
.catch(err => logger.error('Cannot add dislikes of video %s.', video.uuid, { err }))
|
||||
} else {
|
||||
jobPayloads.push({ uri: fetchedVideo.dislikes, videoId: video.id, type: 'video-dislikes' as 'video-dislikes' })
|
||||
}
|
||||
|
||||
if (syncParam.shares === true) {
|
||||
await crawlCollectionPage<string>(fetchedVideo.shares, items => addVideoShares(items, video))
|
||||
.catch(err => logger.error('Cannot add shares of video %s.', video.uuid, { err }))
|
||||
} else {
|
||||
jobPayloads.push({ uri: fetchedVideo.shares, videoId: video.id, type: 'video-shares' as 'video-shares' })
|
||||
}
|
||||
|
||||
if (syncParam.comments === true) {
|
||||
await crawlCollectionPage<string>(fetchedVideo.comments, items => addVideoComments(items, video))
|
||||
.catch(err => logger.error('Cannot add comments of video %s.', video.uuid, { err }))
|
||||
} else {
|
||||
jobPayloads.push({ uri: fetchedVideo.shares, videoId: video.id, type: 'video-shares' as 'video-shares' })
|
||||
}
|
||||
|
||||
await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }))
|
||||
|
||||
return { video }
|
||||
}
|
||||
|
||||
async function createRates (actorUrls: string[], video: VideoModel, rate: VideoRateType) {
|
||||
let rateCounts = 0
|
||||
const tasks: Bluebird<number>[] = []
|
||||
|
||||
for (const actorUrl of actorUrls) {
|
||||
const actor = await getOrCreateActorAndServerAndModel(actorUrl)
|
||||
const p = AccountVideoRateModel
|
||||
.create({
|
||||
videoId: video.id,
|
||||
accountId: actor.Account.id,
|
||||
type: rate
|
||||
})
|
||||
.then(() => rateCounts += 1)
|
||||
await Bluebird.map(actorUrls, async actorUrl => {
|
||||
try {
|
||||
const actor = await getOrCreateActorAndServerAndModel(actorUrl)
|
||||
const [ , created ] = await AccountVideoRateModel
|
||||
.findOrCreate({
|
||||
where: {
|
||||
videoId: video.id,
|
||||
accountId: actor.Account.id
|
||||
},
|
||||
defaults: {
|
||||
videoId: video.id,
|
||||
accountId: actor.Account.id,
|
||||
type: rate
|
||||
}
|
||||
})
|
||||
|
||||
tasks.push(p)
|
||||
}
|
||||
|
||||
await Promise.all(tasks)
|
||||
if (created) rateCounts += 1
|
||||
} catch (err) {
|
||||
logger.warn('Cannot add rate %s for actor %s.', rate, actorUrl, { err })
|
||||
}
|
||||
}, { concurrency: CRAWL_REQUEST_CONCURRENCY })
|
||||
|
||||
logger.info('Adding %d %s to video %s.', rateCounts, rate, video.uuid)
|
||||
|
||||
|
@ -294,34 +324,35 @@ async function createRates (actorUrls: string[], video: VideoModel, rate: VideoR
|
|||
}
|
||||
|
||||
async function addVideoShares (shareUrls: string[], instance: VideoModel) {
|
||||
for (const shareUrl of shareUrls) {
|
||||
// Fetch url
|
||||
const { body } = await doRequest({
|
||||
uri: shareUrl,
|
||||
json: true,
|
||||
activityPub: true
|
||||
})
|
||||
if (!body || !body.actor) {
|
||||
logger.warn('Cannot add remote share with url: %s, skipping...', shareUrl)
|
||||
continue
|
||||
}
|
||||
await Bluebird.map(shareUrls, async shareUrl => {
|
||||
try {
|
||||
// Fetch url
|
||||
const { body } = await doRequest({
|
||||
uri: shareUrl,
|
||||
json: true,
|
||||
activityPub: true
|
||||
})
|
||||
if (!body || !body.actor) throw new Error('Body of body actor is invalid')
|
||||
|
||||
const actorUrl = body.actor
|
||||
const actor = await getOrCreateActorAndServerAndModel(actorUrl)
|
||||
const actorUrl = body.actor
|
||||
const actor = await getOrCreateActorAndServerAndModel(actorUrl)
|
||||
|
||||
const entry = {
|
||||
actorId: actor.id,
|
||||
videoId: instance.id,
|
||||
url: shareUrl
|
||||
}
|
||||
|
||||
await VideoShareModel.findOrCreate({
|
||||
where: {
|
||||
const entry = {
|
||||
actorId: actor.id,
|
||||
videoId: instance.id,
|
||||
url: shareUrl
|
||||
},
|
||||
defaults: entry
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
await VideoShareModel.findOrCreate({
|
||||
where: {
|
||||
url: shareUrl
|
||||
},
|
||||
defaults: entry
|
||||
})
|
||||
} catch (err) {
|
||||
logger.warn('Cannot add share %s.', shareUrl, { err })
|
||||
}
|
||||
}, { concurrency: CRAWL_REQUEST_CONCURRENCY })
|
||||
}
|
||||
|
||||
async function fetchRemoteVideo (videoUrl: string): Promise<VideoTorrentObject> {
|
||||
|
@ -355,5 +386,6 @@ export {
|
|||
videoFileActivityUrlToDBAttributes,
|
||||
getOrCreateVideo,
|
||||
getOrCreateVideoChannel,
|
||||
addVideoShares
|
||||
addVideoShares,
|
||||
createRates
|
||||
}
|
||||
|
|
|
@ -1,22 +1,36 @@
|
|||
import * as Bull from 'bull'
|
||||
import { logger } from '../../../helpers/logger'
|
||||
import { processActivities } from '../../activitypub/process'
|
||||
import { ActivitypubHttpBroadcastPayload } from './activitypub-http-broadcast'
|
||||
import { VideoModel } from '../../../models/video/video'
|
||||
import { addVideoShares, createRates } from '../../activitypub/videos'
|
||||
import { addVideoComments } from '../../activitypub/video-comments'
|
||||
import { crawlCollectionPage } from '../../activitypub/crawl'
|
||||
import { Activity } from '../../../../shared/models/activitypub'
|
||||
|
||||
type FetchType = 'activity' | 'video-likes' | 'video-dislikes' | 'video-shares' | 'video-comments'
|
||||
|
||||
export type ActivitypubHttpFetcherPayload = {
|
||||
uris: string[]
|
||||
uri: string
|
||||
type: FetchType
|
||||
videoId?: number
|
||||
}
|
||||
|
||||
async function processActivityPubHttpFetcher (job: Bull.Job) {
|
||||
logger.info('Processing ActivityPub fetcher in job %d.', job.id)
|
||||
|
||||
const payload = job.data as ActivitypubHttpBroadcastPayload
|
||||
const payload = job.data as ActivitypubHttpFetcherPayload
|
||||
|
||||
for (const uri of payload.uris) {
|
||||
await crawlCollectionPage<Activity>(uri, (items) => processActivities(items))
|
||||
let video: VideoModel
|
||||
if (payload.videoId) video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoId)
|
||||
|
||||
const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise<any> } = {
|
||||
'activity': items => processActivities(items),
|
||||
'video-likes': items => createRates(items, video, 'like'),
|
||||
'video-dislikes': items => createRates(items, video, 'dislike'),
|
||||
'video-shares': items => addVideoShares(items, video),
|
||||
'video-comments': items => addVideoComments(items, video)
|
||||
}
|
||||
|
||||
return crawlCollectionPage(payload.uri, fetcherType[payload.type])
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
@ -20,7 +20,7 @@ describe('Test videos API validator', function () {
|
|||
let userAccessToken = ''
|
||||
let accountName: string
|
||||
let channelId: number
|
||||
let channelUUID: string
|
||||
let channelName: string
|
||||
let videoId
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
|
@ -42,7 +42,7 @@ describe('Test videos API validator', function () {
|
|||
{
|
||||
const res = await getMyUserInformation(server.url, server.accessToken)
|
||||
channelId = res.body.videoChannels[ 0 ].id
|
||||
channelUUID = res.body.videoChannels[ 0 ].uuid
|
||||
channelName = res.body.videoChannels[ 0 ].name
|
||||
accountName = res.body.account.name + '@' + res.body.account.host
|
||||
}
|
||||
})
|
||||
|
@ -140,7 +140,7 @@ describe('Test videos API validator', function () {
|
|||
let path: string
|
||||
|
||||
before(async function () {
|
||||
path = '/api/v1/video-channels/' + channelUUID + '/videos'
|
||||
path = '/api/v1/video-channels/' + channelName + '/videos'
|
||||
})
|
||||
|
||||
it('Should fail with a bad start pagination', async function () {
|
||||
|
|
|
@ -311,7 +311,8 @@ describe('Test follows', function () {
|
|||
likes: 1,
|
||||
dislikes: 1,
|
||||
channel: {
|
||||
name: 'Main root channel',
|
||||
displayName: 'Main root channel',
|
||||
name: 'root_channel',
|
||||
description: '',
|
||||
isLocal
|
||||
},
|
||||
|
|
|
@ -71,7 +71,8 @@ describe('Test handle downs', function () {
|
|||
privacy: VideoPrivacy.PUBLIC,
|
||||
commentsEnabled: true,
|
||||
channel: {
|
||||
name: 'Main root channel',
|
||||
name: 'root_channel',
|
||||
displayName: 'Main root channel',
|
||||
description: '',
|
||||
isLocal: false
|
||||
},
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import * as chai from 'chai'
|
||||
import 'mocha'
|
||||
import { createUser, doubleFollow, flushAndRunMultipleServers, follow, getVideosList, unfollow, userLogin } from '../../utils'
|
||||
import { createUser, doubleFollow, flushAndRunMultipleServers, follow, getVideosList, unfollow, updateVideo, userLogin } from '../../utils'
|
||||
import { killallServers, ServerInfo, uploadVideo } from '../../utils/index'
|
||||
import { setAccessTokensToServers } from '../../utils/users/login'
|
||||
import { Video, VideoChannel } from '../../../../shared/models/videos'
|
||||
|
@ -20,6 +20,7 @@ const expect = chai.expect
|
|||
describe('Test users subscriptions', function () {
|
||||
let servers: ServerInfo[] = []
|
||||
const users: { accessToken: string }[] = []
|
||||
let video3UUID: string
|
||||
|
||||
before(async function () {
|
||||
this.timeout(120000)
|
||||
|
@ -65,7 +66,8 @@ describe('Test users subscriptions', function () {
|
|||
|
||||
await waitJobs(servers)
|
||||
|
||||
await uploadVideo(servers[2].url, users[2].accessToken, { name: 'video server 3 added after follow' })
|
||||
const res = await uploadVideo(servers[2].url, users[2].accessToken, { name: 'video server 3 added after follow' })
|
||||
video3UUID = res.body.video.uuid
|
||||
|
||||
await waitJobs(servers)
|
||||
})
|
||||
|
@ -247,7 +249,21 @@ describe('Test users subscriptions', function () {
|
|||
}
|
||||
})
|
||||
|
||||
it('Should update a video of server 3 and see the updated video on server 1', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
await updateVideo(servers[2].url, users[2].accessToken, video3UUID, { name: 'video server 3 added after follow updated' })
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
const res = await listUserSubscriptionVideos(servers[0].url, users[0].accessToken, 'createdAt')
|
||||
const videos: Video[] = res.body.data
|
||||
expect(videos[2].name).to.equal('video server 3 added after follow updated')
|
||||
})
|
||||
|
||||
it('Should remove user of server 3 subscription', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
await removeUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:9003')
|
||||
|
||||
await waitJobs(servers)
|
||||
|
@ -267,6 +283,8 @@ describe('Test users subscriptions', function () {
|
|||
})
|
||||
|
||||
it('Should remove the root subscription and not display the videos anymore', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
await removeUserSubscription(servers[0].url, users[0].accessToken, 'root_channel@localhost:9001')
|
||||
|
||||
await waitJobs(servers)
|
||||
|
@ -288,7 +306,7 @@ describe('Test users subscriptions', function () {
|
|||
for (const video of res.body.data) {
|
||||
expect(video.name).to.not.contain('1-3')
|
||||
expect(video.name).to.not.contain('2-3')
|
||||
expect(video.name).to.not.contain('video server 3 added after follow')
|
||||
expect(video.name).to.not.contain('video server 3 added after follow updated')
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -309,7 +327,7 @@ describe('Test users subscriptions', function () {
|
|||
|
||||
expect(videos[0].name).to.equal('video 1-3')
|
||||
expect(videos[1].name).to.equal('video 2-3')
|
||||
expect(videos[2].name).to.equal('video server 3 added after follow')
|
||||
expect(videos[2].name).to.equal('video server 3 added after follow updated')
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -319,7 +337,7 @@ describe('Test users subscriptions', function () {
|
|||
for (const video of res.body.data) {
|
||||
expect(video.name).to.not.contain('1-3')
|
||||
expect(video.name).to.not.contain('2-3')
|
||||
expect(video.name).to.not.contain('video server 3 added after follow')
|
||||
expect(video.name).to.not.contain('video server 3 added after follow updated')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -128,7 +128,8 @@ describe('Test multiple servers', function () {
|
|||
privacy: VideoPrivacy.PUBLIC,
|
||||
commentsEnabled: true,
|
||||
channel: {
|
||||
name: 'my channel',
|
||||
displayName: 'my channel',
|
||||
name: 'super_channel_name',
|
||||
description: 'super channel',
|
||||
isLocal
|
||||
},
|
||||
|
@ -201,7 +202,8 @@ describe('Test multiple servers', function () {
|
|||
tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ],
|
||||
privacy: VideoPrivacy.PUBLIC,
|
||||
channel: {
|
||||
name: 'Main user1 channel',
|
||||
displayName: 'Main user1 channel',
|
||||
name: 'user1_channel',
|
||||
description: 'super channel',
|
||||
isLocal
|
||||
},
|
||||
|
@ -307,7 +309,8 @@ describe('Test multiple servers', function () {
|
|||
tags: [ 'tag1p3' ],
|
||||
privacy: VideoPrivacy.PUBLIC,
|
||||
channel: {
|
||||
name: 'Main root channel',
|
||||
displayName: 'Main root channel',
|
||||
name: 'root_channel',
|
||||
description: '',
|
||||
isLocal
|
||||
},
|
||||
|
@ -339,7 +342,8 @@ describe('Test multiple servers', function () {
|
|||
tags: [ 'tag2p3', 'tag3p3', 'tag4p3' ],
|
||||
privacy: VideoPrivacy.PUBLIC,
|
||||
channel: {
|
||||
name: 'Main root channel',
|
||||
displayName: 'Main root channel',
|
||||
name: 'root_channel',
|
||||
description: '',
|
||||
isLocal
|
||||
},
|
||||
|
@ -647,7 +651,8 @@ describe('Test multiple servers', function () {
|
|||
tags: [ 'tag_up_1', 'tag_up_2' ],
|
||||
privacy: VideoPrivacy.PUBLIC,
|
||||
channel: {
|
||||
name: 'Main root channel',
|
||||
displayName: 'Main root channel',
|
||||
name: 'root_channel',
|
||||
description: '',
|
||||
isLocal
|
||||
},
|
||||
|
@ -967,7 +972,8 @@ describe('Test multiple servers', function () {
|
|||
tags: [ ],
|
||||
privacy: VideoPrivacy.PUBLIC,
|
||||
channel: {
|
||||
name: 'Main root channel',
|
||||
displayName: 'Main root channel',
|
||||
name: 'root_channel',
|
||||
description: '',
|
||||
isLocal
|
||||
},
|
||||
|
|
|
@ -56,7 +56,8 @@ describe('Test a single server', function () {
|
|||
privacy: VideoPrivacy.PUBLIC,
|
||||
commentsEnabled: true,
|
||||
channel: {
|
||||
name: 'Main root channel',
|
||||
displayName: 'Main root channel',
|
||||
name: 'root_channel',
|
||||
description: '',
|
||||
isLocal: true
|
||||
},
|
||||
|
@ -87,7 +88,8 @@ describe('Test a single server', function () {
|
|||
duration: 5,
|
||||
commentsEnabled: false,
|
||||
channel: {
|
||||
name: 'Main root channel',
|
||||
name: 'root_channel',
|
||||
displayName: 'Main root channel',
|
||||
description: '',
|
||||
isLocal: true
|
||||
},
|
||||
|
|
|
@ -438,18 +438,19 @@ async function completeVideoCheck (
|
|||
name: string
|
||||
host: string
|
||||
}
|
||||
isLocal: boolean,
|
||||
tags: string[],
|
||||
privacy: number,
|
||||
likes?: number,
|
||||
dislikes?: number,
|
||||
duration: number,
|
||||
isLocal: boolean
|
||||
tags: string[]
|
||||
privacy: number
|
||||
likes?: number
|
||||
dislikes?: number
|
||||
duration: number
|
||||
channel: {
|
||||
name: string,
|
||||
displayName: string
|
||||
name: string
|
||||
description
|
||||
isLocal: boolean
|
||||
}
|
||||
fixture: string,
|
||||
fixture: string
|
||||
files: {
|
||||
resolution: number
|
||||
size: number
|
||||
|
@ -476,8 +477,8 @@ async function completeVideoCheck (
|
|||
expect(video.account.uuid).to.be.a('string')
|
||||
expect(video.account.host).to.equal(attributes.account.host)
|
||||
expect(video.account.name).to.equal(attributes.account.name)
|
||||
expect(video.channel.displayName).to.equal(attributes.channel.name)
|
||||
expect(video.channel.name).to.have.lengthOf(36)
|
||||
expect(video.channel.displayName).to.equal(attributes.channel.displayName)
|
||||
expect(video.channel.name).to.equal(attributes.channel.name)
|
||||
expect(video.likes).to.equal(attributes.likes)
|
||||
expect(video.dislikes).to.equal(attributes.dislikes)
|
||||
expect(video.isLocal).to.equal(attributes.isLocal)
|
||||
|
@ -497,8 +498,8 @@ async function completeVideoCheck (
|
|||
expect(videoDetails.tags).to.deep.equal(attributes.tags)
|
||||
expect(videoDetails.account.name).to.equal(attributes.account.name)
|
||||
expect(videoDetails.account.host).to.equal(attributes.account.host)
|
||||
expect(videoDetails.channel.displayName).to.equal(attributes.channel.name)
|
||||
expect(videoDetails.channel.name).to.have.lengthOf(36)
|
||||
expect(video.channel.displayName).to.equal(attributes.channel.displayName)
|
||||
expect(video.channel.name).to.equal(attributes.channel.name)
|
||||
expect(videoDetails.channel.host).to.equal(attributes.account.host)
|
||||
expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal)
|
||||
expect(dateIsValid(videoDetails.channel.createdAt.toString())).to.be.true
|
||||
|
|
Loading…
Reference in New Issue