1
0
Fork 0

Fix user notifications on new follow

This commit is contained in:
Chocobozzz 2019-08-02 10:53:36 +02:00
parent 44b88f180b
commit 1198edf4bb
No known key found for this signature in database
GPG key ID: 583A612D890159BE
17 changed files with 82 additions and 36 deletions

View file

@ -29,9 +29,11 @@
<noscript> <noscript>
<p>You are blocking Javascript, and we totally get that. However this endpoint uses Angular, so the front end is in full JavaScript and won't work without it. <p>You are blocking Javascript, and we totally get that. However this endpoint uses Angular, so the front end is in full JavaScript and won't work without it.
</br></br>
<br /><br />
There will be other non JS-based clients to access PeerTube, but for now none is available. Be sure we will update this page with a list once alternative clients are developed. You can certainly develop you own in the meantime as our code is open source and libre software under GNU AGPLv3.0. There will be other non JS-based clients to access PeerTube, but for now none is available. Be sure we will update this page with a list once alternative clients are developed. You can certainly develop you own in the meantime as our code is open source and libre software under GNU AGPLv3.0.
</br></br>
<br /><br />
There might be numerous reasons you refuse to use JavaScript. If it has just to do with security (or lack thereof) of JavaScript-based webapps, then depending on your threat menace you might want to go through the code running on the node you are trying to access, and look for security audits. There might be numerous reasons you refuse to use JavaScript. If it has just to do with security (or lack thereof) of JavaScript-based webapps, then depending on your threat menace you might want to go through the code running on the node you are trying to access, and look for security audits.
</p> </p>
</noscript> </noscript>

View file

