From 29329d6c459f69ce9168f20cfb84c020e41c6e7a Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 29 Mar 2024 14:25:03 +0100 Subject: [PATCH] Implement auto tag on comments and videos * Comments and videos can be automatically tagged using core rules or watched word lists * These tags can be used to automatically filter videos and comments * Introduce a new video comment policy where comments must be approved first * Comments may have to be approved if the user auto block them using core rules or watched word lists * Implement FEP-5624 to federate reply control policies --- apps/peertube-cli/src/peertube-upload.ts | 51 +- apps/peertube-cli/src/shared/cli.ts | 2 +- client/src/app/+admin/admin.component.ts | 12 + .../following-list/follow-modal.component.ts | 9 +- .../+admin/moderation/moderation.routes.ts | 13 + .../watched-words-list-admin.component.html | 10 + .../watched-words-list-admin.component.ts | 13 + .../video-comment-list.component.html | 107 +-- .../video-comment-list.component.scss | 51 -- .../comments/video-comment-list.component.ts | 198 +----- .../overview/videos/video-admin.service.ts | 7 +- .../overview/videos/video-list.component.html | 34 +- .../overview/videos/video-list.component.ts | 11 +- client/src/app/+admin/routes.ts | 4 +- .../automatic-tag.service.ts | 45 ++ ...y-account-auto-tag-policies.component.html | 15 + .../my-account-auto-tag-policies.component.ts | 71 ++ .../comments-on-my-videos.component.html | 7 + .../comments-on-my-videos.component.ts | 14 + ...-account-watched-words-list.component.html | 10 + ...my-account-watched-words-list.component.ts | 19 + .../app/+my-account/my-account.component.ts | 57 +- client/src/app/+my-account/routes.ts | 52 +- .../shared/video-edit.component.html | 12 +- .../shared/video-edit.component.ts | 71 +- .../comment/video-comment.component.html | 3 + .../shared/comment/video-comment.component.ts | 41 +- .../comment/video-comments.component.html | 12 +- .../comment/video-comments.component.ts | 71 +- client/src/app/core/server/server.service.ts | 19 + .../shared/form-validators/host-validators.ts | 24 +- .../form-validators/shared/validator-utils.ts | 18 + .../watched-words-list-validators.ts | 51 ++ .../misc/top-menu-dropdown.component.html | 54 +- .../misc/top-menu-dropdown.component.ts | 8 +- .../users/user-notification.model.ts | 7 + .../shared-main/video/video-details.model.ts | 7 +- .../shared-main/video/video-edit.model.ts | 15 +- .../shared-main/video/video-import.service.ts | 2 +- .../shared/shared-main/video/video.model.ts | 4 + .../shared/shared-main/video/video.service.ts | 2 +- .../batch-domains-modal.component.ts | 7 +- .../user-moderation-dropdown.component.ts | 2 +- ...eo-comment-list-admin-owner.component.html | 122 ++++ ...eo-comment-list-admin-owner.component.scss | 60 ++ ...ideo-comment-list-admin-owner.component.ts | 260 ++++++++ .../video-comment.model.ts | 16 +- .../video-comment.service.ts | 76 ++- .../video-playlist.service.ts | 2 +- .../user-notifications.component.html | 1 + ...ched-words-list-admin-owner.component.html | 86 +++ ...atched-words-list-admin-owner.component.ts | 138 ++++ ...tched-words-list-save-modal.component.html | 49 ++ ...tched-words-list-save-modal.component.scss | 6 + ...watched-words-list-save-modal.component.ts | 95 +++ .../watched-words-list.service.ts | 86 +++ client/src/root-helpers/string.ts | 6 + config/default.yaml | 3 +- config/production.yaml.example | 3 +- package.json | 2 + packages/core-utils/src/users/user-role.ts | 6 +- packages/models/src/activitypub/activity.ts | 20 +- packages/models/src/activitypub/context.ts | 4 +- .../objects/video-comment-object.ts | 5 + .../src/activitypub/objects/video-object.ts | 7 +- .../auto-tag-policies-export.ts | 5 + .../peertube-export-format/index.ts | 2 + .../user-video-history-export.ts | 4 +- .../video-export.model.ts | 6 +- .../watched-words-lists-export.ts | 10 + .../import-export/user-import-result.model.ts | 3 + .../automatic-tag-available.model.ts | 8 + .../moderation/automatic-tag-policy.enum.ts | 6 + ...ent-automatic-tag-policies-update.model.ts | 3 + .../comment-automatic-tag-policies.model.ts | 3 + packages/models/src/moderation/index.ts | 7 +- .../moderation/watched-words-list.model.ts | 9 + .../src/search/videos-common-query.model.ts | 6 +- .../models/src/server/server-config.model.ts | 6 +- .../src/users/user-notification.model.ts | 1 + packages/models/src/users/user-right.enum.ts | 7 +- packages/models/src/videos/comment/index.ts | 1 + .../comment/video-comment-policy.enum.ts | 7 + .../src/videos/comment/video-comment.model.ts | 12 +- .../models/src/videos/video-create.model.ts | 5 + .../models/src/videos/video-include.enum.ts | 3 +- .../models/src/videos/video-update.model.ts | 5 + packages/models/src/videos/video.model.ts | 12 +- .../src/moderation/automatic-tags-command.ts | 68 ++ .../server-commands/src/moderation/index.ts | 2 + .../src/moderation/watched-words-command.ts | 87 +++ packages/server-commands/src/server/server.ts | 26 +- .../src/videos/comments-command.ts | 110 +++- .../src/videos/videos-command.ts | 11 +- .../tests/src/api/check-params/auto-tags.ts | 137 ++++ packages/tests/src/api/check-params/index.ts | 8 +- packages/tests/src/api/check-params/live.ts | 19 +- .../api/check-params/video-channel-syncs.ts | 2 +- .../src/api/check-params/video-comments.ts | 162 +++-- .../src/api/check-params/video-imports.ts | 18 +- .../src/api/check-params/video-passwords.ts | 7 +- .../api/check-params/videos-common-filters.ts | 18 + packages/tests/src/api/check-params/videos.ts | 62 +- packages/tests/src/api/check-params/views.ts | 2 +- .../src/api/check-params/watched-words.ts | 254 ++++++++ packages/tests/src/api/live/live.ts | 3 +- .../src/api/moderation/automatic-tags.ts | 489 ++++++++++++++ .../src/api/moderation/comment-approval.ts | 552 ++++++++++++++++ packages/tests/src/api/moderation/index.ts | 2 + .../tests/src/api/moderation/watched-words.ts | 189 ++++++ .../notifications/comments-notifications.ts | 111 +++- .../tests/src/api/server/config-defaults.ts | 8 +- packages/tests/src/api/server/follows.ts | 8 +- packages/tests/src/api/server/handle-down.ts | 10 +- packages/tests/src/api/users/user-export.ts | 30 +- packages/tests/src/api/users/user-import.ts | 34 +- .../tests/src/api/videos/multiple-servers.ts | 18 +- .../tests/src/api/videos/single-server.ts | 8 +- .../tests/src/api/videos/video-comments.ts | 112 +++- packages/tests/src/feeds/feeds.ts | 109 +++- packages/tests/src/server-helpers/index.ts | 1 + packages/tests/src/server-helpers/regexp.ts | 60 ++ packages/tests/src/shared/import-export.ts | 24 +- packages/tests/src/shared/notifications.ts | 15 +- packages/tests/src/shared/videos.ts | 16 +- .../user-import-completed/html.pug | 6 + .../video-comment-new/html.pug | 5 + server/core/controllers/activitypub/client.ts | 40 +- server/core/controllers/api/automatic-tags.ts | 82 +++ server/core/controllers/api/index.ts | 8 +- server/core/controllers/api/users/me.ts | 58 +- server/core/controllers/api/videos/comment.ts | 104 +-- server/core/controllers/api/videos/update.ts | 20 +- server/core/controllers/api/watched-words.ts | 162 +++++ .../core/controllers/feeds/comment-feeds.ts | 12 +- server/core/helpers/activity-pub-utils.ts | 24 +- server/core/helpers/audit-logger.ts | 2 +- .../custom-validators/activitypub/activity.ts | 61 +- .../activitypub/video-comments.ts | 4 +- .../custom-validators/activitypub/videos.ts | 25 +- .../core/helpers/custom-validators/videos.ts | 9 +- .../custom-validators/watched-words.ts | 17 + server/core/helpers/query.ts | 3 +- server/core/helpers/regexp.ts | 20 +- .../core/initializers/checker-before-init.ts | 2 +- server/core/initializers/config.ts | 3 +- server/core/initializers/constants.ts | 20 + server/core/initializers/database.ts | 28 +- .../initializers/migrations/0840-auto-tags.ts | 122 ++++ .../lib/activitypub/process/process-create.ts | 30 +- .../lib/activitypub/process/process-delete.ts | 2 +- .../lib/activitypub/process/process-flag.ts | 2 +- .../process/process-reply-approval.ts | 45 ++ .../core/lib/activitypub/process/process.ts | 7 +- server/core/lib/activitypub/send/http.ts | 19 +- server/core/lib/activitypub/send/index.ts | 1 + .../core/lib/activitypub/send/send-create.ts | 16 +- .../core/lib/activitypub/send/send-delete.ts | 4 +- .../activitypub/send/send-reply-approval.ts | 36 + .../activitypub/send/shared/audience-utils.ts | 2 +- server/core/lib/activitypub/url.ts | 101 +-- server/core/lib/activitypub/video-comments.ts | 66 +- .../videos/shared/abstract-builder.ts | 28 +- .../lib/activitypub/videos/shared/creator.ts | 4 +- .../shared/object-to-model-attributes.ts | 4 +- server/core/lib/activitypub/videos/updater.ts | 5 +- .../lib/automatic-tags/automatic-tagger.ts | 142 ++++ .../core/lib/automatic-tags/automatic-tags.ts | 99 +++ .../job-queue/handlers/activitypub-cleaner.ts | 4 +- .../lib/job-queue/handlers/video-import.ts | 29 +- .../job-queue/handlers/video-live-ending.ts | 2 +- server/core/lib/local-video-creator.ts | 9 +- server/core/lib/notifier/notifier.ts | 10 + .../shared/comment/comment-mention.ts | 6 +- .../comment/new-comment-for-video-owner.ts | 18 +- server/core/lib/server-config-manager.ts | 7 +- server/core/lib/stat-manager.ts | 8 +- .../exporters/auto-tag-policies.ts | 18 + .../exporters/comments-exporter.ts | 4 +- .../lib/user-import-export/exporters/index.ts | 4 +- .../exporters/videos-exporter.ts | 7 +- .../exporters/watched-words-lists-exporter.ts | 23 + .../review-comments-tag-policies-importer.ts | 31 + .../importers/user-video-history-importer.ts | 10 +- .../importers/videos-importer.ts | 32 +- .../importers/watched-words-lists-importer.ts | 35 + .../lib/user-import-export/user-exporter.ts | 45 +- .../lib/user-import-export/user-importer.ts | 38 +- server/core/lib/video-comment.ts | 135 +++- server/core/lib/video-pre-import.ts | 13 +- server/core/lib/video.ts | 22 +- .../middlewares/validators/automatic-tags.ts | 45 ++ server/core/middlewares/validators/bulk.ts | 2 +- .../middlewares/validators/shared/users.ts | 47 +- .../middlewares/validators/shared/videos.ts | 4 - server/core/middlewares/validators/sort.ts | 2 + .../middlewares/validators/users/users.ts | 20 +- .../validators/videos/video-comments.ts | 172 +++-- .../middlewares/validators/videos/videos.ts | 16 +- .../middlewares/validators/watched-words.ts | 128 ++++ server/core/models/account/account.ts | 35 +- server/core/models/actor/actor-follow.ts | 2 +- .../account-automatic-tag-policy.ts | 96 +++ .../models/automatic-tag/automatic-tag.ts | 69 ++ .../automatic-tag/comment-automatic-tag.ts | 76 +++ .../automatic-tag/video-automatic-tag.ts | 76 +++ server/core/models/shared/model-builder.ts | 7 +- server/core/models/shared/query.ts | 34 +- server/core/models/shared/sql.ts | 33 +- .../user-notitication-list-query-builder.ts | 1 + server/core/models/user/user-notification.ts | 3 +- .../formatter/video-activity-pub-format.ts | 20 +- .../video/formatter/video-api-format.ts | 27 +- .../video-comment-list-query-builder.ts | 77 ++- .../comment/video-comment-table-attributes.ts | 24 +- .../shared/abstract-video-query-builder.ts | 18 + .../sql/video/shared/video-model-builder.ts | 26 + .../video/shared/video-table-attributes.ts | 11 +- .../sql/video/videos-id-list-query-builder.ts | 22 + .../video/videos-model-list-query-builder.ts | 12 +- server/core/models/video/tag.ts | 63 +- server/core/models/video/video-comment.ts | 205 ++++-- server/core/models/video/video-file.ts | 6 +- .../models/video/video-streaming-playlist.ts | 4 +- server/core/models/video/video.ts | 16 +- .../watched-words/watched-words-list.ts | 206 ++++++ server/core/types/express.d.ts | 5 +- .../account-automatic-tag-policy.ts | 3 + .../models/automatic-tag/automatic-tag.ts | 3 + .../automatic-tag/comment-automatic-tag.ts | 15 + .../core/types/models/automatic-tag/index.ts | 4 + .../automatic-tag/video-automatic-tag.ts | 15 + server/core/types/models/index.ts | 2 + .../types/models/user/user-notification.ts | 2 +- .../core/types/models/video/video-channel.ts | 9 +- .../core/types/models/video/video-comment.ts | 22 +- server/core/types/models/video/video.ts | 3 +- .../core/types/models/watched-words/index.ts | 1 + .../watched-words/watched-words-list.ts | 3 + support/doc/api/openapi.yaml | 614 +++++++++++++++++- yarn.lock | 5 + 241 files changed, 8090 insertions(+), 1399 deletions(-) create mode 100644 client/src/app/+admin/moderation/watched-words-list/watched-words-list-admin.component.html create mode 100644 client/src/app/+admin/moderation/watched-words-list/watched-words-list-admin.component.ts create mode 100644 client/src/app/+my-account/my-account-auto-tag-policies/automatic-tag.service.ts create mode 100644 client/src/app/+my-account/my-account-auto-tag-policies/my-account-auto-tag-policies.component.html create mode 100644 client/src/app/+my-account/my-account-auto-tag-policies/my-account-auto-tag-policies.component.ts create mode 100644 client/src/app/+my-account/my-account-comments-on-my-videos/comments-on-my-videos.component.html create mode 100644 client/src/app/+my-account/my-account-comments-on-my-videos/comments-on-my-videos.component.ts create mode 100644 client/src/app/+my-account/my-account-watched-words-list/my-account-watched-words-list.component.html create mode 100644 client/src/app/+my-account/my-account-watched-words-list/my-account-watched-words-list.component.ts create mode 100644 client/src/app/shared/form-validators/shared/validator-utils.ts create mode 100644 client/src/app/shared/form-validators/watched-words-list-validators.ts create mode 100644 client/src/app/shared/shared-video-comment/video-comment-list-admin-owner.component.html create mode 100644 client/src/app/shared/shared-video-comment/video-comment-list-admin-owner.component.scss create mode 100644 client/src/app/shared/shared-video-comment/video-comment-list-admin-owner.component.ts create mode 100644 client/src/app/shared/standalone-watched-words/watched-words-list-admin-owner.component.html create mode 100644 client/src/app/shared/standalone-watched-words/watched-words-list-admin-owner.component.ts create mode 100644 client/src/app/shared/standalone-watched-words/watched-words-list-save-modal.component.html create mode 100644 client/src/app/shared/standalone-watched-words/watched-words-list-save-modal.component.scss create mode 100644 client/src/app/shared/standalone-watched-words/watched-words-list-save-modal.component.ts create mode 100644 client/src/app/shared/standalone-watched-words/watched-words-list.service.ts create mode 100644 packages/models/src/import-export/peertube-export-format/auto-tag-policies-export.ts create mode 100644 packages/models/src/import-export/peertube-export-format/watched-words-lists-export.ts create mode 100644 packages/models/src/moderation/automatic-tag-available.model.ts create mode 100644 packages/models/src/moderation/automatic-tag-policy.enum.ts create mode 100644 packages/models/src/moderation/comment-automatic-tag-policies-update.model.ts create mode 100644 packages/models/src/moderation/comment-automatic-tag-policies.model.ts create mode 100644 packages/models/src/moderation/watched-words-list.model.ts create mode 100644 packages/models/src/videos/comment/video-comment-policy.enum.ts create mode 100644 packages/server-commands/src/moderation/automatic-tags-command.ts create mode 100644 packages/server-commands/src/moderation/watched-words-command.ts create mode 100644 packages/tests/src/api/check-params/auto-tags.ts create mode 100644 packages/tests/src/api/check-params/watched-words.ts create mode 100644 packages/tests/src/api/moderation/automatic-tags.ts create mode 100644 packages/tests/src/api/moderation/comment-approval.ts create mode 100644 packages/tests/src/api/moderation/watched-words.ts create mode 100644 packages/tests/src/server-helpers/regexp.ts create mode 100644 server/core/controllers/api/automatic-tags.ts create mode 100644 server/core/controllers/api/watched-words.ts create mode 100644 server/core/helpers/custom-validators/watched-words.ts create mode 100644 server/core/initializers/migrations/0840-auto-tags.ts create mode 100644 server/core/lib/activitypub/process/process-reply-approval.ts create mode 100644 server/core/lib/activitypub/send/send-reply-approval.ts create mode 100644 server/core/lib/automatic-tags/automatic-tagger.ts create mode 100644 server/core/lib/automatic-tags/automatic-tags.ts create mode 100644 server/core/lib/user-import-export/exporters/auto-tag-policies.ts create mode 100644 server/core/lib/user-import-export/exporters/watched-words-lists-exporter.ts create mode 100644 server/core/lib/user-import-export/importers/review-comments-tag-policies-importer.ts create mode 100644 server/core/lib/user-import-export/importers/watched-words-lists-importer.ts create mode 100644 server/core/middlewares/validators/automatic-tags.ts create mode 100644 server/core/middlewares/validators/watched-words.ts create mode 100644 server/core/models/automatic-tag/account-automatic-tag-policy.ts create mode 100644 server/core/models/automatic-tag/automatic-tag.ts create mode 100644 server/core/models/automatic-tag/comment-automatic-tag.ts create mode 100644 server/core/models/automatic-tag/video-automatic-tag.ts create mode 100644 server/core/models/watched-words/watched-words-list.ts create mode 100644 server/core/types/models/automatic-tag/account-automatic-tag-policy.ts create mode 100644 server/core/types/models/automatic-tag/automatic-tag.ts create mode 100644 server/core/types/models/automatic-tag/comment-automatic-tag.ts create mode 100644 server/core/types/models/automatic-tag/index.ts create mode 100644 server/core/types/models/automatic-tag/video-automatic-tag.ts create mode 100644 server/core/types/models/watched-words/index.ts create mode 100644 server/core/types/models/watched-words/watched-words-list.ts diff --git a/apps/peertube-cli/src/peertube-upload.ts b/apps/peertube-cli/src/peertube-upload.ts index 6a5950883..357870449 100644 --- a/apps/peertube-cli/src/peertube-upload.ts +++ b/apps/peertube-cli/src/peertube-upload.ts @@ -1,9 +1,9 @@ +import { Command } from '@commander-js/extra-typings' +import { VideoCommentPolicy, VideoPrivacy, VideoPrivacyType } from '@peertube/peertube-models' +import { PeerTubeServer } from '@peertube/peertube-server-commands' import { access, constants } from 'fs/promises' import { isAbsolute } from 'path' import { inspect } from 'util' -import { Command } from '@commander-js/extra-typings' -import { VideoPrivacy } from '@peertube/peertube-models' -import { PeerTubeServer } from '@peertube/peertube-server-commands' import { assignToken, buildServer, getServerCredentials, listOptions } from './shared/index.js' type UploadOptions = { @@ -14,13 +14,13 @@ type UploadOptions = { preview?: string file?: string videoName?: string - category?: string - licence?: string + category?: number + licence?: number language?: string - tags?: string + tags?: string[] nsfw?: true videoDescription?: string - privacy?: number + privacy?: VideoPrivacyType channelName?: string noCommentsEnabled?: true support?: string @@ -41,13 +41,13 @@ export function defineUploadProgram () { .option('--preview ', 'Preview path') .option('-f, --file ', 'Video absolute file path') .option('-n, --video-name ', 'Video name') - .option('-c, --category ', 'Category number') - .option('-l, --licence ', 'Licence number') + .option('-c, --category ', 'Category number', parseInt) + .option('-l, --licence ', 'Licence number', parseInt) .option('-L, --language ', 'Language ISO 639 code (fr or en...)') .option('-t, --tags ', 'Video tags', listOptions) .option('-N, --nsfw', 'Video is Not Safe For Work') .option('-d, --video-description ', 'Video description') - .option('-P, --privacy ', 'Privacy', parseInt) + .option('-P, --privacy ', 'Privacy', v => parseInt(v) as VideoPrivacyType) .option('-C, --channel-name ', 'Channel name') .option('--no-comments-enabled', 'Disable video comments') .option('-s, --support ', 'Video support text') @@ -120,10 +120,9 @@ async function run (options: UploadOptions) { } } -async function buildVideoAttributesFromCommander (server: PeerTubeServer, options: UploadOptions, defaultAttributes: any = {}) { +async function buildVideoAttributesFromCommander (server: PeerTubeServer, options: UploadOptions) { const defaultBooleanAttributes = { nsfw: false, - commentsEnabled: true, downloadEnabled: true, waitTranscoding: true } @@ -133,25 +132,29 @@ async function buildVideoAttributesFromCommander (server: PeerTubeServer, option for (const key of Object.keys(defaultBooleanAttributes)) { if (options[key] !== undefined) { booleanAttributes[key] = options[key] - } else if (defaultAttributes[key] !== undefined) { - booleanAttributes[key] = defaultAttributes[key] } else { booleanAttributes[key] = defaultBooleanAttributes[key] } } const videoAttributes = { - name: options.videoName || defaultAttributes.name, - category: options.category || defaultAttributes.category || undefined, - licence: options.licence || defaultAttributes.licence || undefined, - language: options.language || defaultAttributes.language || undefined, - privacy: options.privacy || defaultAttributes.privacy || VideoPrivacy.PUBLIC, - support: options.support || defaultAttributes.support || undefined, - description: options.videoDescription || defaultAttributes.description || undefined, - tags: options.tags || defaultAttributes.tags || undefined - } + name: options.videoName, + category: options.category || undefined, + licence: options.licence || undefined, + language: options.language || undefined, + privacy: options.privacy || VideoPrivacy.PUBLIC, + support: options.support || undefined, + description: options.videoDescription || undefined, + tags: options.tags || undefined, - Object.assign(videoAttributes, booleanAttributes) + commentsPolicy: options.noCommentsEnabled !== undefined + ? options.noCommentsEnabled === true + ? VideoCommentPolicy.DISABLED + : VideoCommentPolicy.ENABLED + : undefined, + + ...booleanAttributes + } if (options.channelName) { const videoChannel = await server.channels.get({ channelName: options.channelName }) diff --git a/apps/peertube-cli/src/shared/cli.ts b/apps/peertube-cli/src/shared/cli.ts index 080eb8237..256f4bcdb 100644 --- a/apps/peertube-cli/src/shared/cli.ts +++ b/apps/peertube-cli/src/shared/cli.ts @@ -120,7 +120,7 @@ function getRemoteObjectOrDie ( return { url, username, password } } -function listOptions (val: any) { +function listOptions (val: string) { return val.split(',') } diff --git a/client/src/app/+admin/admin.component.ts b/client/src/app/+admin/admin.component.ts index 766a4ec52..cb3b0a262 100644 --- a/client/src/app/+admin/admin.component.ts +++ b/client/src/app/+admin/admin.component.ts @@ -153,6 +153,14 @@ export class AdminComponent implements OnInit { }) } + if (this.hasServerWatchedWordsRight()) { + moderationItems.children.push({ + label: $localize`Watched words`, + routerLink: '/admin/moderation/watched-words/list', + iconName: 'eye-open' + }) + } + if (moderationItems.children.length !== 0) this.menuEntries.push(moderationItems) } @@ -241,6 +249,10 @@ export class AdminComponent implements OnInit { return this.auth.getUser().hasRight(UserRight.MANAGE_SERVERS_BLOCKLIST) } + private hasServerWatchedWordsRight () { + return this.auth.getUser().hasRight(UserRight.MANAGE_INSTANCE_WATCHED_WORDS) + } + private hasConfigRight () { return this.auth.getUser().hasRight(UserRight.MANAGE_CONFIGURATION) } diff --git a/client/src/app/+admin/follows/following-list/follow-modal.component.ts b/client/src/app/+admin/follows/following-list/follow-modal.component.ts index c642d2a3a..d57a49f7d 100644 --- a/client/src/app/+admin/follows/following-list/follow-modal.component.ts +++ b/client/src/app/+admin/follows/following-list/follow-modal.component.ts @@ -1,15 +1,16 @@ +import { NgClass, NgIf } from '@angular/common' import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' +import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { Notifier } from '@app/core' import { formatICU } from '@app/helpers' -import { splitAndGetNotEmpty, UNIQUE_HOSTS_OR_HANDLE_VALIDATOR } from '@app/shared/form-validators/host-validators' +import { UNIQUE_HOSTS_OR_HANDLE_VALIDATOR } from '@app/shared/form-validators/host-validators' import { FormReactive } from '@app/shared/shared-forms/form-reactive' import { FormReactiveService } from '@app/shared/shared-forms/form-reactive.service' +import { InstanceFollowService } from '@app/shared/shared-instance/instance-follow.service' import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' -import { NgClass, NgIf } from '@angular/common' -import { FormsModule, ReactiveFormsModule } from '@angular/forms' +import { splitAndGetNotEmpty } from '@root-helpers/string' import { GlobalIconComponent } from '../../../shared/shared-icons/global-icon.component' -import { InstanceFollowService } from '@app/shared/shared-instance/instance-follow.service' @Component({ selector: 'my-follow-modal', diff --git a/client/src/app/+admin/moderation/moderation.routes.ts b/client/src/app/+admin/moderation/moderation.routes.ts index f0494de7b..d354f8dcc 100644 --- a/client/src/app/+admin/moderation/moderation.routes.ts +++ b/client/src/app/+admin/moderation/moderation.routes.ts @@ -5,6 +5,7 @@ import { VideoBlockListComponent } from '@app/+admin/moderation/video-block-list import { UserRightGuard } from '@app/core' import { UserRight } from '@peertube/peertube-models' import { RegistrationListComponent } from './registration-list' +import { WatchedWordsListAdminComponent } from './watched-words-list/watched-words-list-admin.component' export const ModerationRoutes: Routes = [ { @@ -114,6 +115,18 @@ export const ModerationRoutes: Routes = [ title: $localize`Muted instances` } } + }, + + { + path: 'watched-words/list', + component: WatchedWordsListAdminComponent, + canActivate: [ UserRightGuard ], + data: { + userRight: UserRight.MANAGE_INSTANCE_WATCHED_WORDS, + meta: { + title: $localize`Watched words` + } + } } ] } diff --git a/client/src/app/+admin/moderation/watched-words-list/watched-words-list-admin.component.html b/client/src/app/+admin/moderation/watched-words-list/watched-words-list-admin.component.html new file mode 100644 index 000000000..230cd365f --- /dev/null +++ b/client/src/app/+admin/moderation/watched-words-list/watched-words-list-admin.component.html @@ -0,0 +1,10 @@ +

+ + Instance watched words lists +

+ +Video name/description and comments that contain any of the watched words are automatically tagged with the name of the list. +These automatic tags can be used to filter comments and videos. + + + diff --git a/client/src/app/+admin/moderation/watched-words-list/watched-words-list-admin.component.ts b/client/src/app/+admin/moderation/watched-words-list/watched-words-list-admin.component.ts new file mode 100644 index 000000000..2c15c3840 --- /dev/null +++ b/client/src/app/+admin/moderation/watched-words-list/watched-words-list-admin.component.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core' +import { GlobalIconComponent } from '@app/shared/shared-icons/global-icon.component' +import { WatchedWordsListAdminOwnerComponent } from '@app/shared/standalone-watched-words/watched-words-list-admin-owner.component' + +@Component({ + templateUrl: './watched-words-list-admin.component.html', + standalone: true, + imports: [ + GlobalIconComponent, + WatchedWordsListAdminOwnerComponent + ] +}) +export class WatchedWordsListAdminComponent { } diff --git a/client/src/app/+admin/overview/comments/video-comment-list.component.html b/client/src/app/+admin/overview/comments/video-comment-list.component.html index 183854af7..0b87f0f38 100644 --- a/client/src/app/+admin/overview/comments/video-comment-list.component.html +++ b/client/src/app/+admin/overview/comments/video-comment-list.component.html @@ -7,110 +7,5 @@ This view also shows comments from muted accounts. - - -
-
- - -
- -
- - - -
-
-
- - - - - - - - More information - - - Actions - - Account - Video - Comment - Date - - - - - - - - - - - - - - - - - - - - -
- -
- {{ videoComment.account.displayName }} - {{ videoComment.by }} -
-
-
- - - - Commented video - - {{ videoComment.video.name }} - - - -
- - - {{ videoComment.createdAt | date: 'short' }} - -
- - - - -
- - -
- - - - -
- No comments found matching current filters. - No comments found. -
- - -
-
+ diff --git a/client/src/app/+admin/overview/comments/video-comment-list.component.scss b/client/src/app/+admin/overview/comments/video-comment-list.component.scss index 2777bf6d1..d7deffa29 100644 --- a/client/src/app/+admin/overview/comments/video-comment-list.component.scss +++ b/client/src/app/+admin/overview/comments/video-comment-list.component.scss @@ -7,54 +7,3 @@ my-feed { display: inline-block; width: 15px; } - -my-global-icon { - width: 24px; - height: 24px; -} - -.video { - display: flex; - flex-direction: column; - - em { - font-size: 11px; - } - - a { - @include ellipsis; - - color: pvar(--mainForegroundColor); - } -} - -.comment-html { - ::ng-deep { - > div { - max-height: 22px; - } - - div, - p { - @include ellipsis; - } - - p { - margin: 0; - } - } -} - -.right-form { - display: flex; - - > *:not(:last-child) { - @include margin-right(10px); - } -} - -@media screen and (max-width: $primeng-breakpoint) { - .video { - align-items: flex-start !important; - } -} diff --git a/client/src/app/+admin/overview/comments/video-comment-list.component.ts b/client/src/app/+admin/overview/comments/video-comment-list.component.ts index f0fe8880e..5d5e7b89d 100644 --- a/client/src/app/+admin/overview/comments/video-comment-list.component.ts +++ b/client/src/app/+admin/overview/comments/video-comment-list.component.ts @@ -1,54 +1,22 @@ -import { SortMeta, SharedModule } from 'primeng/api' -import { Component, OnInit } from '@angular/core' -import { ActivatedRoute, Router } from '@angular/router' -import { AuthService, ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core' -import { FeedFormat, UserRight } from '@peertube/peertube-models' -import { formatICU } from '@app/helpers' -import { AutoColspanDirective } from '../../../shared/shared-main/angular/auto-colspan.directive' -import { ActorAvatarComponent } from '../../../shared/shared-actor-image/actor-avatar.component' -import { TableExpanderIconComponent } from '../../../shared/shared-tables/table-expander-icon.component' -import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap' -import { ButtonComponent } from '../../../shared/shared-main/buttons/button.component' -import { AdvancedInputFilter, AdvancedInputFilterComponent } from '../../../shared/shared-forms/advanced-input-filter.component' -import { ActionDropdownComponent, DropdownAction } from '../../../shared/shared-main/buttons/action-dropdown.component' -import { NgIf, NgClass, DatePipe } from '@angular/common' -import { TableModule } from 'primeng/table' -import { FeedComponent } from '../../../shared/shared-main/feeds/feed.component' -import { GlobalIconComponent } from '../../../shared/shared-icons/global-icon.component' -import { VideoCommentAdmin } from '@app/shared/shared-video-comment/video-comment.model' -import { BulkService } from '@app/shared/shared-moderation/bulk.service' +import { Component } from '@angular/core' import { VideoCommentService } from '@app/shared/shared-video-comment/video-comment.service' +import { FeedFormat } from '@peertube/peertube-models' +import { GlobalIconComponent } from '../../../shared/shared-icons/global-icon.component' +import { FeedComponent } from '../../../shared/shared-main/feeds/feed.component' +import { VideoCommentListAdminOwnerComponent } from '../../../shared/shared-video-comment/video-comment-list-admin-owner.component' @Component({ selector: 'my-video-comment-list', templateUrl: './video-comment-list.component.html', - styleUrls: [ '../../../shared/shared-moderation/moderation.scss', './video-comment-list.component.scss' ], + styleUrls: [ './video-comment-list.component.scss' ], standalone: true, imports: [ GlobalIconComponent, FeedComponent, - TableModule, - SharedModule, - NgIf, - ActionDropdownComponent, - AdvancedInputFilterComponent, - ButtonComponent, - NgbTooltip, - TableExpanderIconComponent, - NgClass, - ActorAvatarComponent, - AutoColspanDirective, - DatePipe + VideoCommentListAdminOwnerComponent ] }) -export class VideoCommentListComponent extends RestTable implements OnInit { - comments: VideoCommentAdmin[] - totalRecords = 0 - sort: SortMeta = { field: 'createdAt', order: -1 } - pagination: RestPagination = { count: this.rowsPerPage, start: 0 } - - videoCommentActions: DropdownAction[][] = [] - +export class VideoCommentListComponent { syndicationItems = [ { format: FeedFormat.RSS, @@ -66,154 +34,4 @@ export class VideoCommentListComponent extends RestTable imp url: VideoCommentService.BASE_FEEDS_URL + FeedFormat.JSON.toLowerCase() } ] - - bulkActions: DropdownAction[] = [] - - inputFilters: AdvancedInputFilter[] = [ - { - title: $localize`Advanced filters`, - children: [ - { - value: 'local:true', - label: $localize`Local comments` - }, - { - value: 'local:false', - label: $localize`Remote comments` - }, - { - value: 'localVideo:true', - label: $localize`Comments on local videos` - } - ] - } - ] - - get authUser () { - return this.auth.getUser() - } - - constructor ( - protected router: Router, - protected route: ActivatedRoute, - private auth: AuthService, - private notifier: Notifier, - private confirmService: ConfirmService, - private videoCommentService: VideoCommentService, - private markdownRenderer: MarkdownService, - private bulkService: BulkService - ) { - super() - - this.videoCommentActions = [ - [ - { - label: $localize`Delete this comment`, - handler: comment => this.deleteComment(comment), - isDisplayed: () => this.authUser.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) - }, - - { - label: $localize`Delete all comments of this account`, - description: $localize`Comments are deleted after a few minutes`, - handler: comment => this.deleteUserComments(comment), - isDisplayed: () => this.authUser.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) - } - ] - ] - } - - ngOnInit () { - this.initialize() - - this.bulkActions = [ - { - label: $localize`Delete`, - handler: comments => this.removeComments(comments), - isDisplayed: () => this.authUser.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT), - iconName: 'delete' - } - ] - } - - getIdentifier () { - return 'VideoCommentListComponent' - } - - toHtml (text: string) { - return this.markdownRenderer.textMarkdownToHTML({ markdown: text, withHtml: true, withEmoji: true }) - } - - protected reloadDataInternal () { - this.videoCommentService.getAdminVideoComments({ - pagination: this.pagination, - sort: this.sort, - search: this.search - }).subscribe({ - next: async resultList => { - this.totalRecords = resultList.total - - this.comments = [] - - for (const c of resultList.data) { - this.comments.push( - new VideoCommentAdmin(c, await this.toHtml(c.text)) - ) - } - }, - - error: err => this.notifier.error(err.message) - }) - } - - private removeComments (comments: VideoCommentAdmin[]) { - const commentArgs = comments.map(c => ({ videoId: c.video.id, commentId: c.id })) - - this.videoCommentService.deleteVideoComments(commentArgs) - .subscribe({ - next: () => { - this.notifier.success( - formatICU( - $localize`{count, plural, =1 {1 comment deleted.} other {{count} comments deleted.}}`, - { count: commentArgs.length } - ) - ) - - this.reloadData() - }, - - error: err => this.notifier.error(err.message), - - complete: () => this.selectedRows = [] - }) - } - - private deleteComment (comment: VideoCommentAdmin) { - this.videoCommentService.deleteVideoComment(comment.video.id, comment.id) - .subscribe({ - next: () => this.reloadData(), - - error: err => this.notifier.error(err.message) - }) - } - - private async deleteUserComments (comment: VideoCommentAdmin) { - const message = $localize`Do you really want to delete all comments of ${comment.by}?` - const res = await this.confirmService.confirm(message, $localize`Delete`) - if (res === false) return - - const options = { - accountName: comment.by, - scope: 'instance' as 'instance' - } - - this.bulkService.removeCommentsOf(options) - .subscribe({ - next: () => { - this.notifier.success($localize`Comments of ${options.accountName} will be deleted in a few minutes`) - }, - - error: err => this.notifier.error(err.message) - }) - } } diff --git a/client/src/app/+admin/overview/videos/video-admin.service.ts b/client/src/app/+admin/overview/videos/video-admin.service.ts index 6a45eb201..8ab91be96 100644 --- a/client/src/app/+admin/overview/videos/video-admin.service.ts +++ b/client/src/app/+admin/overview/videos/video-admin.service.ts @@ -113,7 +113,8 @@ export class VideoAdminService { VideoInclude.BLOCKED_OWNER | VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.FILES | - VideoInclude.SOURCE + VideoInclude.SOURCE | + VideoInclude.AUTOMATIC_TAGS let privacyOneOf = getAllPrivacies() @@ -143,6 +144,10 @@ export class VideoAdminService { excludePublic: { prefix: 'excludePublic', handler: () => true + }, + autoTagOneOf: { + prefix: 'autoTag:', + multiple: true } }) diff --git a/client/src/app/+admin/overview/videos/video-list.component.html b/client/src/app/+admin/overview/videos/video-list.component.html index 0bdccbab8..1dd0953f8 100644 --- a/client/src/app/+admin/overview/videos/video-list.component.html +++ b/client/src/app/+admin/overview/videos/video-list.component.html @@ -70,22 +70,34 @@ - @if (video.isLocal) { - Local - } @else { - Remote - } +
+ @if (video.isLocal) { + Local + } @else { + Remote + } - {{ video.privacy.label }} + {{ video.privacy.label }} - NSFW + NSFW - {{ video.state.label }} + {{ video.state.label }} - Account muted - Server muted + Account muted + Server muted - Blocked + Blocked +
+ +
+ @for (tag of video.automaticTags; track tag) { + {{ tag }} + } +
diff --git a/client/src/app/+admin/overview/videos/video-list.component.ts b/client/src/app/+admin/overview/videos/video-list.component.ts index 30868499d..3e6325e32 100644 --- a/client/src/app/+admin/overview/videos/video-list.component.ts +++ b/client/src/app/+admin/overview/videos/video-list.component.ts @@ -1,6 +1,6 @@ import { DatePipe, NgClass, NgFor, NgIf } from '@angular/common' import { Component, OnInit, ViewChild } from '@angular/core' -import { ActivatedRoute, Router } from '@angular/router' +import { ActivatedRoute, Router, RouterLink } from '@angular/router' import { AuthService, ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' import { formatICU, getAbsoluteAPIUrl } from '@app/helpers' import { Video } from '@app/shared/shared-main/video/video.model' @@ -51,6 +51,7 @@ import { VideoAdminService } from './video-admin.service' EmbedComponent, VideoBlockComponent, DatePipe, + RouterLink, BytesPipe ] }) @@ -256,6 +257,14 @@ export class VideoListComponent extends RestTable