moderation.ts 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. import express, { VideoLegacyUploadFile } from 'express'
  2. import { PathLike } from 'fs-extra/esm'
  3. import { Transaction } from 'sequelize'
  4. import { AbuseAuditView, auditLoggerFactory } from '@server/helpers/audit-logger.js'
  5. import { afterCommitIfTransaction } from '@server/helpers/database-utils.js'
  6. import { logger } from '@server/helpers/logger.js'
  7. import { AbuseModel } from '@server/models/abuse/abuse.js'
  8. import { VideoAbuseModel } from '@server/models/abuse/video-abuse.js'
  9. import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse.js'
  10. import { VideoFileModel } from '@server/models/video/video-file.js'
  11. import { FilteredModelAttributes } from '@server/types/index.js'
  12. import {
  13. MAbuseFull,
  14. MAccountDefault,
  15. MAccountLight,
  16. MComment,
  17. MCommentAbuseAccountVideo,
  18. MCommentOwnerVideo,
  19. MUser,
  20. MUserDefault,
  21. MVideoAbuseVideoFull,
  22. MVideoAccountLightBlacklistAllFiles
  23. } from '@server/types/models/index.js'
  24. import { LiveVideoCreate, VideoCommentCreate, VideoCreate, VideoImportCreate } from '@peertube/peertube-models'
  25. import { UserModel } from '../models/user/user.js'
  26. import { VideoCommentModel } from '../models/video/video-comment.js'
  27. import { VideoModel } from '../models/video/video.js'
  28. import { sendAbuse } from './activitypub/send/send-flag.js'
  29. import { Notifier } from './notifier/index.js'
  30. export type AcceptResult = {
  31. accepted: boolean
  32. errorMessage?: string
  33. }
  34. // ---------------------------------------------------------------------------
  35. // Stub function that can be filtered by plugins
  36. function isLocalVideoFileAccepted (object: {
  37. videoBody: VideoCreate
  38. videoFile: VideoLegacyUploadFile
  39. user: MUserDefault
  40. }): AcceptResult {
  41. return { accepted: true }
  42. }
  43. // ---------------------------------------------------------------------------
  44. // Stub function that can be filtered by plugins
  45. function isLocalLiveVideoAccepted (object: {
  46. liveVideoBody: LiveVideoCreate
  47. user: UserModel
  48. }): AcceptResult {
  49. return { accepted: true }
  50. }
  51. // ---------------------------------------------------------------------------
  52. // Stub function that can be filtered by plugins
  53. function isLocalVideoThreadAccepted (_object: {
  54. req: express.Request
  55. commentBody: VideoCommentCreate
  56. video: VideoModel
  57. user: UserModel
  58. }): AcceptResult {
  59. return { accepted: true }
  60. }
  61. // Stub function that can be filtered by plugins
  62. function isLocalVideoCommentReplyAccepted (_object: {
  63. req: express.Request
  64. commentBody: VideoCommentCreate
  65. parentComment: VideoCommentModel
  66. video: VideoModel
  67. user: UserModel
  68. }): AcceptResult {
  69. return { accepted: true }
  70. }
  71. // ---------------------------------------------------------------------------
  72. // Stub function that can be filtered by plugins
  73. function isRemoteVideoCommentAccepted (_object: {
  74. comment: MComment
  75. }): AcceptResult {
  76. return { accepted: true }
  77. }
  78. // ---------------------------------------------------------------------------
  79. // Stub function that can be filtered by plugins
  80. function isPreImportVideoAccepted (object: {
  81. videoImportBody: VideoImportCreate
  82. user: MUser
  83. }): AcceptResult {
  84. return { accepted: true }
  85. }
  86. // Stub function that can be filtered by plugins
  87. function isPostImportVideoAccepted (object: {
  88. videoFilePath: PathLike
  89. videoFile: VideoFileModel
  90. user: MUser
  91. }): AcceptResult {
  92. return { accepted: true }
  93. }
  94. // ---------------------------------------------------------------------------
  95. async function createVideoAbuse (options: {
  96. baseAbuse: FilteredModelAttributes<AbuseModel>
  97. videoInstance: MVideoAccountLightBlacklistAllFiles
  98. startAt: number
  99. endAt: number
  100. transaction: Transaction
  101. reporterAccount: MAccountDefault
  102. skipNotification: boolean
  103. }) {
  104. const { baseAbuse, videoInstance, startAt, endAt, transaction, reporterAccount, skipNotification } = options
  105. const associateFun = async (abuseInstance: MAbuseFull) => {
  106. const videoAbuseInstance: MVideoAbuseVideoFull = await VideoAbuseModel.create({
  107. abuseId: abuseInstance.id,
  108. videoId: videoInstance.id,
  109. startAt,
  110. endAt
  111. }, { transaction })
  112. videoAbuseInstance.Video = videoInstance
  113. abuseInstance.VideoAbuse = videoAbuseInstance
  114. return { isOwned: videoInstance.isOwned() }
  115. }
  116. return createAbuse({
  117. base: baseAbuse,
  118. reporterAccount,
  119. flaggedAccount: videoInstance.VideoChannel.Account,
  120. transaction,
  121. skipNotification,
  122. associateFun
  123. })
  124. }
  125. function createVideoCommentAbuse (options: {
  126. baseAbuse: FilteredModelAttributes<AbuseModel>
  127. commentInstance: MCommentOwnerVideo
  128. transaction: Transaction
  129. reporterAccount: MAccountDefault
  130. skipNotification: boolean
  131. }) {
  132. const { baseAbuse, commentInstance, transaction, reporterAccount, skipNotification } = options
  133. const associateFun = async (abuseInstance: MAbuseFull) => {
  134. const commentAbuseInstance: MCommentAbuseAccountVideo = await VideoCommentAbuseModel.create({
  135. abuseId: abuseInstance.id,
  136. videoCommentId: commentInstance.id
  137. }, { transaction })
  138. commentAbuseInstance.VideoComment = commentInstance
  139. abuseInstance.VideoCommentAbuse = commentAbuseInstance
  140. return { isOwned: commentInstance.isOwned() }
  141. }
  142. return createAbuse({
  143. base: baseAbuse,
  144. reporterAccount,
  145. flaggedAccount: commentInstance.Account,
  146. transaction,
  147. skipNotification,
  148. associateFun
  149. })
  150. }
  151. function createAccountAbuse (options: {
  152. baseAbuse: FilteredModelAttributes<AbuseModel>
  153. accountInstance: MAccountDefault
  154. transaction: Transaction
  155. reporterAccount: MAccountDefault
  156. skipNotification: boolean
  157. }) {
  158. const { baseAbuse, accountInstance, transaction, reporterAccount, skipNotification } = options
  159. const associateFun = () => {
  160. return Promise.resolve({ isOwned: accountInstance.isOwned() })
  161. }
  162. return createAbuse({
  163. base: baseAbuse,
  164. reporterAccount,
  165. flaggedAccount: accountInstance,
  166. transaction,
  167. skipNotification,
  168. associateFun
  169. })
  170. }
  171. // ---------------------------------------------------------------------------
  172. export {
  173. isLocalLiveVideoAccepted,
  174. isLocalVideoFileAccepted,
  175. isLocalVideoThreadAccepted,
  176. isRemoteVideoCommentAccepted,
  177. isLocalVideoCommentReplyAccepted,
  178. isPreImportVideoAccepted,
  179. isPostImportVideoAccepted,
  180. createAbuse,
  181. createVideoAbuse,
  182. createVideoCommentAbuse,
  183. createAccountAbuse
  184. }
  185. // ---------------------------------------------------------------------------
  186. async function createAbuse (options: {
  187. base: FilteredModelAttributes<AbuseModel>
  188. reporterAccount: MAccountDefault
  189. flaggedAccount: MAccountLight
  190. associateFun: (abuseInstance: MAbuseFull) => Promise<{ isOwned: boolean }>
  191. skipNotification: boolean
  192. transaction: Transaction
  193. }) {
  194. const { base, reporterAccount, flaggedAccount, associateFun, transaction, skipNotification } = options
  195. const auditLogger = auditLoggerFactory('abuse')
  196. const abuseAttributes = Object.assign({}, base, { flaggedAccountId: flaggedAccount.id })
  197. const abuseInstance: MAbuseFull = await AbuseModel.create(abuseAttributes, { transaction })
  198. abuseInstance.ReporterAccount = reporterAccount
  199. abuseInstance.FlaggedAccount = flaggedAccount
  200. const { isOwned } = await associateFun(abuseInstance)
  201. if (isOwned === false) {
  202. sendAbuse(reporterAccount.Actor, abuseInstance, abuseInstance.FlaggedAccount, transaction)
  203. }
  204. const abuseJSON = abuseInstance.toFormattedAdminJSON()
  205. auditLogger.create(reporterAccount.Actor.getIdentifier(), new AbuseAuditView(abuseJSON))
  206. if (!skipNotification) {
  207. afterCommitIfTransaction(transaction, () => {
  208. Notifier.Instance.notifyOnNewAbuse({
  209. abuse: abuseJSON,
  210. abuseInstance,
  211. reporter: reporterAccount.Actor.getIdentifier()
  212. })
  213. })
  214. }
  215. logger.info('Abuse report %d created.', abuseInstance.id)
  216. return abuseJSON
  217. }