Add auto follow back support for instances
This commit is contained in:
parent
f69ec5f340
commit
8424c4026a
|
@ -112,7 +112,7 @@ export class UserNotification implements UserNotificationServer {
|
|||
|
||||
case UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS:
|
||||
this.videoAutoBlacklistUrl = '/admin/moderation/video-auto-blacklist/list'
|
||||
this.videoUrl = this.buildVideoUrl(this.video)
|
||||
this.videoUrl = this.buildVideoUrl(this.videoBlacklist.video)
|
||||
break
|
||||
|
||||
case UserNotificationType.BLACKLIST_ON_MY_VIDEO:
|
||||
|
|
|
@ -273,5 +273,21 @@ followers:
|
|||
# Whether or not an administrator must manually validate a new follower
|
||||
manual_approval: false
|
||||
|
||||
followings:
|
||||
instance:
|
||||
# If you want to automatically follow back new instance followers
|
||||
# Only follows accepted followers (in case you enabled manual followers approbation)
|
||||
# If this option is enabled, use the mute feature instead of deleting followings
|
||||
# /!\ Don't enable this if you don't have a reactive moderation team /!\
|
||||
auto_follow_back:
|
||||
enabled: false
|
||||
|
||||
# If you want to automatically follow instances of the public index
|
||||
# If this option is enabled, use the mute feature instead of deleting followings
|
||||
# /!\ Don't enable this if you don't have a reactive moderation team /!\
|
||||
auto_follow_index:
|
||||
enabled: false
|
||||
index_url: 'https://instances.joinpeertube.org'
|
||||
|
||||
theme:
|
||||
default: 'default'
|
||||
|
|
|
@ -288,5 +288,20 @@ followers:
|
|||
# Whether or not an administrator must manually validate a new follower
|
||||
manual_approval: false
|
||||
|
||||
followings:
|
||||
instance:
|
||||
# If you want to automatically follow back new instance followers
|
||||
# If this option is enabled, use the mute feature instead of deleting followings
|
||||
# /!\ Don't enable this if you don't have a reactive moderation team /!\
|
||||
auto_follow_back:
|
||||
enabled: false
|
||||
|
||||
# If you want to automatically follow instances of the public index
|
||||
# If this option is enabled, use the mute feature instead of deleting followings
|
||||
# /!\ Don't enable this if you don't have a reactive moderation team /!\
|
||||
auto_follow_index:
|
||||
enabled: false
|
||||
index_url: 'https://instances.joinpeertube.org'
|
||||
|
||||
theme:
|
||||
default: 'default'
|
||||
|
|
|
@ -132,6 +132,7 @@
|
|||
"lru-cache": "^5.1.1",
|
||||
"magnet-uri": "^5.1.4",
|
||||
"memoizee": "^0.4.14",
|
||||
"module-alias": "^2.2.1",
|
||||
"morgan": "^1.5.3",
|
||||
"multer": "^1.1.0",
|
||||
"nodemailer": "^6.0.0",
|
||||
|
@ -224,5 +225,8 @@
|
|||
"scripty": {
|
||||
"silent": true
|
||||
},
|
||||
"sasslintConfig": "client/.sass-lint.yml"
|
||||
"sasslintConfig": "client/.sass-lint.yml",
|
||||
"_moduleAliases": {
|
||||
"@server": "dist/server"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
require('module-alias/register')
|
||||
|
||||
// FIXME: https://github.com/nodejs/node/pull/16853
|
||||
import { PluginManager } from './server/lib/plugins/plugin-manager'
|
||||
|
||||
|
|
|
@ -300,6 +300,18 @@ function customConfig (): CustomConfig {
|
|||
enabled: CONFIG.FOLLOWERS.INSTANCE.ENABLED,
|
||||
manualApproval: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL
|
||||
}
|
||||
},
|
||||
followings: {
|
||||
instance: {
|
||||
autoFollowBack: {
|
||||
enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_BACK.ENABLED
|
||||
},
|
||||
|
||||
autoFollowIndex: {
|
||||
enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.ENABLED,
|
||||
indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
|||
import { JobQueue } from '../../../lib/job-queue'
|
||||
import { removeRedundancyOf } from '../../../lib/redundancy'
|
||||
import { sequelizeTypescript } from '../../../initializers/database'
|
||||
import { autoFollowBackIfNeeded } from '../../../lib/activitypub/follow'
|
||||
|
||||
const serverFollowsRouter = express.Router()
|
||||
serverFollowsRouter.get('/following',
|
||||
|
@ -172,5 +173,7 @@ async function acceptFollower (req: express.Request, res: express.Response) {
|
|||
follow.state = 'accepted'
|
||||
await follow.save()
|
||||
|
||||
await autoFollowBackIfNeeded(follow)
|
||||
|
||||
return res.status(204).end()
|
||||
}
|
||||
|
|
|
@ -76,7 +76,8 @@ async function updateNotificationSettings (req: express.Request, res: express.Re
|
|||
newFollow: body.newFollow,
|
||||
newUserRegistration: body.newUserRegistration,
|
||||
commentMention: body.commentMention,
|
||||
newInstanceFollower: body.newInstanceFollower
|
||||
newInstanceFollower: body.newInstanceFollower,
|
||||
autoInstanceFollowing: body.autoInstanceFollowing
|
||||
}
|
||||
|
||||
await UserNotificationSettingModel.update(values, query)
|
||||
|
|
|
@ -232,6 +232,23 @@ const CONFIG = {
|
|||
get MANUAL_APPROVAL () { return config.get<boolean>('followers.instance.manual_approval') }
|
||||
}
|
||||
},
|
||||
FOLLOWINGS: {
|
||||
INSTANCE: {
|
||||
AUTO_FOLLOW_BACK: {
|
||||
get ENABLED () {
|
||||
return config.get<boolean>('followings.instance.auto_follow_back.enabled')
|
||||
}
|
||||
},
|
||||
AUTO_FOLLOW_INDEX: {
|
||||
get ENABLED () {
|
||||
return config.get<boolean>('followings.instance.auto_follow_index.enabled')
|
||||
},
|
||||
get INDEX_URL () {
|
||||
return config.get<string>('followings.instance.auto_follow_index.index_url')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
THEME: {
|
||||
get DEFAULT () { return config.get<string>('theme.default') }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import { MActorFollowActors } from '../../typings/models'
|
||||
import { CONFIG } from '../../initializers/config'
|
||||
import { SERVER_ACTOR_NAME } from '../../initializers/constants'
|
||||
import { JobQueue } from '../job-queue'
|
||||
import { logger } from '../../helpers/logger'
|
||||
import { getServerActor } from '../../helpers/utils'
|
||||
import { ServerModel } from '@server/models/server/server'
|
||||
|
||||
async function autoFollowBackIfNeeded (actorFollow: MActorFollowActors) {
|
||||
if (!CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_BACK.ENABLED) return
|
||||
|
||||
const follower = actorFollow.ActorFollower
|
||||
|
||||
if (follower.type === 'Application' && follower.preferredUsername === SERVER_ACTOR_NAME) {
|
||||
logger.info('Auto follow back %s.', follower.url)
|
||||
|
||||
const me = await getServerActor()
|
||||
|
||||
const server = await ServerModel.load(follower.serverId)
|
||||
const host = server.host
|
||||
|
||||
const payload = {
|
||||
host,
|
||||
name: SERVER_ACTOR_NAME,
|
||||
followerActorId: me.id,
|
||||
isAutoFollow: true
|
||||
}
|
||||
|
||||
JobQueue.Instance.createJob({ type: 'activitypub-follow', payload })
|
||||
.catch(err => logger.error('Cannot create auto follow back job for %s.', host, err))
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
autoFollowBackIfNeeded
|
||||
}
|
|
@ -24,7 +24,7 @@ async function processAccept (actor: MActorDefault, targetActor: MActorSignature
|
|||
if (!follow) throw new Error('Cannot find associated follow.')
|
||||
|
||||
if (follow.state !== 'accepted') {
|
||||
follow.set('state', 'accepted')
|
||||
follow.state = 'accepted'
|
||||
await follow.save()
|
||||
|
||||
await addFetchOutboxJob(targetActor)
|
||||
|
|
|
@ -10,7 +10,8 @@ import { getAPId } from '../../../helpers/activitypub'
|
|||
import { getServerActor } from '../../../helpers/utils'
|
||||
import { CONFIG } from '../../../initializers/config'
|
||||
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
|
||||
import { MAccount, MActorFollowActors, MActorFollowFull, MActorSignature } from '../../../typings/models'
|
||||
import { MActorFollowActors, MActorSignature } from '../../../typings/models'
|
||||
import { autoFollowBackIfNeeded } from '../follow'
|
||||
|
||||
async function processFollowActivity (options: APProcessorOptions<ActivityFollow>) {
|
||||
const { activity, byActor } = options
|
||||
|
@ -28,7 +29,7 @@ export {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function processFollow (byActor: MActorSignature, targetActorURL: string) {
|
||||
const { actorFollow, created, isFollowingInstance } = await sequelizeTypescript.transaction(async t => {
|
||||
const { actorFollow, created, isFollowingInstance, targetActor } = await sequelizeTypescript.transaction(async t => {
|
||||
const targetActor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(targetActorURL, t)
|
||||
|
||||
if (!targetActor) throw new Error('Unknown actor')
|
||||
|
@ -67,21 +68,24 @@ async function processFollow (byActor: MActorSignature, targetActorURL: string)
|
|||
actorFollow.ActorFollowing = targetActor
|
||||
|
||||
// Target sends to actor he accepted the follow request
|
||||
if (actorFollow.state === 'accepted') await sendAccept(actorFollow)
|
||||
if (actorFollow.state === 'accepted') {
|
||||
await sendAccept(actorFollow)
|
||||
await autoFollowBackIfNeeded(actorFollow)
|
||||
}
|
||||
|
||||
return { actorFollow, created, isFollowingInstance }
|
||||
return { actorFollow, created, isFollowingInstance, targetActor }
|
||||
})
|
||||
|
||||
// Rejected
|
||||
if (!actorFollow) return
|
||||
|
||||
if (created) {
|
||||
if (isFollowingInstance) {
|
||||
Notifier.Instance.notifyOfNewInstanceFollow(actorFollow)
|
||||
} else {
|
||||
const actorFollowFull = actorFollow as MActorFollowFull
|
||||
actorFollowFull.ActorFollower.Account = await actorFollow.ActorFollower.$get('Account') as MAccount
|
||||
const follower = await ActorModel.loadFull(byActor.id)
|
||||
const actorFollowFull = Object.assign(actorFollow, { ActorFollowing: targetActor, ActorFollower: follower })
|
||||
|
||||
if (isFollowingInstance) {
|
||||
Notifier.Instance.notifyOfNewInstanceFollow(actorFollowFull)
|
||||
} else {
|
||||
Notifier.Instance.notifyOfNewUserFollow(actorFollowFull)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { ActivityFollow } from '../../../../shared/models/activitypub'
|
||||
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
||||
import { getActorFollowActivityPubUrl } from '../url'
|
||||
import { unicastTo } from './utils'
|
||||
import { logger } from '../../../helpers/logger'
|
||||
|
|
|
@ -6,8 +6,15 @@ import { JobQueue } from './job-queue'
|
|||
import { EmailPayload } from './job-queue/handlers/email'
|
||||
import { readFileSync } from 'fs-extra'
|
||||
import { WEBSERVER } from '../initializers/constants'
|
||||
import { MCommentOwnerVideo, MVideo, MVideoAbuseVideo, MVideoAccountLight, MVideoBlacklistVideo } from '../typings/models/video'
|
||||
import { MActorFollowActors, MActorFollowFollowingFullFollowerAccount, MUser } from '../typings/models'
|
||||
import {
|
||||
MCommentOwnerVideo,
|
||||
MVideo,
|
||||
MVideoAbuseVideo,
|
||||
MVideoAccountLight,
|
||||
MVideoBlacklistLightVideo,
|
||||
MVideoBlacklistVideo
|
||||
} from '../typings/models/video'
|
||||
import { MActorFollowActors, MActorFollowFull, MUser } from '../typings/models'
|
||||
import { MVideoImport, MVideoImportVideo } from '@server/typings/models/video/video-import'
|
||||
|
||||
type SendEmailOptions = {
|
||||
|
@ -107,7 +114,7 @@ class Emailer {
|
|||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||
}
|
||||
|
||||
addNewFollowNotification (to: string[], actorFollow: MActorFollowFollowingFullFollowerAccount, followType: 'account' | 'channel') {
|
||||
addNewFollowNotification (to: string[], actorFollow: MActorFollowFull, followType: 'account' | 'channel') {
|
||||
const followerName = actorFollow.ActorFollower.Account.getDisplayName()
|
||||
const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName()
|
||||
|
||||
|
@ -144,6 +151,22 @@ class Emailer {
|
|||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||
}
|
||||
|
||||
addAutoInstanceFollowingNotification (to: string[], actorFollow: MActorFollowActors) {
|
||||
const text = `Hi dear admin,\n\n` +
|
||||
`Your instance automatically followed a new instance: ${actorFollow.ActorFollowing.url}` +
|
||||
`\n\n` +
|
||||
`Cheers,\n` +
|
||||
`${CONFIG.EMAIL.BODY.SIGNATURE}`
|
||||
|
||||
const emailPayload: EmailPayload = {
|
||||
to,
|
||||
subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Auto instance following',
|
||||
text
|
||||
}
|
||||
|
||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||
}
|
||||
|
||||
myVideoPublishedNotification (to: string[], video: MVideo) {
|
||||
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
|
||||
|
||||
|
@ -265,9 +288,9 @@ class Emailer {
|
|||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||
}
|
||||
|
||||
addVideoAutoBlacklistModeratorsNotification (to: string[], video: MVideo) {
|
||||
addVideoAutoBlacklistModeratorsNotification (to: string[], videoBlacklist: MVideoBlacklistLightVideo) {
|
||||
const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list'
|
||||
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
|
||||
const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
|
||||
|
||||
const text = `Hi,\n\n` +
|
||||
`A recently added video was auto-blacklisted and requires moderator review before publishing.` +
|
||||
|
|
|
@ -10,12 +10,13 @@ import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
|||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { Notifier } from '../../notifier'
|
||||
import { sequelizeTypescript } from '../../../initializers/database'
|
||||
import { MAccount, MActor, MActorFollowActors, MActorFollowFull, MActorFull } from '../../../typings/models'
|
||||
import { MActor, MActorFollowActors, MActorFull } from '../../../typings/models'
|
||||
|
||||
export type ActivitypubFollowPayload = {
|
||||
followerActorId: number
|
||||
name: string
|
||||
host: string
|
||||
isAutoFollow?: boolean
|
||||
}
|
||||
|
||||
async function processActivityPubFollow (job: Bull.Job) {
|
||||
|
@ -35,7 +36,7 @@ async function processActivityPubFollow (job: Bull.Job) {
|
|||
|
||||
const fromActor = await ActorModel.load(payload.followerActorId)
|
||||
|
||||
return retryTransactionWrapper(follow, fromActor, targetActor)
|
||||
return retryTransactionWrapper(follow, fromActor, targetActor, payload.isAutoFollow)
|
||||
}
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
@ -45,7 +46,7 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function follow (fromActor: MActor, targetActor: MActorFull) {
|
||||
async function follow (fromActor: MActor, targetActor: MActorFull, isAutoFollow = false) {
|
||||
if (fromActor.id === targetActor.id) {
|
||||
throw new Error('Follower is the same than target actor.')
|
||||
}
|
||||
|
@ -75,14 +76,15 @@ async function follow (fromActor: MActor, targetActor: MActorFull) {
|
|||
return actorFollow
|
||||
})
|
||||
|
||||
if (actorFollow.state === 'accepted') {
|
||||
const followerFull = Object.assign(fromActor, { Account: await actorFollow.ActorFollower.$get('Account') as MAccount })
|
||||
const followerFull = await ActorModel.loadFull(fromActor.id)
|
||||
|
||||
const actorFollowFull = Object.assign(actorFollow, {
|
||||
ActorFollowing: targetActor,
|
||||
ActorFollower: followerFull
|
||||
})
|
||||
|
||||
Notifier.Instance.notifyOfNewUserFollow(actorFollowFull)
|
||||
}
|
||||
if (actorFollow.state === 'accepted') Notifier.Instance.notifyOfNewUserFollow(actorFollowFull)
|
||||
if (isAutoFollow === true) Notifier.Instance.notifyOfAutoInstanceFollowing(actorFollowFull)
|
||||
|
||||
return actorFollow
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import { createVideoMiniatureFromUrl, generateVideoMiniature } from '../../thumb
|
|||
import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
|
||||
import { MThumbnail } from '../../../typings/models/video/thumbnail'
|
||||
import { MVideoImportDefault, MVideoImportDefaultFiles, MVideoImportVideo } from '@server/typings/models/video/video-import'
|
||||
import { MVideoBlacklistVideo, MVideoBlacklist } from '@server/typings/models'
|
||||
|
||||
type VideoImportYoutubeDLPayload = {
|
||||
type: 'youtube-dl'
|
||||
|
@ -204,7 +205,9 @@ async function processFile (downloader: () => Promise<string>, videoImport: MVid
|
|||
Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true)
|
||||
|
||||
if (video.isBlacklisted()) {
|
||||
Notifier.Instance.notifyOnVideoAutoBlacklist(video)
|
||||
const videoBlacklist = Object.assign(video.VideoBlacklist, { Video: video })
|
||||
|
||||
Notifier.Instance.notifyOnVideoAutoBlacklist(videoBlacklist)
|
||||
} else {
|
||||
Notifier.Instance.notifyOnNewVideoIfNeeded(video)
|
||||
}
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users'
|
||||
import { logger } from '../helpers/logger'
|
||||
import { VideoModel } from '../models/video/video'
|
||||
import { Emailer } from './emailer'
|
||||
import { UserNotificationModel } from '../models/account/user-notification'
|
||||
import { VideoCommentModel } from '../models/video/video-comment'
|
||||
import { UserModel } from '../models/account/user'
|
||||
import { PeerTubeSocket } from './peertube-socket'
|
||||
import { CONFIG } from '../initializers/config'
|
||||
import { VideoPrivacy, VideoState } from '../../shared/models/videos'
|
||||
import { VideoBlacklistModel } from '../models/video/video-blacklist'
|
||||
import * as Bluebird from 'bluebird'
|
||||
import { VideoImportModel } from '../models/video/video-import'
|
||||
import { AccountBlocklistModel } from '../models/account/account-blocklist'
|
||||
import {
|
||||
MCommentOwnerVideo,
|
||||
MVideo,
|
||||
MVideoAbuseVideo,
|
||||
MVideoAccountLight,
|
||||
MVideoBlacklistLightVideo,
|
||||
MVideoBlacklistVideo,
|
||||
MVideoFullLight
|
||||
} from '../typings/models/video'
|
||||
import { MUser, MUserAccount, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/typings/models/user'
|
||||
import { MActorFollowActors, MActorFollowFull, MActorFollowFollowingFullFollowerAccount } from '../typings/models'
|
||||
import { ActorFollowModel } from '../models/activitypub/actor-follow'
|
||||
import {
|
||||
MUser,
|
||||
MUserDefault,
|
||||
MUserNotifSettingAccount,
|
||||
MUserWithNotificationSetting,
|
||||
UserNotificationModelForApi
|
||||
} from '@server/typings/models/user'
|
||||
import { MActorFollowFull } from '../typings/models'
|
||||
import { MVideoImportVideo } from '@server/typings/models/video/video-import'
|
||||
import { AccountModel } from '@server/models/account/account'
|
||||
|
||||
class Notifier {
|
||||
|
||||
|
@ -77,9 +77,9 @@ class Notifier {
|
|||
.catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err }))
|
||||
}
|
||||
|
||||
notifyOnVideoAutoBlacklist (video: MVideo): void {
|
||||
this.notifyModeratorsOfVideoAutoBlacklist(video)
|
||||
.catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', video.url, { err }))
|
||||
notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void {
|
||||
this.notifyModeratorsOfVideoAutoBlacklist(videoBlacklist)
|
||||
.catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', videoBlacklist.Video.url, { err }))
|
||||
}
|
||||
|
||||
notifyOnVideoBlacklist (videoBlacklist: MVideoBlacklistVideo): void {
|
||||
|
@ -87,7 +87,7 @@ class Notifier {
|
|||
.catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err }))
|
||||
}
|
||||
|
||||
notifyOnVideoUnblacklist (video: MVideo): void {
|
||||
notifyOnVideoUnblacklist (video: MVideoFullLight): void {
|
||||
this.notifyVideoOwnerOfUnblacklist(video)
|
||||
.catch(err => logger.error('Cannot notify video owner of unblacklist of %s.', video.url, { err }))
|
||||
}
|
||||
|
@ -97,12 +97,12 @@ class Notifier {
|
|||
.catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err }))
|
||||
}
|
||||
|
||||
notifyOnNewUserRegistration (user: MUserAccount): void {
|
||||
notifyOnNewUserRegistration (user: MUserDefault): void {
|
||||
this.notifyModeratorsOfNewUserRegistration(user)
|
||||
.catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err }))
|
||||
}
|
||||
|
||||
notifyOfNewUserFollow (actorFollow: MActorFollowFollowingFullFollowerAccount): void {
|
||||
notifyOfNewUserFollow (actorFollow: MActorFollowFull): void {
|
||||
this.notifyUserOfNewActorFollow(actorFollow)
|
||||
.catch(err => {
|
||||
logger.error(
|
||||
|
@ -114,30 +114,37 @@ class Notifier {
|
|||
})
|
||||
}
|
||||
|
||||
notifyOfNewInstanceFollow (actorFollow: MActorFollowActors): void {
|
||||
notifyOfNewInstanceFollow (actorFollow: MActorFollowFull): void {
|
||||
this.notifyAdminsOfNewInstanceFollow(actorFollow)
|
||||
.catch(err => {
|
||||
logger.error('Cannot notify administrators of new follower %s.', actorFollow.ActorFollower.url, { err })
|
||||
})
|
||||
}
|
||||
|
||||
notifyOfAutoInstanceFollowing (actorFollow: MActorFollowFull): void {
|
||||
this.notifyAdminsOfAutoInstanceFollowing(actorFollow)
|
||||
.catch(err => {
|
||||
logger.error('Cannot notify administrators of auto instance following %s.', actorFollow.ActorFollowing.url, { err })
|
||||
})
|
||||
}
|
||||
|
||||
private async notifySubscribersOfNewVideo (video: MVideoAccountLight) {
|
||||
// List all followers that are users
|
||||
const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId)
|
||||
|
||||
logger.info('Notifying %d users of new video %s.', users.length, video.url)
|
||||
|
||||
function settingGetter (user: UserModel) {
|
||||
function settingGetter (user: MUserWithNotificationSetting) {
|
||||
return user.NotificationSetting.newVideoFromSubscription
|
||||
}
|
||||
|
||||
async function notificationCreator (user: UserModel) {
|
||||
const notification = await UserNotificationModel.create({
|
||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||
type: UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION,
|
||||
userId: user.id,
|
||||
videoId: video.id
|
||||
})
|
||||
notification.Video = video as VideoModel
|
||||
notification.Video = video
|
||||
|
||||
return notification
|
||||
}
|
||||
|
@ -162,17 +169,17 @@ class Notifier {
|
|||
|
||||
logger.info('Notifying user %s of new comment %s.', user.username, comment.url)
|
||||
|
||||
function settingGetter (user: UserModel) {
|
||||
function settingGetter (user: MUserWithNotificationSetting) {
|
||||
return user.NotificationSetting.newCommentOnMyVideo
|
||||
}
|
||||
|
||||
async function notificationCreator (user: UserModel) {
|
||||
const notification = await UserNotificationModel.create({
|
||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||
type: UserNotificationType.NEW_COMMENT_ON_MY_VIDEO,
|
||||
userId: user.id,
|
||||
commentId: comment.id
|
||||
})
|
||||
notification.Comment = comment as VideoCommentModel
|
||||
notification.Comment = comment
|
||||
|
||||
return notification
|
||||
}
|
||||
|
@ -207,19 +214,19 @@ class Notifier {
|
|||
|
||||
logger.info('Notifying %d users of new comment %s.', users.length, comment.url)
|
||||
|
||||
function settingGetter (user: UserModel) {
|
||||
function settingGetter (user: MUserNotifSettingAccount) {
|
||||
if (accountMutedHash[user.Account.id] === true) return UserNotificationSettingValue.NONE
|
||||
|
||||
return user.NotificationSetting.commentMention
|
||||
}
|
||||
|
||||
async function notificationCreator (user: UserModel) {
|
||||
const notification = await UserNotificationModel.create({
|
||||
async function notificationCreator (user: MUserNotifSettingAccount) {
|
||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||
type: UserNotificationType.COMMENT_MENTION,
|
||||
userId: user.id,
|
||||
commentId: comment.id
|
||||
})
|
||||
notification.Comment = comment as VideoCommentModel
|
||||
notification.Comment = comment
|
||||
|
||||
return notification
|
||||
}
|
||||
|
@ -231,7 +238,7 @@ class Notifier {
|
|||
return this.notify({ users, settingGetter, notificationCreator, emailSender })
|
||||
}
|
||||
|
||||
private async notifyUserOfNewActorFollow (actorFollow: MActorFollowFollowingFullFollowerAccount) {
|
||||
private async notifyUserOfNewActorFollow (actorFollow: MActorFollowFull) {
|
||||
if (actorFollow.ActorFollowing.isOwned() === false) return
|
||||
|
||||
// Account follows one of our account?
|
||||
|
@ -253,17 +260,17 @@ class Notifier {
|
|||
|
||||
logger.info('Notifying user %s of new follower: %s.', user.username, followerAccount.getDisplayName())
|
||||
|
||||
function settingGetter (user: UserModel) {
|
||||
function settingGetter (user: MUserWithNotificationSetting) {
|
||||
return user.NotificationSetting.newFollow
|
||||
}
|
||||
|
||||
async function notificationCreator (user: UserModel) {
|
||||
const notification = await UserNotificationModel.create({
|
||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||
type: UserNotificationType.NEW_FOLLOW,
|
||||
userId: user.id,
|
||||
actorFollowId: actorFollow.id
|
||||
})
|
||||
notification.ActorFollow = actorFollow as ActorFollowModel
|
||||
notification.ActorFollow = actorFollow
|
||||
|
||||
return notification
|
||||
}
|
||||
|
@ -275,22 +282,22 @@ class Notifier {
|
|||
return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
|
||||
}
|
||||
|
||||
private async notifyAdminsOfNewInstanceFollow (actorFollow: MActorFollowActors) {
|
||||
private async notifyAdminsOfNewInstanceFollow (actorFollow: MActorFollowFull) {
|
||||
const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW)
|
||||
|
||||
logger.info('Notifying %d administrators of new instance follower: %s.', admins.length, actorFollow.ActorFollower.url)
|
||||
|
||||
function settingGetter (user: UserModel) {
|
||||
function settingGetter (user: MUserWithNotificationSetting) {
|
||||
return user.NotificationSetting.newInstanceFollower
|
||||
}
|
||||
|
||||
async function notificationCreator (user: UserModel) {
|
||||
const notification = await UserNotificationModel.create({
|
||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||
type: UserNotificationType.NEW_INSTANCE_FOLLOWER,
|
||||
userId: user.id,
|
||||
actorFollowId: actorFollow.id
|
||||
})
|
||||
notification.ActorFollow = actorFollow as ActorFollowModel
|
||||
notification.ActorFollow = actorFollow
|
||||
|
||||
return notification
|
||||
}
|
||||
|
@ -302,18 +309,45 @@ class Notifier {
|
|||
return this.notify({ users: admins, settingGetter, notificationCreator, emailSender })
|
||||
}
|
||||
|
||||
private async notifyAdminsOfAutoInstanceFollowing (actorFollow: MActorFollowFull) {
|
||||
const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW)
|
||||
|
||||
logger.info('Notifying %d administrators of auto instance following: %s.', admins.length, actorFollow.ActorFollowing.url)
|
||||
|
||||
function settingGetter (user: MUserWithNotificationSetting) {
|
||||
return user.NotificationSetting.autoInstanceFollowing
|
||||
}
|
||||
|
||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||
type: UserNotificationType.AUTO_INSTANCE_FOLLOWING,
|
||||
userId: user.id,
|
||||
actorFollowId: actorFollow.id
|
||||
})
|
||||
notification.ActorFollow = actorFollow
|
||||
|
||||
return notification
|
||||
}
|
||||
|
||||
function emailSender (emails: string[]) {
|
||||
return Emailer.Instance.addAutoInstanceFollowingNotification(emails, actorFollow)
|
||||
}
|
||||
|
||||
return this.notify({ users: admins, settingGetter, notificationCreator, emailSender })
|
||||
}
|
||||
|
||||
private async notifyModeratorsOfNewVideoAbuse (videoAbuse: MVideoAbuseVideo) {
|
||||
const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES)
|
||||
if (moderators.length === 0) return
|
||||
|
||||
logger.info('Notifying %s user/moderators of new video abuse %s.', moderators.length, videoAbuse.Video.url)
|
||||
|
||||
function settingGetter (user: UserModel) {
|
||||
function settingGetter (user: MUserWithNotificationSetting) {
|
||||
return user.NotificationSetting.videoAbuseAsModerator
|
||||
}
|
||||
|
||||
async function notificationCreator (user: UserModel) {
|
||||
const notification: UserNotificationModelForApi = await UserNotificationModel.create({
|
||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
||||
const notification: UserNotificationModelForApi = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||
type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS,
|
||||
userId: user.id,
|
||||
videoAbuseId: videoAbuse.id
|
||||
|
@ -330,29 +364,29 @@ class Notifier {
|
|||
return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
|
||||
}
|
||||
|
||||
private async notifyModeratorsOfVideoAutoBlacklist (video: MVideo) {
|
||||
private async notifyModeratorsOfVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo) {
|
||||
const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST)
|
||||
if (moderators.length === 0) return
|
||||
|
||||
logger.info('Notifying %s moderators of video auto-blacklist %s.', moderators.length, video.url)
|
||||
logger.info('Notifying %s moderators of video auto-blacklist %s.', moderators.length, videoBlacklist.Video.url)
|
||||
|
||||
function settingGetter (user: UserModel) {
|
||||
function settingGetter (user: MUserWithNotificationSetting) {
|
||||
return user.NotificationSetting.videoAutoBlacklistAsModerator
|
||||
}
|
||||
async function notificationCreator (user: UserModel) {
|
||||
|
||||
const notification = await UserNotificationModel.create({
|
||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||
type: UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS,
|
||||
userId: user.id,
|
||||
videoId: video.id
|
||||
videoBlacklistId: videoBlacklist.id
|
||||
})
|
||||
notification.Video = video as VideoModel
|
||||
notification.VideoBlacklist = videoBlacklist
|
||||
|
||||
return notification
|
||||
}
|
||||
|
||||
function emailSender (emails: string[]) {
|
||||
return Emailer.Instance.addVideoAutoBlacklistModeratorsNotification(emails, video)
|
||||
return Emailer.Instance.addVideoAutoBlacklistModeratorsNotification(emails, videoBlacklist)
|
||||
}
|
||||
|
||||
return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
|
||||
|
@ -364,17 +398,17 @@ class Notifier {
|
|||
|
||||
logger.info('Notifying user %s that its video %s has been blacklisted.', user.username, videoBlacklist.Video.url)
|
||||
|
||||
function settingGetter (user: UserModel) {
|
||||
function settingGetter (user: MUserWithNotificationSetting) {
|
||||
return user.NotificationSetting.blacklistOnMyVideo
|
||||
}
|
||||
|
||||
async function notificationCreator (user: UserModel) {
|
||||
const notification = await UserNotificationModel.create({
|
||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||
type: UserNotificationType.BLACKLIST_ON_MY_VIDEO,
|
||||
userId: user.id,
|
||||
videoBlacklistId: videoBlacklist.id
|
||||
})
|
||||
notification.VideoBlacklist = videoBlacklist as VideoBlacklistModel
|
||||
notification.VideoBlacklist = videoBlacklist
|
||||
|
||||
return notification
|
||||
}
|
||||
|
@ -386,23 +420,23 @@ class Notifier {
|
|||
return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
|
||||
}
|
||||
|
||||
private async notifyVideoOwnerOfUnblacklist (video: MVideo) {
|
||||
private async notifyVideoOwnerOfUnblacklist (video: MVideoFullLight) {
|
||||
const user = await UserModel.loadByVideoId(video.id)
|
||||
if (!user) return
|
||||
|
||||
logger.info('Notifying user %s that its video %s has been unblacklisted.', user.username, video.url)
|
||||
|
||||
function settingGetter (user: UserModel) {
|
||||
function settingGetter (user: MUserWithNotificationSetting) {
|
||||
return user.NotificationSetting.blacklistOnMyVideo
|
||||
}
|
||||
|
||||
async function notificationCreator (user: UserModel) {
|
||||
const notification = await UserNotificationModel.create({
|
||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||
type: UserNotificationType.UNBLACKLIST_ON_MY_VIDEO,
|
||||
userId: user.id,
|
||||
videoId: video.id
|
||||
})
|
||||
notification.Video = video as VideoModel
|
||||
notification.Video = video
|
||||
|
||||
return notification
|
||||
}
|
||||
|
@ -420,17 +454,17 @@ class Notifier {
|
|||
|
||||
logger.info('Notifying user %s of the publication of its video %s.', user.username, video.url)
|
||||
|
||||
function settingGetter (user: UserModel) {
|
||||
function settingGetter (user: MUserWithNotificationSetting) {
|
||||
return user.NotificationSetting.myVideoPublished
|
||||
}
|
||||
|
||||
async function notificationCreator (user: UserModel) {
|
||||
const notification = await UserNotificationModel.create({
|
||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||
type: UserNotificationType.MY_VIDEO_PUBLISHED,
|
||||
userId: user.id,
|
||||
videoId: video.id
|
||||
})
|
||||
notification.Video = video as VideoModel
|
||||
notification.Video = video
|
||||
|
||||
return notification
|
||||
}
|
||||
|
@ -448,17 +482,17 @@ class Notifier {
|
|||
|
||||
logger.info('Notifying user %s its video import %s is finished.', user.username, videoImport.getTargetIdentifier())
|
||||
|
||||
function settingGetter (user: UserModel) {
|
||||
function settingGetter (user: MUserWithNotificationSetting) {
|
||||
return user.NotificationSetting.myVideoImportFinished
|
||||
}
|
||||
|
||||
async function notificationCreator (user: UserModel) {
|
||||
const notification = await UserNotificationModel.create({
|
||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||
type: success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR,
|
||||
userId: user.id,
|
||||
videoImportId: videoImport.id
|
||||
})
|
||||
notification.VideoImport = videoImport as VideoImportModel
|
||||
notification.VideoImport = videoImport
|
||||
|
||||
return notification
|
||||
}
|
||||
|
@ -472,7 +506,7 @@ class Notifier {
|
|||
return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
|
||||
}
|
||||
|
||||
private async notifyModeratorsOfNewUserRegistration (registeredUser: MUserAccount) {
|
||||
private async notifyModeratorsOfNewUserRegistration (registeredUser: MUserDefault) {
|
||||
const moderators = await UserModel.listWithRight(UserRight.MANAGE_USERS)
|
||||
if (moderators.length === 0) return
|
||||
|
||||
|
@ -481,17 +515,17 @@ class Notifier {
|
|||
moderators.length, registeredUser.username
|
||||
)
|
||||
|
||||
function settingGetter (user: UserModel) {
|
||||
function settingGetter (user: MUserWithNotificationSetting) {
|
||||
return user.NotificationSetting.newUserRegistration
|
||||
}
|
||||
|
||||
async function notificationCreator (user: UserModel) {
|
||||
const notification = await UserNotificationModel.create({
|
||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||
type: UserNotificationType.NEW_USER_REGISTRATION,
|
||||
userId: user.id,
|
||||
accountId: registeredUser.Account.id
|
||||
})
|
||||
notification.Account = registeredUser.Account as AccountModel
|
||||
notification.Account = registeredUser.Account
|
||||
|
||||
return notification
|
||||
}
|
||||
|
@ -503,11 +537,11 @@ class Notifier {
|
|||
return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
|
||||
}
|
||||
|
||||
private async notify (options: {
|
||||
users: MUserWithNotificationSetting[],
|
||||
notificationCreator: (user: MUserWithNotificationSetting) => Promise<UserNotificationModelForApi>,
|
||||
private async notify <T extends MUserWithNotificationSetting> (options: {
|
||||
users: T[],
|
||||
notificationCreator: (user: T) => Promise<UserNotificationModelForApi>,
|
||||
emailSender: (emails: string[]) => Promise<any> | Bluebird<any>,
|
||||
settingGetter: (user: MUserWithNotificationSetting) => UserNotificationSettingValue
|
||||
settingGetter: (user: T) => UserNotificationSettingValue
|
||||
}) {
|
||||
const emails: string[] = []
|
||||
|
||||
|
|
|
@ -138,7 +138,8 @@ function createDefaultUserNotificationSettings (user: MUserId, t: Transaction |
|
|||
newUserRegistration: UserNotificationSettingValue.WEB,
|
||||
commentMention: UserNotificationSettingValue.WEB,
|
||||
newFollow: UserNotificationSettingValue.WEB,
|
||||
newInstanceFollower: UserNotificationSettingValue.WEB
|
||||
newInstanceFollower: UserNotificationSettingValue.WEB,
|
||||
autoInstanceFollowing: UserNotificationSettingValue.WEB
|
||||
}
|
||||
|
||||
return UserNotificationSettingModel.create(values, { transaction: t })
|
||||
|
|
|
@ -6,7 +6,7 @@ import { logger } from '../helpers/logger'
|
|||
import { UserAdminFlag } from '../../shared/models/users/user-flag.model'
|
||||
import { Hooks } from './plugins/hooks'
|
||||
import { Notifier } from './notifier'
|
||||
import { MUser, MVideoBlacklist, MVideoWithBlacklistLight } from '@server/typings/models'
|
||||
import { MUser, MVideoBlacklistVideo, MVideoWithBlacklistLight } from '@server/typings/models'
|
||||
|
||||
async function autoBlacklistVideoIfNeeded (parameters: {
|
||||
video: MVideoWithBlacklistLight,
|
||||
|
@ -31,7 +31,7 @@ async function autoBlacklistVideoIfNeeded (parameters: {
|
|||
reason: 'Auto-blacklisted. Moderator review required.',
|
||||
type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED
|
||||
}
|
||||
const [ videoBlacklist ] = await VideoBlacklistModel.findOrCreate<MVideoBlacklist>({
|
||||
const [ videoBlacklist ] = await VideoBlacklistModel.findOrCreate<MVideoBlacklistVideo>({
|
||||
where: {
|
||||
videoId: video.id
|
||||
},
|
||||
|
@ -40,7 +40,9 @@ async function autoBlacklistVideoIfNeeded (parameters: {
|
|||
})
|
||||
video.VideoBlacklist = videoBlacklist
|
||||
|
||||
if (notify) Notifier.Instance.notifyOnVideoAutoBlacklist(video)
|
||||
videoBlacklist.Video = video
|
||||
|
||||
if (notify) Notifier.Instance.notifyOnVideoAutoBlacklist(videoBlacklist)
|
||||
|
||||
logger.info('Video %s auto-blacklisted.', video.uuid)
|
||||
|
||||
|
|
|
@ -43,6 +43,8 @@ const updateNotificationSettingsValidator = [
|
|||
.custom(isUserNotificationSettingValid).withMessage('Should have a valid new user registration notification setting'),
|
||||
body('newInstanceFollower')
|
||||
.custom(isUserNotificationSettingValid).withMessage('Should have a valid new instance follower notification setting'),
|
||||
body('autoInstanceFollowing')
|
||||
.custom(isUserNotificationSettingValid).withMessage('Should have a valid new instance following notification setting'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking updateNotificationSettingsValidator parameters', { parameters: req.body })
|
||||
|
|
|
@ -381,7 +381,7 @@ export class AccountModel extends Model<AccountModel> {
|
|||
}
|
||||
|
||||
toActivityPubObject (this: MAccountAP) {
|
||||
const obj = this.Actor.toActivityPubObject(this.name, 'Account')
|
||||
const obj = this.Actor.toActivityPubObject(this.name)
|
||||
|
||||
return Object.assign(obj, {
|
||||
summary: this.description
|
||||
|
|
|
@ -111,6 +111,15 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM
|
|||
@Column
|
||||
newInstanceFollower: UserNotificationSettingValue
|
||||
|
||||
@AllowNull(false)
|
||||
@Default(null)
|
||||
@Is(
|
||||
'UserNotificationSettingNewInstanceFollower',
|
||||
value => throwIfNotValid(value, isUserNotificationSettingValid, 'autoInstanceFollowing')
|
||||
)
|
||||
@Column
|
||||
autoInstanceFollowing: UserNotificationSettingValue
|
||||
|
||||
@AllowNull(false)
|
||||
@Default(null)
|
||||
@Is(
|
||||
|
@ -165,7 +174,8 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM
|
|||
newUserRegistration: this.newUserRegistration,
|
||||
commentMention: this.commentMention,
|
||||
newFollow: this.newFollow,
|
||||
newInstanceFollower: this.newInstanceFollower
|
||||
newInstanceFollower: this.newInstanceFollower,
|
||||
autoInstanceFollowing: this.autoInstanceFollowing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -135,13 +135,18 @@ function buildAccountInclude (required: boolean, withActor = false) {
|
|||
]
|
||||
},
|
||||
{
|
||||
attributes: [ 'preferredUsername' ],
|
||||
attributes: [ 'preferredUsername', 'type' ],
|
||||
model: ActorModel.unscoped(),
|
||||
required: true,
|
||||
as: 'ActorFollowing',
|
||||
include: [
|
||||
buildChannelInclude(false),
|
||||
buildAccountInclude(false)
|
||||
buildAccountInclude(false),
|
||||
{
|
||||
attributes: [ 'host' ],
|
||||
model: ServerModel.unscoped(),
|
||||
required: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -404,6 +409,11 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
|
|||
|
||||
const account = this.Account ? this.formatActor(this.Account) : undefined
|
||||
|
||||
const actorFollowingType = {
|
||||
Application: 'instance' as 'instance',
|
||||
Group: 'channel' as 'channel',
|
||||
Person: 'account' as 'account'
|
||||
}
|
||||
const actorFollow = this.ActorFollow ? {
|
||||
id: this.ActorFollow.id,
|
||||
state: this.ActorFollow.state,
|
||||
|
@ -415,9 +425,10 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
|
|||
host: this.ActorFollow.ActorFollower.getHost()
|
||||
},
|
||||
following: {
|
||||
type: this.ActorFollow.ActorFollowing.VideoChannel ? 'channel' as 'channel' : 'account' as 'account',
|
||||
type: actorFollowingType[this.ActorFollow.ActorFollowing.type],
|
||||
displayName: (this.ActorFollow.ActorFollowing.VideoChannel || this.ActorFollow.ActorFollowing.Account).getDisplayName(),
|
||||
name: this.ActorFollow.ActorFollowing.preferredUsername
|
||||
name: this.ActorFollow.ActorFollowing.preferredUsername,
|
||||
host: this.ActorFollow.ActorFollowing.getHost()
|
||||
}
|
||||
} : undefined
|
||||
|
||||
|
|
|
@ -43,7 +43,6 @@ import {
|
|||
MActorFormattable,
|
||||
MActorFull,
|
||||
MActorHost,
|
||||
MActorRedundancyAllowedOpt,
|
||||
MActorServer,
|
||||
MActorSummaryFormattable
|
||||
} from '../../typings/models'
|
||||
|
@ -430,15 +429,8 @@ export class ActorModel extends Model<ActorModel> {
|
|||
})
|
||||
}
|
||||
|
||||
toActivityPubObject (this: MActorAP, name: string, type: 'Account' | 'Application' | 'VideoChannel') {
|
||||
toActivityPubObject (this: MActorAP, name: string) {
|
||||
let activityPubType
|
||||
if (type === 'Account') {
|
||||
activityPubType = 'Person' as 'Person'
|
||||
} else if (type === 'Application') {
|
||||
activityPubType = 'Application' as 'Application'
|
||||
} else { // VideoChannel
|
||||
activityPubType = 'Group' as 'Group'
|
||||
}
|
||||
|
||||
let icon = undefined
|
||||
if (this.avatarId) {
|
||||
|
@ -451,7 +443,7 @@ export class ActorModel extends Model<ActorModel> {
|
|||
}
|
||||
|
||||
const json = {
|
||||
type: activityPubType,
|
||||
type: this.type,
|
||||
id: this.url,
|
||||
following: this.getFollowingUrl(),
|
||||
followers: this.getFollowersUrl(),
|
||||
|
|
|
@ -51,6 +51,16 @@ export class ServerModel extends Model<ServerModel> {
|
|||
})
|
||||
BlockedByAccounts: ServerBlocklistModel[]
|
||||
|
||||
static load (id: number): Bluebird<MServer> {
|
||||
const query = {
|
||||
where: {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
return ServerModel.findOne(query)
|
||||
}
|
||||
|
||||
static loadByHost (host: string): Bluebird<MServer> {
|
||||
const query = {
|
||||
where: {
|
||||
|
|
|
@ -517,7 +517,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
|
|||
}
|
||||
|
||||
toActivityPubObject (this: MChannelAP): ActivityPubActor {
|
||||
const obj = this.Actor.toActivityPubObject(this.name, 'VideoChannel')
|
||||
const obj = this.Actor.toActivityPubObject(this.name)
|
||||
|
||||
return Object.assign(obj, {
|
||||
summary: this.description,
|
||||
|
|
|
@ -5,8 +5,16 @@ import 'mocha'
|
|||
import { CustomConfig } from '../../../../shared/models/server/custom-config.model'
|
||||
|
||||
import {
|
||||
createUser, flushTests, killallServers, makeDeleteRequest, makeGetRequest, makePutBodyRequest, flushAndRunServer, ServerInfo,
|
||||
setAccessTokensToServers, userLogin, immutableAssign, cleanupTests
|
||||
cleanupTests,
|
||||
createUser,
|
||||
flushAndRunServer,
|
||||
immutableAssign,
|
||||
makeDeleteRequest,
|
||||
makeGetRequest,
|
||||
makePutBodyRequest,
|
||||
ServerInfo,
|
||||
setAccessTokensToServers,
|
||||
userLogin
|
||||
} from '../../../../shared/extra-utils'
|
||||
|
||||
describe('Test config API validators', function () {
|
||||
|
@ -98,6 +106,17 @@ describe('Test config API validators', function () {
|
|||
enabled: false,
|
||||
manualApproval: true
|
||||
}
|
||||
},
|
||||
followings: {
|
||||
instance: {
|
||||
autoFollowBack: {
|
||||
enabled: true
|
||||
},
|
||||
autoFollowIndex: {
|
||||
enabled: true,
|
||||
indexUrl: 'https://index.example.com'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -172,7 +172,8 @@ describe('Test user notifications API validators', function () {
|
|||
commentMention: UserNotificationSettingValue.WEB,
|
||||
newFollow: UserNotificationSettingValue.WEB,
|
||||
newUserRegistration: UserNotificationSettingValue.WEB,
|
||||
newInstanceFollower: UserNotificationSettingValue.WEB
|
||||
newInstanceFollower: UserNotificationSettingValue.WEB,
|
||||
autoInstanceFollowing: UserNotificationSettingValue.WEB
|
||||
}
|
||||
|
||||
it('Should fail with missing fields', async function () {
|
||||
|
|
|
@ -16,8 +16,8 @@ import {
|
|||
immutableAssign,
|
||||
registerUser,
|
||||
removeVideoFromBlacklist,
|
||||
reportVideoAbuse,
|
||||
updateCustomConfig,
|
||||
reportVideoAbuse, unfollow,
|
||||
updateCustomConfig, updateCustomSubConfig,
|
||||
updateMyUser,
|
||||
updateVideo,
|
||||
updateVideoChannel,
|
||||
|
@ -45,7 +45,8 @@ import {
|
|||
getUserNotifications,
|
||||
markAsReadAllNotifications,
|
||||
markAsReadNotifications,
|
||||
updateMyNotificationSettings
|
||||
updateMyNotificationSettings,
|
||||
checkAutoInstanceFollowing
|
||||
} from '../../../../shared/extra-utils/users/user-notifications'
|
||||
import {
|
||||
User,
|
||||
|
@ -108,7 +109,8 @@ describe('Test users notifications', function () {
|
|||
commentMention: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||
newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||
newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||
newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
|
||||
newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||
autoInstanceFollowing: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
|
||||
}
|
||||
|
||||
before(async function () {
|
||||
|
@ -897,6 +899,36 @@ describe('Test users notifications', function () {
|
|||
const userOverride = { socketNotifications: userNotifications, token: userAccessToken, check: { web: true, mail: false } }
|
||||
await checkNewInstanceFollower(immutableAssign(baseParams, userOverride), 'localhost:' + servers[2].port, 'absence')
|
||||
})
|
||||
|
||||
it('Should send a notification on auto follow back', async function () {
|
||||
this.timeout(40000)
|
||||
|
||||
await unfollow(servers[2].url, servers[2].accessToken, servers[0])
|
||||
await waitJobs(servers)
|
||||
|
||||
const config = {
|
||||
followings: {
|
||||
instance: {
|
||||
autoFollowBack: { enabled: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
await updateCustomSubConfig(servers[0].url, servers[0].accessToken, config)
|
||||
|
||||
await follow(servers[2].url, [ servers[0].url ], servers[2].accessToken)
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
const followerHost = servers[0].host
|
||||
const followingHost = servers[2].host
|
||||
await checkAutoInstanceFollowing(baseParams, followerHost, followingHost, 'presence')
|
||||
|
||||
const userOverride = { socketNotifications: userNotifications, token: userAccessToken, check: { web: true, mail: false } }
|
||||
await checkAutoInstanceFollowing(immutableAssign(baseParams, userOverride), followerHost, followingHost, 'absence')
|
||||
|
||||
config.followings.instance.autoFollowBack.enabled = false
|
||||
await updateCustomSubConfig(servers[0].url, servers[0].accessToken, config)
|
||||
})
|
||||
})
|
||||
|
||||
describe('New actor follow', function () {
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
/* tslint:disable:no-unused-expression */
|
||||
|
||||
import * as chai from 'chai'
|
||||
import 'mocha'
|
||||
import {
|
||||
acceptFollower,
|
||||
cleanupTests,
|
||||
flushAndRunMultipleServers,
|
||||
ServerInfo,
|
||||
setAccessTokensToServers,
|
||||
unfollow,
|
||||
updateCustomSubConfig
|
||||
} from '../../../../shared/extra-utils/index'
|
||||
import { follow, getFollowersListPaginationAndSort, getFollowingListPaginationAndSort } from '../../../../shared/extra-utils/server/follows'
|
||||
import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
|
||||
import { ActorFollow } from '../../../../shared/models/actors'
|
||||
|
||||
const expect = chai.expect
|
||||
|
||||
async function checkFollow (follower: ServerInfo, following: ServerInfo, exists: boolean) {
|
||||
{
|
||||
const res = await getFollowersListPaginationAndSort(following.url, 0, 5, '-createdAt')
|
||||
const follows = res.body.data as ActorFollow[]
|
||||
|
||||
if (exists === true) {
|
||||
expect(res.body.total).to.equal(1)
|
||||
|
||||
expect(follows[ 0 ].follower.host).to.equal(follower.host)
|
||||
expect(follows[ 0 ].state).to.equal('accepted')
|
||||
} else {
|
||||
expect(follows.filter(f => f.state === 'accepted')).to.have.lengthOf(0)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const res = await getFollowingListPaginationAndSort(follower.url, 0, 5, '-createdAt')
|
||||
const follows = res.body.data as ActorFollow[]
|
||||
|
||||
if (exists === true) {
|
||||
expect(res.body.total).to.equal(1)
|
||||
|
||||
expect(follows[ 0 ].following.host).to.equal(following.host)
|
||||
expect(follows[ 0 ].state).to.equal('accepted')
|
||||
} else {
|
||||
expect(follows.filter(f => f.state === 'accepted')).to.have.lengthOf(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function server1Follows2 (servers: ServerInfo[]) {
|
||||
await follow(servers[0].url, [ servers[1].host ], servers[0].accessToken)
|
||||
|
||||
await waitJobs(servers)
|
||||
}
|
||||
|
||||
async function resetFollows (servers: ServerInfo[]) {
|
||||
try {
|
||||
await unfollow(servers[ 0 ].url, servers[ 0 ].accessToken, servers[ 1 ])
|
||||
await unfollow(servers[ 1 ].url, servers[ 1 ].accessToken, servers[ 0 ])
|
||||
} catch { /* empty */ }
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
await checkFollow(servers[0], servers[1], false)
|
||||
await checkFollow(servers[1], servers[0], false)
|
||||
}
|
||||
|
||||
describe('Test auto follows', function () {
|
||||
let servers: ServerInfo[] = []
|
||||
|
||||
before(async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
servers = await flushAndRunMultipleServers(2)
|
||||
|
||||
// Get the access tokens
|
||||
await setAccessTokensToServers(servers)
|
||||
})
|
||||
|
||||
describe('Auto follow back', function () {
|
||||
|
||||
it('Should not auto follow back if the option is not enabled', async function () {
|
||||
this.timeout(15000)
|
||||
|
||||
await server1Follows2(servers)
|
||||
|
||||
await checkFollow(servers[0], servers[1], true)
|
||||
await checkFollow(servers[1], servers[0], false)
|
||||
|
||||
await resetFollows(servers)
|
||||
})
|
||||
|
||||
it('Should auto follow back on auto accept if the option is enabled', async function () {
|
||||
this.timeout(15000)
|
||||
|
||||
const config = {
|
||||
followings: {
|
||||
instance: {
|
||||
autoFollowBack: { enabled: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
await updateCustomSubConfig(servers[1].url, servers[1].accessToken, config)
|
||||
|
||||
await server1Follows2(servers)
|
||||
|
||||
await checkFollow(servers[0], servers[1], true)
|
||||
await checkFollow(servers[1], servers[0], true)
|
||||
|
||||
await resetFollows(servers)
|
||||
})
|
||||
|
||||
it('Should wait the acceptation before auto follow back', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
const config = {
|
||||
followings: {
|
||||
instance: {
|
||||
autoFollowBack: { enabled: true }
|
||||
}
|
||||
},
|
||||
followers: {
|
||||
instance: {
|
||||
manualApproval: true
|
||||
}
|
||||
}
|
||||
}
|
||||
await updateCustomSubConfig(servers[1].url, servers[1].accessToken, config)
|
||||
|
||||
await server1Follows2(servers)
|
||||
|
||||
await checkFollow(servers[0], servers[1], false)
|
||||
await checkFollow(servers[1], servers[0], false)
|
||||
|
||||
await acceptFollower(servers[1].url, servers[1].accessToken, 'peertube@' + servers[0].host)
|
||||
await waitJobs(servers)
|
||||
|
||||
await checkFollow(servers[0], servers[1], true)
|
||||
await checkFollow(servers[1], servers[0], true)
|
||||
|
||||
await resetFollows(servers)
|
||||
})
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
await cleanupTests(servers)
|
||||
})
|
||||
})
|
|
@ -68,6 +68,10 @@ function checkInitialConfig (server: ServerInfo, data: CustomConfig) {
|
|||
|
||||
expect(data.followers.instance.enabled).to.be.true
|
||||
expect(data.followers.instance.manualApproval).to.be.false
|
||||
|
||||
expect(data.followings.instance.autoFollowBack.enabled).to.be.false
|
||||
expect(data.followings.instance.autoFollowIndex.enabled).to.be.false
|
||||
expect(data.followings.instance.autoFollowIndex.indexUrl).to.equal('https://instances.joinpeertube.org')
|
||||
}
|
||||
|
||||
function checkUpdatedConfig (data: CustomConfig) {
|
||||
|
@ -119,6 +123,10 @@ function checkUpdatedConfig (data: CustomConfig) {
|
|||
|
||||
expect(data.followers.instance.enabled).to.be.false
|
||||
expect(data.followers.instance.manualApproval).to.be.true
|
||||
|
||||
expect(data.followings.instance.autoFollowBack.enabled).to.be.true
|
||||
expect(data.followings.instance.autoFollowIndex.enabled).to.be.true
|
||||
expect(data.followings.instance.autoFollowIndex.indexUrl).to.equal('https://updated.example.com')
|
||||
}
|
||||
|
||||
describe('Test config', function () {
|
||||
|
@ -261,6 +269,17 @@ describe('Test config', function () {
|
|||
enabled: false,
|
||||
manualApproval: true
|
||||
}
|
||||
},
|
||||
followings: {
|
||||
instance: {
|
||||
autoFollowBack: {
|
||||
enabled: true
|
||||
},
|
||||
autoFollowIndex: {
|
||||
enabled: true,
|
||||
indexUrl: 'https://updated.example.com'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await updateCustomConfig(server.url, server.accessToken, newCustomConfig)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import './auto-follows'
|
||||
import './config'
|
||||
import './contact-form'
|
||||
import './email'
|
||||
|
|
|
@ -2,7 +2,7 @@ import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
|||
import {
|
||||
MActor,
|
||||
MActorAccount,
|
||||
MActorAccountChannel,
|
||||
MActorDefaultAccountChannel,
|
||||
MActorChannelAccountActor,
|
||||
MActorDefault,
|
||||
MActorFormattable,
|
||||
|
@ -37,8 +37,8 @@ export type MActorFollowActorsDefault = MActorFollow &
|
|||
Use<'ActorFollowing', MActorDefault>
|
||||
|
||||
export type MActorFollowFull = MActorFollow &
|
||||
Use<'ActorFollower', MActorAccountChannel> &
|
||||
Use<'ActorFollowing', MActorAccountChannel>
|
||||
Use<'ActorFollower', MActorDefaultAccountChannel> &
|
||||
Use<'ActorFollowing', MActorDefaultAccountChannel>
|
||||
|
||||
// ############################################################################
|
||||
|
||||
|
@ -51,10 +51,6 @@ export type MActorFollowActorsDefaultSubscription = MActorFollow &
|
|||
Use<'ActorFollower', MActorDefault> &
|
||||
Use<'ActorFollowing', SubscriptionFollowing>
|
||||
|
||||
export type MActorFollowFollowingFullFollowerAccount = MActorFollow &
|
||||
Use<'ActorFollower', MActorAccount> &
|
||||
Use<'ActorFollowing', MActorAccountChannel>
|
||||
|
||||
export type MActorFollowSubscriptions = MActorFollow &
|
||||
Use<'ActorFollowing', MActorChannelAccountActor>
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ export type MActorAccount = MActor &
|
|||
export type MActorChannel = MActor &
|
||||
Use<'VideoChannel', MChannel>
|
||||
|
||||
export type MActorAccountChannel = MActorAccount & MActorChannel
|
||||
export type MActorDefaultAccountChannel = MActorDefault & MActorAccount & MActorChannel
|
||||
|
||||
export type MActorServer = MActor &
|
||||
Use<'Server', MServer>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { UserNotificationModel } from '../../../models/account/user-notification'
|
||||
import { PickWith } from '../../utils'
|
||||
import { PickWith, PickWithOpt } from '../../utils'
|
||||
import { VideoModel } from '../../../models/video/video'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { ServerModel } from '../../../models/server/server'
|
||||
|
@ -48,12 +48,13 @@ export namespace UserNotificationIncludes {
|
|||
|
||||
export type ActorFollower = Pick<ActorModel, 'preferredUsername' | 'getHost'> &
|
||||
PickWith<ActorModel, 'Account', AccountInclude> &
|
||||
PickWith<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> &
|
||||
PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>>
|
||||
PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> &
|
||||
PickWithOpt<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>>
|
||||
|
||||
export type ActorFollowing = Pick<ActorModel, 'preferredUsername'> &
|
||||
export type ActorFollowing = Pick<ActorModel, 'preferredUsername' | 'type' | 'getHost'> &
|
||||
PickWith<ActorModel, 'VideoChannel', VideoChannelInclude> &
|
||||
PickWith<ActorModel, 'Account', AccountInclude>
|
||||
PickWith<ActorModel, 'Account', AccountInclude> &
|
||||
PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>>
|
||||
|
||||
export type ActorFollowInclude = Pick<ActorFollowModel, 'id' | 'state'> &
|
||||
PickWith<ActorFollowModel, 'ActorFollower', ActorFollower> &
|
||||
|
|
|
@ -13,6 +13,9 @@ export type MVideoBlacklistUnfederated = Pick<MVideoBlacklist, 'unfederated'>
|
|||
|
||||
// ############################################################################
|
||||
|
||||
export type MVideoBlacklistLightVideo = MVideoBlacklistLight &
|
||||
Use<'Video', MVideo>
|
||||
|
||||
export type MVideoBlacklistVideo = MVideoBlacklist &
|
||||
Use<'Video', MVideo>
|
||||
|
||||
|
|
|
@ -11,3 +11,12 @@ export type PickWith<T, KT extends keyof T, V> = {
|
|||
export type PickWithOpt<T, KT extends keyof T, V> = {
|
||||
[P in KT]?: T[P] extends V ? V : never
|
||||
}
|
||||
|
||||
// https://github.com/krzkaczor/ts-essentials Rocks!
|
||||
export type DeepPartial<T> = {
|
||||
[P in keyof T]?: T[P] extends Array<infer U>
|
||||
? Array<DeepPartial<U>>
|
||||
: T[P] extends ReadonlyArray<infer U>
|
||||
? ReadonlyArray<DeepPartial<U>>
|
||||
: DeepPartial<T[P]>
|
||||
};
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { makeDeleteRequest, makeGetRequest, makePutBodyRequest } from '../requests/requests'
|
||||
import { CustomConfig } from '../../models/server/custom-config.model'
|
||||
import { DeepPartial } from '@server/typings/utils'
|
||||
import { merge } from 'lodash'
|
||||
|
||||
function getConfig (url: string) {
|
||||
const path = '/api/v1/config'
|
||||
|
@ -44,7 +46,7 @@ function updateCustomConfig (url: string, token: string, newCustomConfig: Custom
|
|||
})
|
||||
}
|
||||
|
||||
function updateCustomSubConfig (url: string, token: string, newConfig: any) {
|
||||
function updateCustomSubConfig (url: string, token: string, newConfig: DeepPartial<CustomConfig>) {
|
||||
const updateParams: CustomConfig = {
|
||||
instance: {
|
||||
name: 'PeerTube updated',
|
||||
|
@ -130,10 +132,21 @@ function updateCustomSubConfig (url: string, token: string, newConfig: any) {
|
|||
enabled: true,
|
||||
manualApproval: false
|
||||
}
|
||||
},
|
||||
followings: {
|
||||
instance: {
|
||||
autoFollowBack: {
|
||||
enabled: false
|
||||
},
|
||||
autoFollowIndex: {
|
||||
indexUrl: 'https://instances.joinpeertube.org',
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(updateParams, newConfig)
|
||||
merge(updateParams, newConfig)
|
||||
|
||||
return updateCustomConfig(url, token, updateParams)
|
||||
}
|
||||
|
|
|
@ -279,8 +279,9 @@ async function checkNewActorFollow (
|
|||
expect(notification.actorFollow.follower.name).to.equal(followerName)
|
||||
expect(notification.actorFollow.follower.host).to.not.be.undefined
|
||||
|
||||
expect(notification.actorFollow.following.displayName).to.equal(followingDisplayName)
|
||||
expect(notification.actorFollow.following.type).to.equal(followType)
|
||||
const following = notification.actorFollow.following
|
||||
expect(following.displayName).to.equal(followingDisplayName)
|
||||
expect(following.type).to.equal(followType)
|
||||
} else {
|
||||
expect(notification).to.satisfy(n => {
|
||||
return n.type !== notificationType ||
|
||||
|
@ -327,6 +328,37 @@ async function checkNewInstanceFollower (base: CheckerBaseParams, followerHost:
|
|||
await checkNotification(base, notificationChecker, emailFinder, type)
|
||||
}
|
||||
|
||||
async function checkAutoInstanceFollowing (base: CheckerBaseParams, followerHost: string, followingHost: string, type: CheckerType) {
|
||||
const notificationType = UserNotificationType.AUTO_INSTANCE_FOLLOWING
|
||||
|
||||
function notificationChecker (notification: UserNotification, type: CheckerType) {
|
||||
if (type === 'presence') {
|
||||
expect(notification).to.not.be.undefined
|
||||
expect(notification.type).to.equal(notificationType)
|
||||
|
||||
const following = notification.actorFollow.following
|
||||
checkActor(following)
|
||||
expect(following.name).to.equal('peertube')
|
||||
expect(following.host).to.equal(followingHost)
|
||||
|
||||
expect(notification.actorFollow.follower.name).to.equal('peertube')
|
||||
expect(notification.actorFollow.follower.host).to.equal(followerHost)
|
||||
} else {
|
||||
expect(notification).to.satisfy(n => {
|
||||
return n.type !== notificationType || n.actorFollow.following.host !== followingHost
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function emailFinder (email: object) {
|
||||
const text: string = email[ 'text' ]
|
||||
|
||||
return text.includes(' automatically followed a new instance') && text.includes(followingHost)
|
||||
}
|
||||
|
||||
await checkNotification(base, notificationChecker, emailFinder, type)
|
||||
}
|
||||
|
||||
async function checkCommentMention (
|
||||
base: CheckerBaseParams,
|
||||
uuid: string,
|
||||
|
@ -427,8 +459,8 @@ async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, vi
|
|||
expect(notification).to.not.be.undefined
|
||||
expect(notification.type).to.equal(notificationType)
|
||||
|
||||
expect(notification.video.id).to.be.a('number')
|
||||
checkVideo(notification.video, videoName, videoUUID)
|
||||
expect(notification.videoBlacklist.video.id).to.be.a('number')
|
||||
checkVideo(notification.videoBlacklist.video, videoName, videoUUID)
|
||||
} else {
|
||||
expect(notification).to.satisfy((n: UserNotification) => {
|
||||
return n === undefined || n.video === undefined || n.video.uuid !== videoUUID
|
||||
|
@ -480,6 +512,7 @@ export {
|
|||
markAsReadAllNotifications,
|
||||
checkMyVideoImportIsFinished,
|
||||
checkUserRegistered,
|
||||
checkAutoInstanceFollowing,
|
||||
checkVideoIsPublished,
|
||||
checkNewVideoFromSubscription,
|
||||
checkNewActorFollow,
|
||||
|
|
|
@ -99,4 +99,16 @@ export interface CustomConfig {
|
|||
}
|
||||
}
|
||||
|
||||
followings: {
|
||||
instance: {
|
||||
autoFollowBack: {
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
autoFollowIndex: {
|
||||
enabled: boolean
|
||||
indexUrl: string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,4 +16,5 @@ export interface UserNotificationSetting {
|
|||
newFollow: UserNotificationSettingValue
|
||||
commentMention: UserNotificationSettingValue
|
||||
newInstanceFollower: UserNotificationSettingValue
|
||||
autoInstanceFollowing: UserNotificationSettingValue
|
||||
}
|
||||
|
|
|
@ -19,7 +19,9 @@ export enum UserNotificationType {
|
|||
|
||||
VIDEO_AUTO_BLACKLIST_FOR_MODERATORS = 12,
|
||||
|
||||
NEW_INSTANCE_FOLLOWER = 13
|
||||
NEW_INSTANCE_FOLLOWER = 13,
|
||||
|
||||
AUTO_INSTANCE_FOLLOWING = 14
|
||||
}
|
||||
|
||||
export interface VideoInfo {
|
||||
|
@ -78,10 +80,12 @@ export interface UserNotification {
|
|||
id: number
|
||||
follower: ActorInfo
|
||||
state: FollowState
|
||||
|
||||
following: {
|
||||
type: 'account' | 'channel'
|
||||
type: 'account' | 'channel' | 'instance'
|
||||
name: string
|
||||
displayName: string
|
||||
host: string
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
"typeRoots": [ "node_modules/@types", "server/typings" ],
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@server/typings/*": [ "server/typings/*" ],
|
||||
"@server/models/*": [ "server/models/*" ]
|
||||
"@server/*": [ "server/*" ]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
|
|
|
@ -4610,6 +4610,11 @@ mocha@^6.0.0:
|
|||
yargs-parser "13.0.0"
|
||||
yargs-unparser "1.5.0"
|
||||
|
||||
module-alias@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.1.tgz#553aea9dc7f99cd45fd75e34a574960dc46550da"
|
||||
integrity sha512-LTez0Eo+YtfUhgzhu/LqxkUzOpD+k5C0wXBLun0L1qE2BhHf6l09dqam8e7BnoMYA6mAlP0vSsGFQ8QHhGN/aQ==
|
||||
|
||||
moment-timezone@^0.5.21, moment-timezone@^0.5.25:
|
||||
version "0.5.26"
|
||||
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.26.tgz#c0267ca09ae84631aa3dc33f65bedbe6e8e0d772"
|
||||
|
|
Loading…
Reference in New Issue