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,131 @@ +// @flow + +import bots from 'lib/facts/bots'; +import { createMessageReply } from 'lib/shared/message-utils'; +import { type MessageReportCreationRequest } from 'lib/types/message-report-types'; +import { messageTypes } 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 { createBotViewer } from '../session/bots'; +import type { Viewer } from '../session/viewer'; +import createMessages from './message-creator'; + +const { commbot } = bots; + +async function createMessageReport( + viewer: Viewer, + request: MessageReportCreationRequest, +) { + const [ + reportedMessageText, + viewerUsername, + commbotThreadID, + reportedThread, + reportedMessageAuthor, + ] = await fetchMessageReportData(viewer, request); + + const reportMessage = getCommbotMessage( + viewerUsername, + reportedMessageAuthor, + reportedThread?.name, + reportedMessageText, + ); + const time = Date.now(); + const result = await createMessages(createBotViewer(commbot.userID), [ + { + type: messageTypes.TEXT, + threadID: commbotThreadID, + creatorID: commbot.userID, + time, + text: reportMessage, + }, + ]); + if (result.length === 0) { + throw new ServerError('message_report_failed'); + } +} + +async function fetchMessageReportData( + viewer: Viewer, + request: MessageReportCreationRequest, +): Promise<[?string, ?string, string, ?ServerThreadInfo, ?string]> { + 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; + 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, + viewerUsername, + commbotThreadID, + reportedThread, + reportedMessageAuthor, + ]; +} + +async function getCommbotThreadID(userID: string): Promise { + let commbotThreadID = await fetchPersonalThreadID(userID, commbot.userID); + if (!commbotThreadID) { + commbotThreadID = createCommbotThread(userID); + } + return commbotThreadID; +} + +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 ? createMessageReply(message) : 'non-text message'; + return ( + `user ${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,17 @@ return threads[0]?.id.toString(); } +async function serverThreadInfoFromMessageInfo( + message: ?RawMessageInfo | MessageInfo, +): Promise { + const threadID = message?.threadID; + if (threadID) { + const threads = await fetchServerThreadInfos(SQL`t.id = ${threadID}`); + return threads.threadInfos[threadID]; + } + return null; +} + export { fetchServerThreadInfos, fetchThreadInfos, @@ -249,4 +261,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,5 @@ +// @flow + +export type MessageReportCreationRequest = { + +messageID: string, +};