moderation.ts 7.6 KB

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