@ -2,8 +2,10 @@ import { ActivityAccept } from '../../../../shared/models/activitypub'
import { ActorModel } from '../../../models/activitypub/actor' import { ActorModel } from '../../../models/activitypub/actor'
import { ActorFollowModel } from '../../../models/activitypub/actor-follow' import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
import { addFetchOutboxJob } from '../actor' import { addFetchOutboxJob } from '../actor'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
async function processAcceptActivity (activity: ActivityAccept, targetActor: ActorModel, inboxActor?: ActorModel) { async function processAcceptActivity (options: APProcessorOptions<ActivityAccept>) {
const { byActor: targetActor, inboxActor } = options
if (inboxActor === undefined) throw new Error('Need to accept on explicit inbox.') if (inboxActor === undefined) throw new Error('Need to accept on explicit inbox.')
return processAccept(inboxActor, targetActor) return processAccept(inboxActor, targetActor)

View file

@ -8,9 +8,14 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos'
import { Notifier } from '../../notifier' import { Notifier } from '../../notifier'
import { VideoModel } from '../../../models/video/video' import { VideoModel } from '../../../models/video/video'
import { logger } from '../../../helpers/logger' import { logger } from '../../../helpers/logger'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
async function processAnnounceActivity (activity: ActivityAnnounce, actorAnnouncer: ActorModel) { async function processAnnounceActivity (options: APProcessorOptions<ActivityAnnounce>) {
return retryTransactionWrapper(processVideoShare, actorAnnouncer, activity) const { activity, byActor: actorAnnouncer } = options
// Only notify if it is not from a fetcher job
const notify = options.fromFetch !== true
return retryTransactionWrapper(processVideoShare, actorAnnouncer, activity, notify)
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -21,7 +26,7 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async function processVideoShare (actorAnnouncer: ActorModel, activity: ActivityAnnounce) { async function processVideoShare (actorAnnouncer: ActorModel, activity: ActivityAnnounce, notify: boolean) {
const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id
let video: VideoModel let video: VideoModel
@ -63,5 +68,5 @@ async function processVideoShare (actorAnnouncer: ActorModel, activity: Activity
return undefined return undefined
}) })
if (videoCreated) Notifier.Instance.notifyOnNewVideoIfNeeded(video) if (videoCreated && notify) Notifier.Instance.notifyOnNewVideoIfNeeded(video)
} }

View file

@ -12,17 +12,22 @@ import { Notifier } from '../../notifier'
import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
import { createOrUpdateVideoPlaylist } from '../playlist' import { createOrUpdateVideoPlaylist } from '../playlist'
import { VideoModel } from '../../../models/video/video' import { VideoModel } from '../../../models/video/video'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
async function processCreateActivity (activity: ActivityCreate, byActor: ActorModel) { async function processCreateActivity (options: APProcessorOptions<ActivityCreate>) {
const { activity, byActor } = options
// Only notify if it is not from a fetcher job
const notify = options.fromFetch !== true
const activityObject = activity.object const activityObject = activity.object
const activityType = activityObject.type const activityType = activityObject.type
if (activityType === 'Video') { if (activityType === 'Video') {
return processCreateVideo(activity) return processCreateVideo(activity, notify)
} }
if (activityType === 'Note') { if (activityType === 'Note') {
return retryTransactionWrapper(processCreateVideoComment, activity, byActor) return retryTransactionWrapper(processCreateVideoComment, activity, byActor, notify)
} }
if (activityType === 'CacheFile') { if (activityType === 'CacheFile') {
@ -45,12 +50,12 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async function processCreateVideo (activity: ActivityCreate) { async function processCreateVideo (activity: ActivityCreate, notify: boolean) {
const videoToCreateData = activity.object as VideoTorrentObject const videoToCreateData = activity.object as VideoTorrentObject
const { video, created } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoToCreateData }) const { video, created } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoToCreateData })
if (created) Notifier.Instance.notifyOnNewVideoIfNeeded(video) if (created && notify) Notifier.Instance.notifyOnNewVideoIfNeeded(video)
return video return video
} }
@ -71,7 +76,7 @@ async function processCreateCacheFile (activity: ActivityCreate, byActor: ActorM
} }
} }
async function processCreateVideoComment (activity: ActivityCreate, byActor: ActorModel) { async function processCreateVideoComment (activity: ActivityCreate, byActor: ActorModel, notify: boolean) {
const commentObject = activity.object as VideoCommentObject const commentObject = activity.object as VideoCommentObject
const byAccount = byActor.Account const byAccount = byActor.Account
@ -99,7 +104,7 @@ async function processCreateVideoComment (activity: ActivityCreate, byActor: Act
await forwardVideoRelatedActivity(activity, undefined, exceptions, video) await forwardVideoRelatedActivity(activity, undefined, exceptions, video)
} }
if (created === true) Notifier.Instance.notifyOnNewComment(comment) if (created && notify) Notifier.Instance.notifyOnNewComment(comment)
} }
async function processCreatePlaylist (activity: ActivityCreate, byActor: ActorModel) { async function processCreatePlaylist (activity: ActivityCreate, byActor: ActorModel) {

View file

@ -9,8 +9,11 @@ import { VideoChannelModel } from '../../../models/video/video-channel'
import { VideoCommentModel } from '../../../models/video/video-comment' import { VideoCommentModel } from '../../../models/video/video-comment'
import { forwardVideoRelatedActivity } from '../send/utils' import { forwardVideoRelatedActivity } from '../send/utils'
import { VideoPlaylistModel } from '../../../models/video/video-playlist' import { VideoPlaylistModel } from '../../../models/video/video-playlist'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
async function processDeleteActivity (options: APProcessorOptions<ActivityDelete>) {
const { activity, byActor } = options
async function processDeleteActivity (activity: ActivityDelete, byActor: ActorModel) {
const objectUrl = typeof activity.object === 'string' ? activity.object : activity.object.id const objectUrl = typeof activity.object === 'string' ? activity.object : activity.object.id
if (activity.actor === objectUrl) { if (activity.actor === objectUrl) {

View file

@ -7,8 +7,10 @@ import { ActorModel } from '../../../models/activitypub/actor'
import { getOrCreateVideoAndAccountAndChannel } from '../videos' import { getOrCreateVideoAndAccountAndChannel } from '../videos'
import { forwardVideoRelatedActivity } from '../send/utils' import { forwardVideoRelatedActivity } from '../send/utils'
import { getVideoDislikeActivityPubUrl } from '../url' import { getVideoDislikeActivityPubUrl } from '../url'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
async function processDislikeActivity (activity: ActivityCreate | ActivityDislike, byActor: ActorModel) { async function processDislikeActivity (options: APProcessorOptions<ActivityCreate | ActivityDislike>) {
const { activity, byActor } = options
return retryTransactionWrapper(processDislike, activity, byActor) return retryTransactionWrapper(processDislike, activity, byActor)
} }

View file

@ -8,8 +8,10 @@ import { VideoAbuseModel } from '../../../models/video/video-abuse'
import { getOrCreateVideoAndAccountAndChannel } from '../videos' import { getOrCreateVideoAndAccountAndChannel } from '../videos'
import { Notifier } from '../../notifier' import { Notifier } from '../../notifier'
import { getAPId } from '../../../helpers/activitypub' import { getAPId } from '../../../helpers/activitypub'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
async function processFlagActivity (activity: ActivityCreate | ActivityFlag, byActor: ActorModel) { async function processFlagActivity (options: APProcessorOptions<ActivityCreate | ActivityFlag>) {
const { activity, byActor } = options
return retryTransactionWrapper(processCreateVideoAbuse, activity, byActor) return retryTransactionWrapper(processCreateVideoAbuse, activity, byActor)
} }

View file

@ -9,8 +9,10 @@ import { Notifier } from '../../notifier'
import { getAPId } from '../../../helpers/activitypub' import { getAPId } from '../../../helpers/activitypub'
import { getServerActor } from '../../../helpers/utils' import { getServerActor } from '../../../helpers/utils'
import { CONFIG } from '../../../initializers/config' import { CONFIG } from '../../../initializers/config'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
async function processFollowActivity (activity: ActivityFollow, byActor: ActorModel) { async function processFollowActivity (options: APProcessorOptions<ActivityFollow>) {
const { activity, byActor } = options
const activityObject = getAPId(activity.object) const activityObject = getAPId(activity.object)
return retryTransactionWrapper(processFollow, byActor, activityObject) return retryTransactionWrapper(processFollow, byActor, activityObject)

View file

@ -7,8 +7,10 @@ import { forwardVideoRelatedActivity } from '../send/utils'
import { getOrCreateVideoAndAccountAndChannel } from '../videos' import { getOrCreateVideoAndAccountAndChannel } from '../videos'
import { getVideoLikeActivityPubUrl } from '../url' import { getVideoLikeActivityPubUrl } from '../url'
import { getAPId } from '../../../helpers/activitypub' import { getAPId } from '../../../helpers/activitypub'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
async function processLikeActivity (activity: ActivityLike, byActor: ActorModel) { async function processLikeActivity (options: APProcessorOptions<ActivityLike>) {
const { activity, byActor } = options
return retryTransactionWrapper(processLikeVideo, byActor, activity) return retryTransactionWrapper(processLikeVideo, byActor, activity)
} }

View file

@ -2,8 +2,10 @@ import { ActivityReject } from '../../../../shared/models/activitypub/activity'
import { sequelizeTypescript } from '../../../initializers' import { sequelizeTypescript } from '../../../initializers'
import { ActorModel } from '../../../models/activitypub/actor' import { ActorModel } from '../../../models/activitypub/actor'
import { ActorFollowModel } from '../../../models/activitypub/actor-follow' import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
async function processRejectActivity (activity: ActivityReject, targetActor: ActorModel, inboxActor?: ActorModel) { async function processRejectActivity (options: APProcessorOptions<ActivityReject>) {
const { byActor: targetActor, inboxActor } = options
if (inboxActor === undefined) throw new Error('Need to reject on explicit inbox.') if (inboxActor === undefined) throw new Error('Need to reject on explicit inbox.')
return processReject(inboxActor, targetActor) return processReject(inboxActor, targetActor)

View file

@ -10,8 +10,10 @@ import { forwardVideoRelatedActivity } from '../send/utils'
import { getOrCreateVideoAndAccountAndChannel } from '../videos' import { getOrCreateVideoAndAccountAndChannel } from '../videos'
import { VideoShareModel } from '../../../models/video/video-share' import { VideoShareModel } from '../../../models/video/video-share'
import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
async function processUndoActivity (activity: ActivityUndo, byActor: ActorModel) { async function processUndoActivity (options: APProcessorOptions<ActivityUndo>) {
const { activity, byActor } = options
const activityToUndo = activity.object const activityToUndo = activity.object
if (activityToUndo.type === 'Like') { if (activityToUndo.type === 'Like') {

View file

@ -14,8 +14,11 @@ import { createOrUpdateCacheFile } from '../cache-file'
import { forwardVideoRelatedActivity } from '../send/utils' import { forwardVideoRelatedActivity } from '../send/utils'
import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
import { createOrUpdateVideoPlaylist } from '../playlist' import { createOrUpdateVideoPlaylist } from '../playlist'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) {
const { activity, byActor } = options
async function processUpdateActivity (activity: ActivityUpdate, byActor: ActorModel) {
const objectType = activity.object.type const objectType = activity.object.type
if (objectType === 'Video') { if (objectType === 'Video') {

View file

@ -3,8 +3,10 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos'
import { forwardVideoRelatedActivity } from '../send/utils' import { forwardVideoRelatedActivity } from '../send/utils'
import { Redis } from '../../redis' import { Redis } from '../../redis'
import { ActivityCreate, ActivityView, ViewObject } from '../../../../shared/models/activitypub' import { ActivityCreate, ActivityView, ViewObject } from '../../../../shared/models/activitypub'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
async function processViewActivity (activity: ActivityView | ActivityCreate, byActor: ActorModel) { async function processViewActivity (options: APProcessorOptions<ActivityCreate | ActivityView>) {
const { activity, byActor } = options
return processCreateView(activity, byActor) return processCreateView(activity, byActor)
} }

View file

@ -15,8 +15,9 @@ import { getOrCreateActorAndServerAndModel } from '../actor'
import { processDislikeActivity } from './process-dislike' import { processDislikeActivity } from './process-dislike'
import { processFlagActivity } from './process-flag' import { processFlagActivity } from './process-flag'
import { processViewActivity } from './process-view' import { processViewActivity } from './process-view'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
const processActivity: { [ P in ActivityType ]: (activity: Activity, byActor: ActorModel, inboxActor?: ActorModel) => Promise<any> } = { const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions<Activity>) => Promise<any> } = {
Create: processCreateActivity, Create: processCreateActivity,
Update: processUpdateActivity, Update: processUpdateActivity,
Delete: processDeleteActivity, Delete: processDeleteActivity,
@ -37,11 +38,15 @@ async function processActivities (
signatureActor?: ActorModel signatureActor?: ActorModel
inboxActor?: ActorModel inboxActor?: ActorModel
outboxUrl?: string outboxUrl?: string
} = {}) { fromFetch?: boolean
} = {}
) {
const { outboxUrl, signatureActor, inboxActor, fromFetch = false } = options
const actorsCache: { [ url: string ]: ActorModel } = {} const actorsCache: { [ url: string ]: ActorModel } = {}
for (const activity of activities) { for (const activity of activities) {
if (!options.signatureActor && [ 'Create', 'Announce', 'Like' ].includes(activity.type) === false) { if (!signatureActor && [ 'Create', 'Announce', 'Like' ].includes(activity.type) === false) {
logger.error('Cannot process activity %s (type: %s) without the actor signature.', activity.id, activity.type) logger.error('Cannot process activity %s (type: %s) without the actor signature.', activity.id, activity.type)
continue continue
} }
@ -49,17 +54,17 @@ async function processActivities (
const actorUrl = getAPId(activity.actor) const actorUrl = getAPId(activity.actor)
// When we fetch remote data, we don't have signature // When we fetch remote data, we don't have signature
if (options.signatureActor && actorUrl !== options.signatureActor.url) { if (signatureActor && actorUrl !== signatureActor.url) {
logger.warn('Signature mismatch between %s and %s, skipping.', actorUrl, options.signatureActor.url) logger.warn('Signature mismatch between %s and %s, skipping.', actorUrl, signatureActor.url)
continue continue
} }
if (options.outboxUrl && checkUrlsSameHost(options.outboxUrl, actorUrl) !== true) { if (outboxUrl && checkUrlsSameHost(outboxUrl, actorUrl) !== true) {
logger.warn('Host mismatch between outbox URL %s and actor URL %s, skipping.', options.outboxUrl, actorUrl) logger.warn('Host mismatch between outbox URL %s and actor URL %s, skipping.', outboxUrl, actorUrl)
continue continue
} }
const byActor = options.signatureActor || actorsCache[actorUrl] || await getOrCreateActorAndServerAndModel(actorUrl) const byActor = signatureActor || actorsCache[actorUrl] || await getOrCreateActorAndServerAndModel(actorUrl)
actorsCache[actorUrl] = byActor actorsCache[actorUrl] = byActor
const activityProcessor = processActivity[activity.type] const activityProcessor = processActivity[activity.type]
@ -69,7 +74,7 @@ async function processActivities (
} }
try { try {
await activityProcessor(activity, byActor, options.inboxActor) await activityProcessor({ activity, byActor, inboxActor: inboxActor, fromFetch })
} catch (err) { } catch (err) {
logger.warn('Cannot process activity %s.', activity.type, { err }) logger.warn('Cannot process activity %s.', activity.type, { err })
} }

View file

@ -33,7 +33,7 @@ async function processActivityPubHttpFetcher (job: Bull.Job) {
if (payload.accountId) account = await AccountModel.load(payload.accountId) if (payload.accountId) account = await AccountModel.load(payload.accountId)
const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise<any> } = { const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise<any> } = {
'activity': items => processActivities(items, { outboxUrl: payload.uri }), 'activity': items => processActivities(items, { outboxUrl: payload.uri, fromFetch: true }),
'video-likes': items => createRates(items, video, 'like'), 'video-likes': items => createRates(items, video, 'like'),
'video-dislikes': items => createRates(items, video, 'dislike'), 'video-dislikes': items => createRates(items, video, 'dislike'),
'video-shares': items => addVideoShares(items, video), 'video-shares': items => addVideoShares(items, video),

View file

@ -408,9 +408,7 @@ export class PluginManager implements ServerHook {
private async regeneratePluginGlobalCSS () { private async regeneratePluginGlobalCSS () {
await this.resetCSSGlobalFile() await this.resetCSSGlobalFile()
for (const key of Object.keys(this.getRegisteredPlugins())) { for (const plugin of this.getRegisteredPlugins()) {
const plugin = this.registeredPlugins[key]
await this.addCSSToGlobalFile(plugin.path, plugin.css) await this.addCSSToGlobalFile(plugin.path, plugin.css)
} }
} }

View file

@ -0,0 +1,9 @@
import { Activity } from '../../shared/models/activitypub'
import { ActorModel } from '../models/activitypub/actor'
export type APProcessorOptions<T extends Activity> = {
activity: T
byActor: ActorModel
inboxActor?: ActorModel
fromFetch?: boolean
}