blob: d9edfe6fd845caf4dffe0ceb9e2b55a45f70805b [file] [log] [blame]
Adrià Vilanova Martínez647f6e12024-12-05 15:34:40 +01001import { ProtobufNumber, ProtobufObject } from '../common/protojs.types.js';
2import ItemMetadataState from './enums/ItemMetadataState.js';
3import GapModel from './Gap.js';
4import ThreadModel from './Thread';
5import UserModel from './User.js';
6
7// TODO(https://iavm.xyz/b/twpowertools/231): This class is being used for 2
8// messages in different places. Fix this.
9/**
10 * Model for the `ForumMessage` protobuf message.
11 *
12 * WARNING: it has methods which correspond to the `MessageView` message.
13 */
14export default class MessageModel {
15 private data: ProtobufObject;
16 private thread: ThreadModel;
17 private commentsAndGaps: Array<MessageModel | GapModel> | null;
18
19 constructor(data?: ProtobufObject, thread?: ThreadModel) {
20 this.data = data ?? {};
21 this.thread = thread ?? new ThreadModel();
22 this.commentsAndGaps = null;
23 }
24
25 getCreatedTimestamp() {
26 return (this.data[1]?.[1]?.[2] as ProtobufNumber) ?? null;
27 }
28
29 getCreatedMicroseconds() {
30 let a = this.getCreatedTimestamp();
31 if (a === null) a = '0';
32 return BigInt(a);
33 }
34
35 getRawCommentsAndGaps(): ProtobufObject[] {
36 return this.data[12] ?? [];
37 }
38
39 private getMessageOrGapModels(): Array<MessageModel | GapModel> {
40 const rawMogs = this.getRawCommentsAndGaps();
41 return rawMogs
42 .filter((mog) => mog !== undefined)
43 .map((mog) => {
44 if (mog[1]) return new MessageModel(mog[1], this.thread);
45 if (mog[2]) return new GapModel(mog[2], this.thread);
46 throw new Error('Expected message or gap.');
47 });
48 }
49
50 getCommentsAndGaps(): Array<MessageModel | GapModel> {
51 if (this.commentsAndGaps === null)
52 this.commentsAndGaps = this.getMessageOrGapModels();
53 return this.commentsAndGaps;
54 }
55
56 clearCommentsAndGaps() {
57 this.commentsAndGaps = [];
58 this.data[12] = [];
59 }
60
61 getPayload() {
62 return this.data[1]?.[4] as string ?? null;
63 }
64
65 setPayload(value: string | null) {
66 if (!this.data[1]) this.data[1] = [];
67 this.data[1][4] = value;
68 }
69
70 getId() {
71 return this.data[1]?.[1]?.[1] as ProtobufNumber ?? null;
72 }
73
74 getAuthor(): ProtobufObject | null {
75 return this.data[3] ?? null;
76 }
77
78 getParentMessageId() {
79 return this.data[1]?.[37] as ProtobufNumber ?? null;
80 }
81
82 clearParentMessageId() {
83 if (!this.data[1]) return;
84 delete this.data[1][37];
85 }
86
87 isDeleted() {
88 return this.data[5]?.[3] as boolean ?? null;
89 }
90
91 getState() {
92 return this.data[5]?.[1] as number ?? null;
93 }
94
95 getEndPendingStateTimestampMicros() {
96 return this.data[1]?.[17] as ProtobufNumber ?? null;
97 }
98
99 isTakenDown() {
100 return [
101 ItemMetadataState.AUTOMATED_ABUSE_TAKE_DOWN_DELETE,
102 ItemMetadataState.MANUAL_PROFILE_TAKE_DOWN_SUSPEND,
103 ItemMetadataState.AUTOMATED_ABUSE_TAKE_DOWN_HIDE,
104 ItemMetadataState.MANUAL_TAKE_DOWN_DELETE,
105 ItemMetadataState.MANUAL_TAKE_DOWN_HIDE,
106 ].includes(this.getState());
107 }
108
109 isComment() {
110 return !!this.getParentMessageId;
111 }
112
113 toRawMessageOrGap(): ProtobufObject {
114 return { 1: this.data };
115 }
116
117 mergeCommentOrGapViews(a: MessageModel) {
118 this.commentsAndGaps = ThreadModel.mergeMessageOrGaps(
119 a.getCommentsAndGaps(),
120 this.getCommentsAndGaps(),
121 );
122 this.data[12] = this.commentsAndGaps.map((cog) => cog.toRawMessageOrGap());
123 }
124
125 /**
126 * The following method is based on logic written by Googlers in the TW
127 * frontend and thus is not included as part of the MIT license.
128 *
129 * Source:
130 * module$exports$google3$customer_support$content$ui$client$tailwind$models$message_model$message_model.MessageModel.prototype.canComment
131 */
132 canComment(currentUser: UserModel) {
133 if (this.isDeleted()) return false;
134 if (this.isTakenDown()) return false;
135 if (currentUser.isAccountDisabled()) return false;
136 if (
137 this.thread.isLocked() &&
138 !currentUser.isAtLeastCommunityManager(this.thread.getForumId())
139 ) {
140 return false;
141 }
142 if (
143 this.thread.isSoftLocked() &&
144 !currentUser.isAtLeastSilverRole() &&
145 !this.thread.isAuthoredByUser()
146 ) {
147 return false;
148 }
149 return true;
150 }
151}