Adrià Vilanova Martínez | 7e8796c | 2022-01-23 21:46:46 +0100 | [diff] [blame] | 1 | import {MDCTooltip} from '@material/tooltip'; |
| 2 | import {waitFor} from 'poll-until-promise'; |
| 3 | |
| 4 | import {isOptionEnabled} from '../../common/optionsUtils.js'; |
Adrià Vilanova Martínez | 74a2520 | 2022-01-23 23:36:58 +0100 | [diff] [blame^] | 5 | import {createPlainTooltip} from '../../common/tooltip.js'; |
Adrià Vilanova Martínez | 7e8796c | 2022-01-23 21:46:46 +0100 | [diff] [blame] | 6 | |
| 7 | import {createExtBadge} from './utils/common.js'; |
| 8 | |
| 9 | const kViewUnifiedUserResponseEvent = 'TWPT_ViewUnifiedUserResponse'; |
Adrià Vilanova Martínez | 74a2520 | 2022-01-23 23:36:58 +0100 | [diff] [blame^] | 10 | const kListCannedResponsesResponse = 'TWPT_ListCannedResponsesResponse'; |
Adrià Vilanova Martínez | 7e8796c | 2022-01-23 21:46:46 +0100 | [diff] [blame] | 11 | |
| 12 | const kAbuseCategories = [ |
| 13 | ['1', 'Account'], |
| 14 | ['2', 'Display name'], |
| 15 | ['3', 'Avatar'], |
| 16 | ]; |
| 17 | const kAbuseViolationCategories = { |
| 18 | 0: 'NO_VIOLATION', |
| 19 | 1: 'COMMUNITY_POLICY_VIOLATION', |
| 20 | 2: 'LEGAL_VIOLATION', |
| 21 | 3: 'CSAI_VIOLATION', |
| 22 | 4: 'OTHER_VIOLATION', |
| 23 | }; |
| 24 | const 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 | |
| 145 | export default class ExtraInfo { |
| 146 | constructor() { |
| 147 | this.lastProfile = { |
| 148 | body: {}, |
| 149 | id: -1, |
| 150 | timestamp: 0, |
| 151 | }; |
Adrià Vilanova Martínez | 74a2520 | 2022-01-23 23:36:58 +0100 | [diff] [blame^] | 152 | this.lastCRsList = { |
| 153 | body: {}, |
| 154 | id: -1, |
| 155 | duplicateNames: new Set(), |
| 156 | }; |
Adrià Vilanova Martínez | 7e8796c | 2022-01-23 21:46:46 +0100 | [diff] [blame] | 157 | 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ínez | 74a2520 | 2022-01-23 23:36:58 +0100 | [diff] [blame^] | 170 | 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ínez | 7e8796c | 2022-01-23 21:46:46 +0100 | [diff] [blame] | 191 | } |
| 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ínez | 74a2520 | 2022-01-23 23:36:58 +0100 | [diff] [blame^] | 204 | const [badge, badgeTooltip] = createExtBadge(); |
Adrià Vilanova Martínez | 7e8796c | 2022-01-23 21:46:46 +0100 | [diff] [blame] | 205 | 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ínez | 74a2520 | 2022-01-23 23:36:58 +0100 | [diff] [blame^] | 280 | this.isEnabled().then(isEnabled => { |
Adrià Vilanova Martínez | 7e8796c | 2022-01-23 21:46:46 +0100 | [diff] [blame] | 281 | if (isEnabled) return this.injectAtProfile(card); |
| 282 | }); |
| 283 | } |
Adrià Vilanova Martínez | 74a2520 | 2022-01-23 23:36:58 +0100 | [diff] [blame^] | 284 | |
| 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ínez | 7e8796c | 2022-01-23 21:46:46 +0100 | [diff] [blame] | 355 | } |