diff --git a/keyserver/src/creators/message-report-creator.js b/keyserver/src/creators/message-report-creator.js new file mode 100644 --- /dev/null +++ b/keyserver/src/creators/message-report-creator.js @@ -0,0 +1,141 @@ +// @flow + +import bots from 'lib/facts/bots'; +import { createMessageQuote } from 'lib/shared/message-utils'; +import { type MessageReportCreationRequest } from 'lib/types/message-report-types'; +import { messageTypes } from 'lib/types/message-types'; +import type { RawMessageInfo } from 'lib/types/message-types'; +import type { ServerThreadInfo } from 'lib/types/thread-types'; +import { ServerError } from 'lib/utils/errors'; +import { promiseAll } from 'lib/utils/promises'; + +import { createCommbotThread } from '../bots/commbot'; +import { fetchMessageInfoByID } from '../fetchers/message-fetchers'; +import { + fetchPersonalThreadID, + serverThreadInfoFromMessageInfo, +} from '../fetchers/thread-fetchers'; +import { + fetchUsername, + fetchKeyserverAdminID, +} from '../fetchers/user-fetchers'; +import type { Viewer } from '../session/viewer'; +import createMessages from './message-creator'; + +const { commbot } = bots; + +type MessageReportData = { + +reportedMessageText: ?string, + +reporterUsername: ?string, + +commbotThreadID: string, + +reportedThread: ?ServerThreadInfo, + +reportedMessageAuthor: ?string, +}; + +async function createMessageReport( + viewer: Viewer, + request: MessageReportCreationRequest, +): Promise { + const { + reportedMessageText, + reporterUsername, + commbotThreadID, + reportedThread, + reportedMessageAuthor, + } = await fetchMessageReportData(viewer, request); + + const reportMessage = getCommbotMessage( + reporterUsername, + reportedMessageAuthor, + reportedThread?.name, + reportedMessageText, + ); + const time = Date.now(); + const result = await createMessages(viewer, [ + { + type: messageTypes.TEXT, + threadID: commbotThreadID, + creatorID: commbot.userID, + time, + text: reportMessage, + }, + ]); + if (result.length === 0) { + throw new ServerError('message_report_failed'); + } + return result; +} + +async function fetchMessageReportData( + viewer: Viewer, + request: MessageReportCreationRequest, +): Promise { + const keyserverAdminIDPromise = fetchKeyserverAdminID(); + const reportedMessagePromise = fetchMessageInfoByID( + viewer, + request.messageID, + ); + const promises = {}; + + promises.viewerUsername = fetchUsername(viewer.id); + + const keyserverAdminID = await keyserverAdminIDPromise; + if (!keyserverAdminID) { + throw new ServerError('keyserver_admin_not_found'); + } + promises.commbotThreadID = getCommbotThreadID(keyserverAdminID); + + const reportedMessage = await reportedMessagePromise; + + if (reportedMessage) { + promises.reportedThread = serverThreadInfoFromMessageInfo(reportedMessage); + } + + const reportedMessageAuthorID = reportedMessage?.creatorID; + if (reportedMessageAuthorID) { + promises.reportedMessageAuthor = fetchUsername(reportedMessageAuthorID); + } + + const reportedMessageText = + reportedMessage?.type === 0 ? reportedMessage.text : null; + + const { + viewerUsername, + commbotThreadID, + reportedThread, + reportedMessageAuthor, + } = await promiseAll(promises); + + return { + reportedMessageText, + reporterUsername: viewerUsername, + commbotThreadID, + reportedThread, + reportedMessageAuthor, + }; +} + +async function getCommbotThreadID(userID: string): Promise { + const commbotThreadID = await fetchPersonalThreadID(userID, commbot.userID); + return commbotThreadID ?? createCommbotThread(userID); +} + +function getCommbotMessage( + reporterUsername: ?string, + messageAuthorUsername: ?string, + threadName: ?string, + message: ?string, +): string { + reporterUsername = reporterUsername ?? '[null]'; + const messageAuthor = messageAuthorUsername + ? `${messageAuthorUsername}’s` + : 'this'; + const thread = threadName ? `chat titled "${threadName}"` : 'chat'; + const reply = message ? createMessageQuote(message) : 'non-text message'; + return ( + `${reporterUsername} reported ${messageAuthor} message in ${thread}\n` + + reply + ); +} + +export default createMessageReport; diff --git a/keyserver/src/fetchers/thread-fetchers.js b/keyserver/src/fetchers/thread-fetchers.js --- a/keyserver/src/fetchers/thread-fetchers.js +++ b/keyserver/src/fetchers/thread-fetchers.js @@ -7,6 +7,7 @@ getCommunity, } from 'lib/shared/thread-utils'; import { hasMinCodeVersion } from 'lib/shared/version-utils'; +import type { RawMessageInfo, MessageInfo } from 'lib/types/message-types'; import { threadTypes, type ThreadType, @@ -240,6 +241,14 @@ return threads[0]?.id.toString(); } +async function serverThreadInfoFromMessageInfo( + message: RawMessageInfo | MessageInfo, +): Promise { + const threadID = message.threadID; + const threads = await fetchServerThreadInfos(SQL`t.id = ${threadID}`); + return threads.threadInfos[threadID]; +} + export { fetchServerThreadInfos, fetchThreadInfos, @@ -249,4 +258,5 @@ determineThreadAncestry, personalThreadQuery, fetchPersonalThreadID, + serverThreadInfoFromMessageInfo, }; diff --git a/lib/types/message-report-types.js b/lib/types/message-report-types.js new file mode 100644 --- /dev/null +++ b/lib/types/message-report-types.js @@ -0,0 +1,11 @@ +// @flow + +import type { RawMessageInfo } from './message-types'; + +export type MessageReportCreationRequest = { + +messageID: string, +}; + +export type MessageReportCreationResult = { + +messageInfo: RawMessageInfo, +};