|
265 | 265 | // Parsing strategy avoids Instagram's obfuscated class names (which rotate). |
266 | 266 | // We find comment rows by looking for profile-picture <img> elements inside |
267 | 267 | // a <section>, then walk up to the row container. The username comes from |
268 | | - // the image's alt text ("<name>'s profile picture" — stable semantic HTML), |
269 | | - // and the message is whatever text follows it in the row. |
| 268 | + // the visible username text, and the message is whatever text follows it in |
| 269 | + // the row. |
270 | 270 |
|
271 | 271 | var LIVE_COMMENT_INPUT_SELECTOR = "footer textarea, footer input, footer [contenteditable='true'], [aria-label='Add a comment…']"; |
272 | 272 | var PROFILE_IMG_SELECTOR = "img[alt*='profile picture'], img[alt*='profile photo']"; |
| 273 | + var PROFILE_IMG_FALLBACK_SELECTOR = "img[src]"; |
273 | 274 |
|
274 | 275 | var COMMENT_PERMALINK_SELECTOR = "a[href*='/c/']"; |
275 | 276 |
|
276 | 277 | function isLivePage(){ |
277 | 278 | try { |
278 | 279 | var path = window.location.pathname || ""; |
279 | 280 | if (path.includes("/live") || path.includes("%2Flive")){ return true; } |
280 | | - if (document.querySelector("svg[aria-label='Viewer count icon']")){ return true; } |
| 281 | + if (findViewerCountIcon()){ return true; } |
281 | 282 | } catch(e){} |
282 | 283 | return false; |
283 | 284 | } |
284 | 285 |
|
| 286 | + function hasViewerCountText(scope){ |
| 287 | + if (!scope || !scope.querySelectorAll){ return false; } |
| 288 | + var spans = scope.querySelectorAll("span"); |
| 289 | + for (var i = 0; i < spans.length; i++){ |
| 290 | + var text = (spans[i].textContent || "").trim(); |
| 291 | + if (/^\d[\d.,]*\s*[KM]?$/i.test(text)){ return true; } |
| 292 | + } |
| 293 | + return false; |
| 294 | + } |
| 295 | + |
| 296 | + function findViewerCountIcon(){ |
| 297 | + var icon = document.querySelector("svg[aria-label='Viewer count icon']"); |
| 298 | + if (icon){ return icon; } |
| 299 | + var svgs = document.querySelectorAll("header svg[role='img']"); |
| 300 | + for (var i = 0; i < svgs.length; i++){ |
| 301 | + var scope = svgs[i].parentElement; |
| 302 | + for (var depth = 0; scope && depth < 5; depth++){ |
| 303 | + if (hasViewerCountText(scope)){ return svgs[i]; } |
| 304 | + scope = scope.parentElement; |
| 305 | + } |
| 306 | + } |
| 307 | + return null; |
| 308 | + } |
| 309 | + |
285 | 310 | function isSsappContext(){ |
286 | 311 | try { |
287 | 312 | if (window.ninjafy || window.electronApi || window.__ssapp){ return true; } |
|
293 | 318 |
|
294 | 319 | function nameFromAlt(img){ |
295 | 320 | if (!img || !img.alt){ return ""; } |
296 | | - return (img.alt + "") |
| 321 | + var alt = (img.alt + "").trim(); |
| 322 | + var englishName = alt |
297 | 323 | .replace(/['’]s profile picture.*$/i, "") |
298 | 324 | .replace(/['’]s profile photo.*$/i, "") |
299 | 325 | .trim(); |
| 326 | + if (englishName && (englishName !== alt)){ return englishName; } |
| 327 | + |
| 328 | + var localizedName = alt |
| 329 | + .replace(/^.*\bperfil\b\s+(de|del|do|da)\s+/i, "") |
| 330 | + .replace(/^.*\bprofil\b\s+(de|du|von)\s+/i, "") |
| 331 | + .replace(/^.*\bprofile\b\s+(of)\s+/i, "") |
| 332 | + .trim(); |
| 333 | + if (localizedName && (localizedName !== alt)){ return localizedName; } |
| 334 | + return alt; |
| 335 | + } |
| 336 | + |
| 337 | + function isLikelyProfileImage(img){ |
| 338 | + if (!img || !img.src){ return false; } |
| 339 | + var alt = ((img.alt || "") + "").toLowerCase(); |
| 340 | + if ((alt.indexOf("profile picture") !== -1) || (alt.indexOf("profile photo") !== -1)){ return true; } |
| 341 | + if ((alt.indexOf("perfil") !== -1) || (alt.indexOf("profil") !== -1)){ return true; } |
| 342 | + if (img.closest && img.closest("[role='link'], a[href]")){ return true; } |
| 343 | + return false; |
| 344 | + } |
| 345 | + |
| 346 | + function getProfileImgCandidates(scope){ |
| 347 | + var candidates = []; |
| 348 | + if (!scope || !scope.querySelectorAll){ return candidates; } |
| 349 | + var imgs = scope.querySelectorAll(PROFILE_IMG_FALLBACK_SELECTOR); |
| 350 | + for (var i = 0; i < imgs.length; i++){ |
| 351 | + if (isLikelyProfileImage(imgs[i])){ candidates.push(imgs[i]); } |
| 352 | + } |
| 353 | + return candidates; |
| 354 | + } |
| 355 | + |
| 356 | + function getProfileImgFromRow(row){ |
| 357 | + if (!row || !row.querySelector){ return null; } |
| 358 | + var img = row.querySelector(PROFILE_IMG_SELECTOR); |
| 359 | + if (img){ return img; } |
| 360 | + var candidates = getProfileImgCandidates(row); |
| 361 | + if (candidates.length){ return candidates[0]; } |
| 362 | + return row.querySelector(PROFILE_IMG_FALLBACK_SELECTOR); |
| 363 | + } |
| 364 | + |
| 365 | + function findLiveUsername(row, profileImg){ |
| 366 | + var altName = nameFromAlt(profileImg); |
| 367 | + var leaves = collectTextLeaves(row); |
| 368 | + for (var i = 0; i < leaves.length; i++){ |
| 369 | + var text = normalizeLiveText(leaves[i].textContent || ""); |
| 370 | + if (!text){ continue; } |
| 371 | + if (altName && (text.toLowerCase() === altName.toLowerCase())){ return text; } |
| 372 | + if (text.length > 80){ continue; } |
| 373 | + return text; |
| 374 | + } |
| 375 | + return altName; |
300 | 376 | } |
301 | 377 |
|
302 | 378 | // Walk up from an element until we find the comment row container. |
|
319 | 395 | function looksLikeRow(node){ |
320 | 396 | if (!node || !node.querySelector){ return false; } |
321 | 397 | if (node.querySelector("header, video, footer")){ return false; } |
322 | | - var pics = node.querySelectorAll(PROFILE_IMG_SELECTOR); |
| 398 | + var pics = getProfileImgCandidates(node); |
323 | 399 | if (pics.length !== 1){ return false; } |
324 | 400 | return true; |
325 | 401 | } |
326 | 402 |
|
327 | 403 | function findLiveRows(){ |
328 | 404 | var rows = []; |
329 | 405 | var seen = new Set(); |
330 | | - var imgs = document.querySelectorAll("section " + PROFILE_IMG_SELECTOR); |
| 406 | + var imgs = document.querySelectorAll("section " + PROFILE_IMG_FALLBACK_SELECTOR); |
331 | 407 | for (var i = 0; i < imgs.length; i++){ |
332 | 408 | var row = rowFromImage(imgs[i]); |
333 | 409 | if (!row){ continue; } |
|
447 | 523 | function parseLiveRow(row){ |
448 | 524 | if (!row || !row.querySelector){ return null; } |
449 | 525 |
|
450 | | - var profileImg = row.querySelector(PROFILE_IMG_SELECTOR) || row.querySelector("img[src]"); |
| 526 | + var profileImg = getProfileImgFromRow(row) || row.querySelector("img[src]"); |
451 | 527 | var chatimg = profileImg ? (profileImg.src + "") : ""; |
452 | | - var chatname = nameFromAlt(profileImg); |
| 528 | + var chatname = findLiveUsername(row, profileImg); |
453 | 529 | var chatmessage = ""; |
454 | 530 | var streamEvent = false; |
455 | 531 | var messageLeaf = null; |
|
542 | 618 | var targetSection = null; |
543 | 619 | for (var i = 0; i < sections.length; i++) { |
544 | 620 | var section = sections[i]; |
545 | | - if (section.querySelectorAll(PROFILE_IMG_SELECTOR).length > 0) { |
| 621 | + if (getProfileImgCandidates(section).length > 0) { |
546 | 622 | targetSection = section; |
547 | 623 | break; |
548 | 624 | } |
|
599 | 675 | function checkViewers(){ |
600 | 676 | if (!(settings.showviewercount || settings.hypemode)){ return; } |
601 | 677 | try { |
602 | | - var icon = document.querySelector("svg[aria-label='Viewer count icon']"); |
| 678 | + var icon = findViewerCountIcon(); |
603 | 679 | if (!icon){ return; } |
604 | 680 | // Walk upward and look for a nearby numeric span in the header. |
605 | 681 | var scope = icon.closest("header") || icon.parentElement; |
|
739 | 815 | if (!isElementVisible(node)){ continue; } |
740 | 816 | var clickable = closestClickable(node); |
741 | 817 | if (!clickable || !isElementVisible(clickable)){ continue; } |
742 | | - if (!clickable.querySelector("img[alt*='profile picture'], img[alt*='profile photo'], canvas")){ continue; } |
| 818 | + if (!clickable.querySelector("canvas") && !getProfileImgCandidates(clickable).length){ continue; } |
743 | 819 | return clickable; |
744 | 820 | } |
745 | 821 | return null; |
|
0 commit comments