blob: 21e75464023db43222009cbda14b05e82abf35f8 [file] [log] [blame]
Adrià Vilanova Martínez0d92a0c2023-11-06 01:37:20 +01001import {MDCTooltip} from '@material/tooltip';
2import {waitFor} from 'poll-until-promise';
3
4import {parseUrl} from '../../../common/commonUtils.js';
5import OptionsWatcher from '../../../common/optionsWatcher.js';
6
7import {kViewThreadResponse} from './consts.js';
8import ProfileInfoHandler from './handlers/profile.js';
9import ThreadListInfoHandler from './handlers/threadList.js';
10import ExpandedThreadListExtraInfoInjection from './injections/expandedThreadList.js';
11import ProfileAbuseExtraInfoInjection from './injections/profileAbuse.js';
12import ProfilePerForumStatsExtraInfoInjection from './injections/profilePerForumStats.js';
13import ThreadListExtraInfoInjection from './injections/threadList.js';
14import ThreadExtraInfoService from './services/thread.js';
15
16export default class ExtraInfo {
17 constructor() {
18 this.optionsWatcher = new OptionsWatcher(['extrainfo', 'perforumstats']);
19
20 const profileInfoHandler = new ProfileInfoHandler();
21 const threadListInfoHandler = new ThreadListInfoHandler();
22
23 this.profileAbuse = new ProfileAbuseExtraInfoInjection(
24 profileInfoHandler, this.optionsWatcher);
25 this.profilePerForumStats = new ProfilePerForumStatsExtraInfoInjection(
26 profileInfoHandler, this.optionsWatcher);
27 this.expandedThreadList = new ExpandedThreadListExtraInfoInjection(
28 threadListInfoHandler, this.optionsWatcher);
29 this.threadList = new ThreadListExtraInfoInjection(
30 threadListInfoHandler, this.optionsWatcher);
31
32 this.lastThread = {
33 body: {},
34 id: -1,
35 timestamp: 0,
36 };
37
38 this.setUpHandlers();
39 }
40
41 setUpHandlers() {
42 window.addEventListener(kViewThreadResponse, e => {
43 if (e.detail.id < this.lastThread.id) return;
44
45 this.lastThread = {
46 body: e.detail.body,
47 id: e.detail.id,
48 timestamp: Date.now(),
49 };
50 });
51 }
52
53 injectAbuseChipsAtProfileIfEnabled(card) {
54 this.profileAbuse.injectIfEnabled({card});
55 }
56
57 injectAtThreadListIfEnabled(li) {
58 const injectionDetails = this.threadList.getInjectionDetails(li);
59 this.threadList.injectIfEnabled(injectionDetails);
60 }
61
62 injectAtExpandedThreadListIfEnabled(toolbelt) {
63 const injectionDetails =
64 this.expandedThreadList.getInjectionDetails(toolbelt);
65 this.expandedThreadList.injectIfEnabled(injectionDetails);
66 }
67
68 injectPerForumStatsIfEnabled(chart) {
69 this.profilePerForumStats.injectIfEnabled({chart});
70 }
71
72 // Whether |feature| is enabled
73 isEnabled(feature) {
74 return this.optionsWatcher.isEnabled(feature);
75 }
76
77 /**
78 * Thread view functionality
79 */
80 injectAtQuestion(stateChips) {
81 let currentPage = parseUrl(location.href);
82 if (currentPage === false) {
83 console.error('extraInfo: couldn\'t parse current URL:', location.href);
84 return;
85 }
86
87 waitFor(
88 () => {
89 let now = Date.now();
90 let threadInfo = this.lastThread.body['1']?.['2']?.['1'];
91 if (now - this.lastThread.timestamp < 30 * 1000 &&
92 threadInfo?.['1'] == currentPage.thread &&
93 threadInfo?.['3'] == currentPage.forum)
94 return Promise.resolve(this.lastThread);
95 return Promise.reject(
96 new Error('Didn\'t receive thread information'));
97 },
98 {
99 interval: 500,
100 timeout: 30 * 1000,
101 })
102 .then(thread => {
103 const [info, tooltips] =
104 ThreadExtraInfoService.getThreadChips(thread.body?.['1']);
105 this.addExtraInfoElement(info, stateChips, false);
106 for (const tooltip of tooltips) new MDCTooltip(tooltip);
107 })
108 .catch(err => {
109 console.error(
110 'extraInfo: error while injecting question extra info: ', err);
111 });
112 }
113
114 injectAtQuestionIfEnabled(stateChips) {
115 this.isEnabled('extrainfo').then(isEnabled => {
116 if (isEnabled) return this.injectAtQuestion(stateChips);
117 });
118 }
119
120 injectAtMessage(messageNode) {
121 let currentPage = parseUrl(location.href);
122 if (currentPage === false) {
123 console.error('extraInfo: couldn\'t parse current URL:', location.href);
124 return;
125 }
126
127 let footer = messageNode.querySelector('.footer-fill');
128 if (!footer) {
129 console.error('extraInfo: message doesn\'t have a footer:', messageNode);
130 return;
131 }
132
133 const [type, index] =
134 this.getMessageInfo(this.lastThread.body, messageNode);
135 if (index == -1) {
136 console.error('extraInfo: this.getMessageInfo() returned index -1.');
137 return;
138 }
139
140 waitFor(
141 () => {
142 let now = Date.now();
143 let threadInfo = this.lastThread.body['1']?.['2']?.['1'];
144 if (now - this.lastThread.timestamp < 30 * 1000 &&
145 threadInfo?.['1'] == currentPage.thread &&
146 threadInfo?.['3'] == currentPage.forum) {
147 const message = this.getMessageByTypeAndIndex(
148 this.lastThread.body, type, index);
149 if (message) return Promise.resolve(message);
150 }
151
152 return Promise.reject(new Error(
153 'Didn\'t receive thread information (type: ' + type +
154 ', index: ' + index + ')'));
155 },
156 {
157 interval: 1000,
158 timeout: 30 * 1000,
159 })
160 .then(message => {
161 let info = [];
162
163 const endPendingStateTimestampMicros = message['1']?.['17'];
164 const [pendingStateInfo, pendingTooltip] =
165 this.getPendingStateInfo(endPendingStateTimestampMicros);
166 if (pendingStateInfo) info.push(pendingStateInfo);
167
168 const itemMetadata = message['1']?.['5'];
169 const mdInfo = ThreadExtraInfoService.getMetadataInfo(itemMetadata);
170 info.push(...mdInfo);
171
172 const liveReviewStatus = message['1']?.['36'];
173 const [liveReviewInfo, liveReviewTooltip] =
174 this.getLiveReviewStatusChip(liveReviewStatus);
175 if (liveReviewInfo) info.push(liveReviewInfo);
176
177 this.addExtraInfoElement(info, footer, true);
178 if (pendingTooltip) new MDCTooltip(pendingTooltip);
179 if (liveReviewTooltip) new MDCTooltip(liveReviewTooltip);
180 })
181 .catch(err => {
182 console.error(
183 'extraInfo: error while injecting message extra info: ', err);
184 });
185 }
186
187 injectAtMessageIfEnabled(message) {
188 this.isEnabled('extrainfo').then(isEnabled => {
189 if (isEnabled) return this.injectAtMessage(message);
190 });
191 }
192}