From 6dcef9f3fee44322086a7966d70d69b0ef0fa2e0 Mon Sep 17 00:00:00 2001 From: zenparsing Date: Sat, 31 Aug 2024 19:56:49 -0400 Subject: [PATCH] Fix twitter publisher detection --- .../publisher/twitter/detector.ts | 121 ++++++++++++++++++ .../publisher/twitter/publisherInfo.ts | 26 +++- 2 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 scripts/brave_rewards/publisher/twitter/detector.ts diff --git a/scripts/brave_rewards/publisher/twitter/detector.ts b/scripts/brave_rewards/publisher/twitter/detector.ts new file mode 100644 index 0000000..1d29f7f --- /dev/null +++ b/scripts/brave_rewards/publisher/twitter/detector.ts @@ -0,0 +1,121 @@ +export const scriptText = ` + +(async function() { + + async function pollFor(fn, opts) { + const startTime = Date.now() + while (Date.now() - startTime < opts.timeout) { + const result = fn() + if (result) { + return result + } + await new Promise((resolve) => setTimeout(resolve, opts.interval)) + } + console.log('Polling timeout occurred') + return null + } + + function getElementStore(elem) { + if (!elem) { + return null + } + for (const name of Object.getOwnPropertyNames(elem)) { + if (name.startsWith('__reactProps$')) { + let store = null + try { store = elem[name].children.props.store } + catch {} + if (store && typeof store.getState === 'function') { + return store + } + } + } + return null + } + + function findStore(elem, depth = 0) { + if (!elem) { + return null + } + let store = getElementStore(elem) + if (store) { + return store + } + if (depth === 4) { + return null + } + for (let child of elem.children) { + store = findStore(child, depth + 1) + if (store) { + return store + } + } + return null + } + + let stateStore = null + + function getStore() { + if (!stateStore) { + stateStore = findStore(document.getElementById('react-root')) + } + return stateStore + } + + function getUserFromState(state, screenName) { + const userEntities = state.entities.users.entities + for (let [key, value] of Object.entries(userEntities)) { + if (value.screen_name.toLocaleLowerCase() === + screenName.toLocaleLowerCase()) { + return { + siteID: key, + imageURL: String(value.profile_image_url_https || '') + } + } + } + return null + } + + function getUserByScreenName(screenName) { + const store = getStore() + if (!store) { + return null + } + try { + return getUserFromState(store.getState(), screenName) + } catch (e) { + console.error('Error attempting to get user state', e) + } + return null + } + + function getScreenNameFromPath(path) { + let match = path.match(/^\\/([^\\/]+)(\\/|\\/status\\/[\\s\\S]+)?$/) + if (match) { + return match[1] + } + return null + } + + function getUserFromPath(path) { + const screenName = getScreenNameFromPath(path) + if (screenName) { + const user = getUserByScreenName(screenName) + if (user) { + return user + } + } + return null + } + + const user = await pollFor(() => getUserFromPath(location.pathname), { + interval: 600, + timeout: 8000 + }) + + document.dispatchEvent(new CustomEvent('rewards-publisher-detected', { + detail: { user } + })) + +})() + +` diff --git a/scripts/brave_rewards/publisher/twitter/publisherInfo.ts b/scripts/brave_rewards/publisher/twitter/publisherInfo.ts index ada03ba..3b9c424 100644 --- a/scripts/brave_rewards/publisher/twitter/publisherInfo.ts +++ b/scripts/brave_rewards/publisher/twitter/publisherInfo.ts @@ -7,7 +7,7 @@ import { getPort } from '../common/messaging' import * as commonUtils from '../common/utils' -import * as api from './api' +import * as detector from './detector' import * as types from './types' import * as utils from './utils' @@ -71,6 +71,28 @@ const sendForExcludedPage = () => { }) } +const injectDetectionScript = () => { + return new Promise((resolve, reject) => { + const script = document.createElement('script') + script.textContent = detector.scriptText + document.head.appendChild(script) + const listener = (event: CustomEvent) => { + const { user } = event.detail + if (!user) { + reject(new Error('Unable to find user data in state store')) + return + } + resolve({ + id_str: user.siteID, + profile_image_url_https: user.imageURL + }) + document.removeEventListener('rewards-publisher-detected', listener) + } + document.addEventListener('rewards-publisher-detected', listener) + document.head.removeChild(script) + }) +} + const sendForStandardPage = (url: URL) => { const screenName = utils.getScreenNameFromUrl(url) if (!screenName) { @@ -83,7 +105,7 @@ const sendForStandardPage = (url: URL) => { return } - api.getUserDetails(screenName) + injectDetectionScript() .then((userDetails: any) => { userCache.put(screenName, userDetails) savePublisherVisit(screenName, userDetails)