blob: 266592aa4002eb5625d652d4f3eda1652c731675 [file] [log] [blame]
Adrià Vilanova Martínez7e8796c2022-01-23 21:46:46 +01001import {MDCTooltip} from '@material/tooltip';
2import {waitFor} from 'poll-until-promise';
3
4import {isOptionEnabled} from '../../common/optionsUtils.js';
Adrià Vilanova Martínez74a25202022-01-23 23:36:58 +01005import {createPlainTooltip} from '../../common/tooltip.js';
Adrià Vilanova Martínez7e8796c2022-01-23 21:46:46 +01006
7import {createExtBadge} from './utils/common.js';
8
9const kViewUnifiedUserResponseEvent = 'TWPT_ViewUnifiedUserResponse';
Adrià Vilanova Martínez74a25202022-01-23 23:36:58 +010010const kListCannedResponsesResponse = 'TWPT_ListCannedResponsesResponse';
Adrià Vilanova Martínez7e8796c2022-01-23 21:46:46 +010011
12const kAbuseCategories = [
13 ['1', 'Account'],
14 ['2', 'Display name'],
15 ['3', 'Avatar'],
16];
17const kAbuseViolationCategories = {
18 0: 'NO_VIOLATION',
19 1: 'COMMUNITY_POLICY_VIOLATION',
20 2: 'LEGAL_VIOLATION',
21 3: 'CSAI_VIOLATION',
22 4: 'OTHER_VIOLATION',
23};
24const kAbuseViolationTypes = {
25 0: 'UNSPECIFIED',
26 23: 'ACCOUNT_DISABLED',
27 55: 'ACCOUNT_HAS_SERVICES_DISABLED',
28 35: 'ACCOUNT_HIJACKED',
29 96: 'ACCOUNT_LEAKED_CREDENTIALS',
30 92: 'ACCOUNT_NOT_SUPPORTED',
31 81: 'ARTISTIC_NUDITY',
32 66: 'BAD_BEHAVIOR_PATTERN',
33 78: 'BAD_ENGAGEMENT_BEHAVIOR_PATTERN',
34 79: 'BORDERLINE_HARASSMENT',
35 80: 'BORDERLINE_HATE_SPEECH',
36 38: 'BOTNET',
37 32: 'BRANDING_VIOLATION',
38 100: 'CAPITALIZING_TRAGIC_EVENTS',
39 105: 'CLOAKING',
40 49: 'COIN_MINING',
41 7: 'COMMERCIAL_CONTENT',
42 97: 'COPPA_REGULATED',
43 57: 'COPYRIGHT_CIRCUMVENTION',
44 8: 'COPYRIGHTED_CONTENT',
45 58: 'COURT_ORDER',
46 51: 'CSAI',
47 94: 'CSAI_INSPECT',
48 52: 'CSAI_CARTOON_HUMOR',
49 53: 'CSAI_SOLICITATION',
50 108: 'CSAI_NON_APPARENT',
51 67: 'DANGEROUS',
52 37: 'DATA_SCRAPING',
53 86: 'DECEPTIVE_OAUTH_IMPLEMENTATION',
54 46: 'DEFAMATORY_CONTENT',
55 36: 'DELINQUENT_BILLING',
56 30: 'DISRUPTION_ATTEMPT',
57 112: 'DOMESTIC_INTERFERENCE',
58 22: 'DOS',
59 9: 'DUPLICATE_CONTENT',
60 68: 'DUPLICATE_LOCAL_PAGE',
61 121: 'NON_QUALIFYING_ORGANIZATION',
62 115: 'EGREGIOUS_INTERACTION_WITH_MINOR',
63 83: 'ENGAGEMENT_COLLUSION',
64 41: 'EXPLOIT_ATTACKS',
65 65: 'FAKE_USER',
66 2: 'FRAUD',
67 21: 'FREE_TRIAL_VIOLATION',
68 43: 'GIBBERISH',
69 101: 'FOREIGN_INTERFERENCE',
70 59: 'GOVERNMENT_ORDER',
71 10: 'GRAPHICAL_VIOLENCE',
72 11: 'HARASSMENT',
73 12: 'HATE_SPEECH',
74 90: 'IDENTICAL_PRODUCT_NAME',
75 60: 'ILLEGAL_DRUGS',
76 13: 'IMPERSONATION',
77 69: 'IMPERSONATION_WITH_PII',
78 116: 'INAPPROPRIATE_INTERACTION_WITH_MINOR',
79 45: 'INAPPROPRIATE_CONTENT_SPEECH',
80 106: 'INTENTIONAL_THWARTING',
81 27: 'INTRUSION_ATTEMPT',
82 87: 'INVALID_API_USAGE',
83 14: 'INVALID_CONTENT',
84 20: 'INVALID_GCE_USAGE',
85 120: 'INVALID_STORAGE_USAGE',
86 15: 'INVALID_IMAGE_QUALITY',
87 88: 'INVALID_API_PRIVACY_POLICY_DISCLOSURE',
88 54: 'INVALID_USAGE_OF_IP_PROXYING',
89 99: 'KEYWORD_STUFFING',
90 61: 'LEGAL_COUNTERFEIT',
91 62: 'LEGAL_EXPORT',
92 63: 'LEGAL_PRIVACY',
93 33: 'LEGAL_REVIEW',
94 91: 'LEGAL_PROTECTED',
95 70: 'LOW_QUALITY_CONTENT',
96 93: 'LOW_REPUTATION_PHONE_NUMBER',
97 6: 'MALICIOUS_SOFTWARE',
98 40: 'MALWARE',
99 113: 'MISLEADING',
100 114: 'MISREP_OF_ID',
101 89: 'MEMBER_OF_ABUSIVE_GCE_NETWORK',
102 84: 'NON_CONSENSUAL_EXPLICIT_IMAGERY',
103 1: 'NONE',
104 102: 'OFF_TOPIC',
105 31: 'OPEN_PROXY',
106 28: 'PAYMENT_FRAUD',
107 16: 'PEDOPHILIA',
108 71: 'PERSONAL_INFORMATION_CONTENT',
109 25: 'PHISHING',
110 34: 'POLICY_REVIEW',
111 17: 'PORNOGRAPHY',
112 29: 'QUOTA_CIRCUMVENTION',
113 72: 'QUOTA_EXCEEDED',
114 73: 'REGULATED',
115 24: 'REPEATED_POLICY_VIOLATION',
116 104: 'RESOURCE_COMPROMISED',
117 107: 'REWARD_PROGRAMS_ABUSE',
118 74: 'ROGUE_PHARMA',
119 82: 'ESCORT',
120 75: 'SPAMMY_LOCAL_VERTICAL',
121 39: 'SEND_EMAIL_SPAM',
122 117: 'SEXTORTION',
123 118: 'SEX_TRAFFICKING',
124 44: 'SEXUALLY_EXPLICIT_CONTENT',
125 3: 'SHARDING',
126 95: 'SOCIAL_ENGINEERING',
127 109: 'SUSPICIOUS',
128 19: 'TRADEMARK_CONTENT',
129 50: 'TRAFFIC_PUMPING',
130 76: 'UNSAFE_RACY',
131 103: 'UNUSUAL_ACTIVITY_ALERT',
132 64: 'UNWANTED_CONTENT',
133 26: 'UNWANTED_SOFTWARE',
134 77: 'VIOLENT_EXTREMISM',
135 119: 'UNAUTH_IMAGES_OF_MINORS',
136 85: 'UNAUTHORIZED_SERVICE_RESELLING',
137 98: 'CSAI_EXTERNAL',
138 5: 'SPAM',
139 4: 'UNSAFE',
140 47: 'CHILD_PORNOGRAPHY_INCITATION',
141 18: 'TERRORISM_SUPPORT',
142 56: 'CSAI_WORST_OF_WORST',
143};
144
145export default class ExtraInfo {
146 constructor() {
147 this.lastProfile = {
148 body: {},
149 id: -1,
150 timestamp: 0,
151 };
Adrià Vilanova Martínez74a25202022-01-23 23:36:58 +0100152 this.lastCRsList = {
153 body: {},
154 id: -1,
155 duplicateNames: new Set(),
156 };
Adrià Vilanova Martínez7e8796c2022-01-23 21:46:46 +0100157 this.setUpHandlers();
158 }
159
160 setUpHandlers() {
161 window.addEventListener(kViewUnifiedUserResponseEvent, e => {
162 if (e.detail.id < this.lastProfile.id) return;
163
164 this.lastProfile = {
165 body: e.detail.body,
166 id: e.detail.id,
167 timestamp: Date.now(),
168 };
169 });
Adrià Vilanova Martínez74a25202022-01-23 23:36:58 +0100170 window.addEventListener(kListCannedResponsesResponse, e => {
171 if (e.detail.id < this.lastCRsList.id) return;
172
173 // Look if there are duplicate names
174 const crs = e.detail.body?.['1'] ?? [];
175 const names = crs.map(cr => cr?.['7']).slice().sort();
176 let duplicateNames = new Set();
177 for (let i = 1; i < names.length; i++)
178 if (names[i - 1] == names[i]) duplicateNames.add(names[i]);
179
180 this.lastCRsList = {
181 body: e.detail.body,
182 id: e.detail.id,
183 duplicateNames,
184 };
185 });
186 }
187
188 // Whether the feature is enabled
189 isEnabled() {
190 return isOptionEnabled('extrainfo');
Adrià Vilanova Martínez7e8796c2022-01-23 21:46:46 +0100191 }
192
193 // Add a pretty component which contains |info| to |node|.
194 addExtraInfoElement(info, node) {
195 // Don't create
196 if (info.length == 0) return;
197
198 let container = document.createElement('div');
199 container.classList.add('TWPT-extrainfo-container');
200
201 let badgeCell = document.createElement('div');
202 badgeCell.classList.add('TWPT-extrainfo-badge-cell');
203
Adrià Vilanova Martínez74a25202022-01-23 23:36:58 +0100204 const [badge, badgeTooltip] = createExtBadge();
Adrià Vilanova Martínez7e8796c2022-01-23 21:46:46 +0100205 badgeCell.append(badge);
206
207 let infoCell = document.createElement('div');
208 infoCell.classList.add('TWPT-extrainfo-info-cell');
209
210 for (const i of info) {
211 let iRow = document.createElement('div');
212 iRow.append(i);
213 infoCell.append(iRow);
214 }
215
216 container.append(badgeCell, infoCell);
217 node.append(container);
218 new MDCTooltip(badgeTooltip);
219 }
220
221 fieldInfo(field, value) {
222 let span = document.createElement('span');
223 span.append(document.createTextNode(field + ': '));
224
225 let valueEl = document.createElement('span');
226 valueEl.style.fontFamily = 'monospace';
227 valueEl.textContent = value;
228
229 span.append(valueEl);
230 return span;
231 }
232
233 // Profile functionality
234 injectAtProfile(card) {
235 waitFor(
236 () => {
237 let now = Date.now();
238 if (now - this.lastProfile.timestamp < 15 * 1000)
239 return Promise.resolve(this.lastProfile);
240 return Promise.reject('Didn\'t receive profile information');
241 },
242 {
243 interval: 500,
244 timeout: 15 * 1000,
245 })
246 .then(profile => {
247 let info = [];
248 const abuseViolationCategory = profile.body?.['1']?.['6'];
249 if (abuseViolationCategory) {
250 info.push(this.fieldInfo(
251 'Abuse category',
252 kAbuseViolationCategories[abuseViolationCategory] ??
253 abuseViolationCategory));
254 }
255
256 const profileAbuse = profile.body?.['1']?.['1']?.['8'];
257
258 for (const [index, category] of kAbuseCategories) {
259 const violation = profileAbuse?.[index]?.['1']?.['1'];
260 if (violation) {
261 info.push(this.fieldInfo(
262 category + ' policy violation',
263 kAbuseViolationTypes[violation]));
264 }
265 }
266
267 const appealCount = profileAbuse?.['4'];
268 if (appealCount !== undefined)
269 info.push(this.fieldInfo('Number of appeals', appealCount));
270
271 this.addExtraInfoElement(info, card);
272 })
273 .catch(err => {
274 console.error(
275 'extraInfo: error while injecting profile extra info: ', err);
276 });
277 }
278
279 injectAtProfileIfEnabled(card) {
Adrià Vilanova Martínez74a25202022-01-23 23:36:58 +0100280 this.isEnabled().then(isEnabled => {
Adrià Vilanova Martínez7e8796c2022-01-23 21:46:46 +0100281 if (isEnabled) return this.injectAtProfile(card);
282 });
283 }
Adrià Vilanova Martínez74a25202022-01-23 23:36:58 +0100284
285 // Canned responses (CRs) functionality
286
287 getCRName(tags, isExpanded) {
288 if (!isExpanded)
289 return tags.parentNode?.querySelector?.('.text .name')?.textContent;
290
291 // https://www.youtube.com/watch?v=Z6_ZNW1DACE
292 return tags.parentNode?.parentNode?.parentNode?.parentNode?.parentNode
293 ?.parentNode?.parentNode?.querySelector?.('.text .name')
294 ?.textContent;
295 }
296
297 // Inject usage stats in the |tags| component of a CR
298 injectAtCR(tags, isExpanded) {
299 waitFor(() => {
300 if (this.lastCRsList.id != -1) return Promise.resolve(this.lastCRsList);
301 return Promise.reject('Didn\'t receive canned responses list');
302 }, {
303 interval: 500,
304 timeout: 15 * 1000,
305 }).then(crs => {
306 let name = this.getCRName(tags, isExpanded);
307
308 // If another CR has the same name, there's no easy way to distinguish
309 // them, so don't show the usage stats.
310 if (crs.duplicateNames.has(name)) return;
311
312 for (const cr of (crs.body?.['1'] ?? [])) {
313 if (cr['7'] == name) {
314 let tag = document.createElement('material-chip');
315 tag.classList.add('TWPT-tag');
316
317 let container = document.createElement('div');
318 container.classList.add('TWPT-chip-content-container');
319
320 let content = document.createElement('div');
321 content.classList.add('TWPT-content');
322
323 const [badge, badgeTooltip] = createExtBadge();
324
325 let label = document.createElement('span');
326 label.textContent = 'Used ' + (cr['8'] ?? '0') + ' times';
327
328 content.append(badge, label);
329 container.append(content);
330 tag.append(container);
331 tags.append(tag);
332
333 new MDCTooltip(badgeTooltip);
334
335 if (cr['9']) {
336 const lastUsedTime = Math.floor(parseInt(cr['9']) / 1e3);
337 let date = (new Date(lastUsedTime)).toLocaleString();
338 createPlainTooltip(label, 'Last used: ' + date);
339 }
340
341 break;
342 }
343 }
344 });
345 }
346
347 injectAtCRIfEnabled(tags, isExpanded) {
348 // If the tag has already been injected, exit.
349 if (tags.querySelector('.TWPT-tag')) return;
350
351 this.isEnabled().then(isEnabled => {
352 if (isEnabled) return this.injectAtCR(tags, isExpanded);
353 });
354 }
Adrià Vilanova Martínez7e8796c2022-01-23 21:46:46 +0100355}