// Content Script - runs on X.com
// Adds "Save to Hookd" option to tweet/post menu
console.log('Hookd content script loaded');
// Listen for messages from background
chrome.runtime.onMessage.addListener((msg, sender, response) => {
if (msg.type === 'GET_SAVED') {
response([]);
}
});
// Add "Save to Hookd" to X's menu
function addHookdOption() {
// Find all "more" buttons (the ... button on tweets/posts)
document.querySelectorAll('[data-testid="tweet"] [role="button"], [data-testid="tweet"] [aria-label*="more"], [data-testid="tweet"] [aria-label*="More"]').forEach(menuBtn => {
// Check if we already added our option
if (menuBtn.closest('[data-testid="tweet"]')?.querySelector('.hookd-menu-option')) continue;
const tweet = menuBtn.closest('[data-testid="tweet"]');
if (!tweet) return;
// Create save button
const saveBtn = document.createElement('div');
saveBtn.className = 'hookd-menu-option';
saveBtn.style.cssText = `
padding: 12px 16px;
cursor: pointer;
display: flex;
align-items: center;
gap: 12px;
font-size: 15px;
color: #fff;
`;
saveBtn.innerHTML = '📌Save to Hookd';
saveBtn.addEventListener('click', (e) => {
e.stopPropagation();
saveTweet(tweet);
});
// Find the dropdown menu and append
const dropdown = menuBtn.closest('[role="menu"]') || menuBtn.closest('div[aria-labelledby]');
if (dropdown && !dropdown.querySelector('.hookd-menu-option')) {
dropdown.appendChild(saveBtn);
}
});
// Also try to find DM more options
document.querySelectorAll('[data-testid="DMMessage"] [role="button"]').forEach(btn => {
if (btn.textContent.includes('more') || btn.getAttribute('aria-label')?.includes('more')) {
const dm = btn.closest('[data-testid="DMMessage"]');
if (dm && !dm.querySelector('.hookd-menu-option')) {
const saveBtn = document.createElement('div');
saveBtn.className = 'hookd-menu-option';
saveBtn.style.cssText = `
padding: 8px 12px;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
`;
saveBtn.innerHTML = '📌 Save to Hookd';
saveBtn.addEventListener('click', (e) => {
e.stopPropagation();
saveDM(dm);
});
// Insert after the clickable area
btn.parentElement?.appendChild(saveBtn);
}
}
});
}
// Save tweet
function saveTweet(tweetElement) {
try {
// Extract author
const authorEl = tweetElement.querySelector('[data-testid="User-Name"] span a[role="link"]') ||
tweetElement.querySelector('a[tabindex="-1"]');
const author = authorEl?.textContent?.trim()?.replace('@', '') || 'unknown';
// Extract content
const contentEl = tweetElement.querySelector('[data-testid="tweetText"]');
const content = contentEl?.textContent?.trim() || '';
// Extract link
const linkEl = tweetElement.querySelector('a[href*="/status/"]');
const link = linkEl?.href || '';
const item = {
id: Date.now(),
author: author,
content: content.substring(0, 500),
link: link,
source: 'tweet',
time: new Date().toISOString()
};
// Save to storage via background
chrome.runtime.sendMessage({
type: 'SAVE_ITEM',
data: item
}, (response) => {
if (response?.success) {
showNotification('Saved to Hookd!');
} else {
showNotification('Failed to save');
}
});
} catch (err) {
console.error('Hookd save error:', err);
showNotification('Error saving');
}
}
// Save DM
function saveDM(dmElement) {
try {
const contentEl = dmElement.querySelector('[data-testid="dmMessageContent"]');
const content = contentEl?.textContent?.trim() || '';
const item = {
id: Date.now(),
author: 'DM',
content: content.substring(0, 500),
source: 'dm',
time: new Date().toISOString()
};
chrome.runtime.sendMessage({
type: 'SAVE_ITEM',
data: item
}, (response) => {
if (response?.success) {
showNotification('Saved to Hookd!');
} else {
showNotification('Failed to save');
}
});
} catch (err) {
console.error('Hookd save error:', err);
}
}
// Show notification
function showNotification(message) {
const notif = document.createElement('div');
notif.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
background: #4F46E5;
color: white;
padding: 12px 20px;
border-radius: 8px;
font-family: Arial, sans-serif;
font-size: 14px;
z-index: 99999;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
`;
notif.textContent = message;
document.body.appendChild(notif);
setTimeout(() => {
notif.style.opacity = '0';
notif.style.transition = 'opacity 0.3s';
setTimeout(() => notif.remove(), 300);
}, 2000);
}
// Watch for new elements
const observer = new MutationObserver(() => {
addHookdOption();
});
// Start observing
observer.observe(document.body, {
childList: true,
subtree: true
});
// Initial scan
setTimeout(addHookdOption, 1000);
setTimeout(addHookdOption, 3000);
console.log('Hookd content script initialized');