diff --git a/src/social-network-provider/twitter.com/ui/fetch.ts b/src/social-network-provider/twitter.com/ui/fetch.ts index 902b9049c8cd..f3faf8510810 100644 --- a/src/social-network-provider/twitter.com/ui/fetch.ts +++ b/src/social-network-provider/twitter.com/ui/fetch.ts @@ -8,7 +8,7 @@ import { } from '../../../social-network/ui' import { deconstructPayload } from '../../../utils/type-transform/Payload' import { instanceOfTwitterUI } from './index' -import { bioCardParser, postParser, postImageParser } from '../utils/fetch' +import { bioCardParser, postParser, postImageParser, postIdParser } from '../utils/fetch' import { isNil } from 'lodash-es' import Services from '../../../extension/service' import { twitterUrl } from '../utils/url' @@ -86,15 +86,19 @@ const registerUserCollector = () => { } const registerPostCollector = (self: SocialNetworkUI) => { + const getTweetNode = (node: HTMLElement) => { + return node.closest( + [ + '.tweet', // timeline page for legacy twitter + '.main-tweet', // detail page for legacy twitter + 'article > div', // new twitter + ].join(), + ) + } + new MutationObserverWatcher(postsContentSelector()) .useForeach((node, _, proxy) => { - const tweetNode = node.closest( - [ - '.tweet', // timeline page for legacy twitter - '.main-tweet', // detail page for legacy twitter - 'article > div', // new twitter - ].join(), - ) + const tweetNode = getTweetNode(node) if (!tweetNode) return // noinspection JSUnnecessarySemicolon const info = getEmptyPostInfoByElement({ @@ -142,7 +146,10 @@ const registerPostCollector = (self: SocialNetworkUI) => { } }) .setDOMProxyOption({ afterShadowRootInit: { mode: 'closed' } }) - .assignKeys(node => node.innerHTML) + .assignKeys(node => { + const tweetNode = getTweetNode(node) + return tweetNode ? `${postIdParser(tweetNode)}${node.innerHTML}` : node.innerHTML + }) .startWatch({ childList: true, subtree: true, diff --git a/src/social-network-provider/twitter.com/utils/fetch.ts b/src/social-network-provider/twitter.com/utils/fetch.ts index c7b3393a2c7b..0506a55a9a22 100644 --- a/src/social-network-provider/twitter.com/utils/fetch.ts +++ b/src/social-network-provider/twitter.com/utils/fetch.ts @@ -22,10 +22,14 @@ const parseNameArea = (t: string) => { } } -const parsePid = (t: string) => { +const parseId = (t: string) => { return regexMatch(t, /status\/(\d+)/, 1)! } +const isMobilePost = (node: HTMLElement) => { + return node.classList.contains('tweet') ?? node.classList.contains('main-tweet') +} + export const bioCardParser = (cardNode: HTMLDivElement) => { if (cardNode.classList.contains('profile')) { const avatarElement = cardNode.querySelector('.avatar img') @@ -67,8 +71,41 @@ export const bioCardParser = (cardNode: HTMLDivElement) => { } } +export const postIdParser = (node: HTMLElement) => { + if (isMobilePost(node)) { + const idNode = node.querySelector('.tweet-text') + return idNode ? idNode.getAttribute('data-id') ?? undefined : undefined + } else { + const idNode = defaultTo( + node.children[1].querySelector('a[href*="status"]'), + node.parentElement!.querySelector('a[href*="status"]'), + ) + return idNode ? parseId(idNode.href) : undefined + } +} + +export const postNameParser = (node: HTMLElement) => { + if (isMobilePost(node)) { + return parseNameArea(notNullable(node.querySelector('.user-info')).innerText) + } else { + const tweetElement = node.querySelector('[data-testid="tweet"]') ?? node + return parseNameArea(notNullable(tweetElement.children[1].querySelector('a')).innerText) + } +} + +export const postAvatarParser = (node: HTMLElement) => { + if (isMobilePost(node)) { + const avatarElement = node.querySelector('.avatar img') + return avatarElement ? avatarElement.src : undefined + } else { + const tweetElement = node.querySelector('[data-testid="tweet"]') ?? node + const avatarElement = tweetElement.children[0].querySelector(`img[src*="twimg.com"]`) + return avatarElement ? avatarElement.src : undefined + } +} + export const postContentParser = (node: HTMLElement) => { - if (node.classList.contains('tweet') || node.classList.contains('main-tweet')) { + if (isMobilePost(node)) { const containerNode = node.querySelector('.tweet-text > div') if (!containerNode) { return '' @@ -96,18 +133,15 @@ export const postContentParser = (node: HTMLElement) => { } export const postImageParser = async (node: HTMLElement) => { - if (node.classList.contains('tweet') || node.classList.contains('main-tweet')) { + if (isMobilePost(node)) { // TODO: Support steganography in legacy twitter return '' } else { const imgNodes = node.querySelectorAll('img[src*="twimg.com/media"]') if (!imgNodes.length) return '' - const imgUrls = Array.from(imgNodes).map(node => node.getAttribute('src') || '') + const imgUrls = Array.from(imgNodes).map(node => node.getAttribute('src') ?? '') if (!imgUrls.length) return '' - const tweetElement = node.querySelector('[data-testid="tweet"]') || node - const { handle } = parseNameArea( - notNullable(tweetElement.children[1].querySelector('a')).innerText, - ) + const { handle } = postNameParser(node) const posterIdentity = new PersonIdentifier(twitterUrl.hostIdentifier, handle) return ( await Promise.all( @@ -126,36 +160,10 @@ export const postImageParser = async (node: HTMLElement) => { } export const postParser = (node: HTMLElement) => { - if (node.classList.contains('tweet') || node.classList.contains('main-tweet')) { - const { name, handle } = parseNameArea( - notNullable(node.querySelector('.user-info')).innerText, - ) - const avatarElement = node.querySelector('.avatar img') - const pidLocation = node.querySelector('.tweet-text') - return { - name, - handle, - pid: pidLocation ? pidLocation.getAttribute('data-id') : undefined, - avatar: avatarElement ? avatarElement.src : undefined, - content: postContentParser(node), - } - } else { - const tweetElement = node.querySelector('[data-testid="tweet"]') || node - const { name, handle } = parseNameArea( - notNullable(tweetElement.children[1].querySelector('a')).innerText, - ) - const avatarElement = tweetElement.children[0].querySelector(`img[src*="twimg.com"]`) - const pidLocation = defaultTo( - node.children[1].querySelector('a[href*="status"]'), - node.parentElement!.querySelector('a[href*="status"]'), - ) - return { - name, - handle, - // pid may not available at promoted tweet - pid: pidLocation ? parsePid(pidLocation.href) : undefined, - avatar: avatarElement ? avatarElement.src : undefined, - content: postContentParser(node), - } + return { + ...postNameParser(node), + avatar: postAvatarParser(node), + pid: postIdParser(node), + content: postContentParser(node), } }