Files
InsightReply/extension/src/content/index.ts
2026-02-28 20:05:15 +08:00

108 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* InsightReply Content Script
* 负责解析 X (Twitter) 页面 DOM提取推文内容并注入交互按钮
*/
console.log('InsightReply Content Script Loaded');
// 1. 定义推文数据结构
interface TweetData {
id: string;
author: string;
content: string;
stats: {
replies: string;
retweets: string;
likes: string;
};
}
// 2. 提取推文内容的逻辑
const extractTweetData = (tweetElement: HTMLElement): TweetData | null => {
try {
// 根据 X 的 DOM 结构提取 (可能会随 Twitter 更新而变化)
const textElement = tweetElement.querySelector('[data-testid="tweetText"]');
const authorElement = tweetElement.querySelector('[data-testid="User-Name"]');
const linkElement = tweetElement.querySelector('time')?.parentElement as HTMLAnchorElement;
// 互动数据提取
const getStat = (testid: string) => {
const el = tweetElement.querySelector(`[data-testid="${testid}"]`);
return el?.getAttribute('aria-label') || '0';
};
if (!textElement || !authorElement || !linkElement) return null;
const tweetId = linkElement.href.split('/').pop() || '';
return {
id: tweetId,
author: authorElement.textContent || 'Unknown',
content: textElement.textContent || '',
stats: {
replies: getStat('reply'),
retweets: getStat('retweet'),
likes: getStat('like'),
}
};
} catch (e) {
console.error('Failed to extract tweet data:', e);
return null;
}
};
// 3. 注入“Insight”按钮
const injectInsightButton = (tweetElement: HTMLElement) => {
// 查找操作栏 (Actions bar)
const actionBar = tweetElement.querySelector('[role="group"]');
if (!actionBar || actionBar.querySelector('.insight-reply-btn')) return;
// 创建按钮
const btnContainer = document.createElement('div');
btnContainer.className = 'insight-reply-btn';
btnContainer.style.display = 'flex';
btnContainer.style.alignItems = 'center';
btnContainer.style.marginLeft = '12px';
btnContainer.style.cursor = 'pointer';
// 按钮内部图标 (简易版)
btnContainer.innerHTML = `
<div style="padding: 4px; border-radius: 9999px; transition: background 0.2s;" onmouseover="this.style.background='rgba(139, 92, 246, 0.1)'" onmouseout="this.style.background='transparent'">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor" style="color: #8B5CF6;">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"></path>
</svg>
</div>
`;
btnContainer.onclick = (e) => {
e.stopPropagation();
const data = extractTweetData(tweetElement);
console.log('Target Tweet Data:', data);
// 发送消息给插件侧边栏/Popup (后续实现)
if (data) {
chrome.runtime.sendMessage({ type: 'SHOW_INSIGHT', payload: data });
}
};
actionBar.appendChild(btnContainer);
};
// 4. 定时或监听 DOM 变化进行扫描
const scanTweets = () => {
const tweets = document.querySelectorAll('article[data-testid="tweet"]');
tweets.forEach((tweet) => {
injectInsightButton(tweet as HTMLElement);
});
};
// 使用 MutationObserver 监听动态加载
const observer = new MutationObserver(() => {
scanTweets();
});
observer.observe(document.body, { childList: true, subtree: true });
// 初始扫描
scanTweets();