Skip to content

Commit

Permalink
fix: distinguish tweets with post id
Browse files Browse the repository at this point in the history
  • Loading branch information
guanbinrui committed Nov 27, 2019
1 parent b36fd65 commit 863ebe9
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 48 deletions.
25 changes: 16 additions & 9 deletions src/social-network-provider/twitter.com/ui/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -86,15 +86,19 @@ const registerUserCollector = () => {
}

const registerPostCollector = (self: SocialNetworkUI) => {
const getTweetNode = (node: HTMLElement) => {
return node.closest<HTMLDivElement>(
[
'.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<HTMLDivElement>(
[
'.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({
Expand Down Expand Up @@ -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,
Expand Down
86 changes: 47 additions & 39 deletions src/social-network-provider/twitter.com/utils/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLImageElement>('.avatar img')
Expand Down Expand Up @@ -67,8 +71,41 @@ export const bioCardParser = (cardNode: HTMLDivElement) => {
}
}

export const postIdParser = (node: HTMLElement) => {
if (isMobilePost(node)) {
const idNode = node.querySelector<HTMLAnchorElement>('.tweet-text')
return idNode ? idNode.getAttribute('data-id') ?? undefined : undefined
} else {
const idNode = defaultTo(
node.children[1].querySelector<HTMLAnchorElement>('a[href*="status"]'),
node.parentElement!.querySelector<HTMLAnchorElement>('a[href*="status"]'),
)
return idNode ? parseId(idNode.href) : undefined
}
}

export const postNameParser = (node: HTMLElement) => {
if (isMobilePost(node)) {
return parseNameArea(notNullable(node.querySelector<HTMLTableCellElement>('.user-info')).innerText)
} else {
const tweetElement = node.querySelector('[data-testid="tweet"]') ?? node
return parseNameArea(notNullable(tweetElement.children[1].querySelector<HTMLAnchorElement>('a')).innerText)
}
}

export const postAvatarParser = (node: HTMLElement) => {
if (isMobilePost(node)) {
const avatarElement = node.querySelector<HTMLImageElement>('.avatar img')
return avatarElement ? avatarElement.src : undefined
} else {
const tweetElement = node.querySelector('[data-testid="tweet"]') ?? node
const avatarElement = tweetElement.children[0].querySelector<HTMLImageElement>(`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 ''
Expand Down Expand Up @@ -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<HTMLImageElement>('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<HTMLAnchorElement>('a')).innerText,
)
const { handle } = postNameParser(node)
const posterIdentity = new PersonIdentifier(twitterUrl.hostIdentifier, handle)
return (
await Promise.all(
Expand 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<HTMLTableCellElement>('.user-info')).innerText,
)
const avatarElement = node.querySelector<HTMLImageElement>('.avatar img')
const pidLocation = node.querySelector<HTMLAnchorElement>('.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<HTMLAnchorElement>('a')).innerText,
)
const avatarElement = tweetElement.children[0].querySelector<HTMLImageElement>(`img[src*="twimg.com"]`)
const pidLocation = defaultTo(
node.children[1].querySelector<HTMLAnchorElement>('a[href*="status"]'),
node.parentElement!.querySelector<HTMLAnchorElement>('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),
}
}

0 comments on commit 863ebe9

Please sign in to comment.