diff --git a/backend/linkedin_service.py b/backend/linkedin_service.py index 70318f5..d4dc04b 100644 --- a/backend/linkedin_service.py +++ b/backend/linkedin_service.py @@ -6,8 +6,11 @@ async def scrape_user(username : str): # Initialize browser async with BrowserManager(headless=False) as browser: - # Load authenticated session - await browser.load_session("playwright/session.json") + # Load authenticated session if it exists + import os + session_path = os.path.join(os.path.dirname(__file__), "playwright", "session.json") + if os.path.exists(session_path): + await browser.load_session(session_path) # Create scraper scraper = PersonScraper(browser.page) @@ -58,7 +61,10 @@ async def scrape_user(username : str): async def scrape_company(company : str): async with BrowserManager(headless=False) as browser: - await browser.load_session("playwright/session.json") + import os + session_path = os.path.join(os.path.dirname(__file__), "playwright", "session.json") + if os.path.exists(session_path): + await browser.load_session(session_path) scraper = CompanyScraper(browser.page) company = await scraper.scrape(f"https://linkedin.com/company/{company}/") @@ -88,9 +94,6 @@ async def scrape_company(company : str): return json.dumps(returned_data) - -#response = asyncio.run(scrape_company("rogers-communications")) -#print(response) -#response = asyncio.run(scrape_user("dayuhechen")) +#response = asyncio.run(scrape_user("aathithy-aananth")) #print(response) -#asyncio.run(scrape_user("dayuhechen")) +#asyncio.run(scrape_user("aathithya-ananth")) diff --git a/backend/yoshixi.txt b/backend/yoshixi.txt deleted file mode 100644 index 0c6bcf0..0000000 --- a/backend/yoshixi.txt +++ /dev/null @@ -1,42 +0,0 @@ -https://www.artstation.com/yoshixi -https://atcoder.jp/users/yoshixi -https://bsky.app/profile/yoshixi.bsky.social -https://cash.app/$yoshixi -https://www.chess.com/member/yoshixi -https://cracked.sh/yoshixi -https://dmoj.ca/user/yoshixi -https://discord.com -https://disqus.com/yoshixi -https://hub.docker.com/u/yoshixi/ -https://www.duolingo.com/profile/yoshixi -https://forums.envato.com/u/yoshixi -https://freesound.org/people/yoshixi/ -https://www.github.com/yoshixi -https://gitlab.com/yoshixi -https://hackerrank.com/yoshixi -https://yoshixi.itch.io/ -https://kick.com/yoshixi -https://www.lesswrong.com/users/yoshixi -https://medium.com/@yoshixi -https://myanimelist.net/profile/yoshixi -https://pastebin.com/u/yoshixi -https://pokemonshowdown.com/users/yoshixi -https://replit.com/@yoshixi -https://www.roblox.com/user.aspx?username=yoshixi -https://scratch.mit.edu/users/yoshixi -https://sketchfab.com/yoshixi -https://yoshixi.slack.com -https://www.snapchat.com/add/yoshixi -https://speakerdeck.com/yoshixi -https://www.tiktok.com/@yoshixi -https://t.me/yoshixi -https://data.typeracer.com/pit/profile?user=yoshixi -https://www.wattpad.com/user/yoshixi -http://www.wikidot.com/user:info/yoshixi -https://en.wikipedia.org/wiki/Special:CentralAuth/yoshixi?uselang=qqx -https://www.youtube.com/@yoshixi -https://last.fm/user/yoshixi -https://note.com/yoshixi -https://osu.ppy.sh/users/yoshixi -https://www.pinterest.com/yoshixi/ -Total Websites Username Detected On : 41 diff --git a/src/components/DetectiveBoard.tsx b/src/components/DetectiveBoard.tsx index dce97f9..8a2a354 100644 --- a/src/components/DetectiveBoard.tsx +++ b/src/components/DetectiveBoard.tsx @@ -323,6 +323,7 @@ function AssetTracker() { export function DetectiveBoard() { const [editor, setEditor] = useState(null) const [isSearching, setIsSearching] = useState(false) + const [isLinkedInSearching, setIsLinkedInSearching] = useState(false) const [isSherlockSearching, setIsSherlockSearching] = useState(false) const [leadBranchingFactor, setLeadBranchingFactor] = useState(2) const [isPanelExpanded, setIsPanelExpanded] = useState(true) @@ -640,6 +641,12 @@ export function DetectiveBoard() { const mainNotePosition = {x:0, y:0} function handleTextUpload(inputstr : string) { + const noteCardWidth = 180 + const noteCardHeight = 180 + const viewport = editor.getViewportPageBounds() + const centerX = viewport.center.x - noteCardWidth / 2 + const centerY = viewport.center.y - noteCardHeight / 2 + if (!editor) return const original_id = createShapeId() editor.createShape({ @@ -860,6 +867,91 @@ export function DetectiveBoard() { resetTimeout() } + + const handleLinkedInSearch = useCallback(async (username: string) => { + if (!editor) return + + setIsLinkedInSearching(true) + + try { + // Call the LinkedIn API endpoint + const response = await fetch(`http://localhost:5000/api/linkedin_photo_pins/${username}`) + const data = await response.json() + + if (data.error) { + console.error('LinkedIn search error:', data.error) + alert(`Error: ${data.error}`) + return + } + + // Calculate base position for photo pins + const allShapes = editor.getCurrentPageShapes() + const existingPhotoPins = allShapes.filter((shape: any) => shape.type === 'photo-pin') + + let baseX = 300 + let baseY = 300 + + if (existingPhotoPins.length > 0) { + // Offset from the last photo pin + const lastPin = existingPhotoPins[existingPhotoPins.length - 1] + baseX = lastPin.x + 250 + baseY = lastPin.y + } + + const pinWidth = 200 + const pinHeight = 200 + const horizontalSpacing = 250 + + // Create photo pins for each logo + data.photo_pins.forEach((pin: any, index: number) => { + const pinX = baseX + (index * horizontalSpacing) + const pinY = baseY + + // Find non-colliding position + const position = findNonCollidingPosition( + editor, + pinX, + pinY, + pinWidth, + pinHeight, + 80 + ) + + editor.createShape({ + id: createShapeId(), + type: 'photo-pin', + x: position.x, + y: position.y, + props: { + w: pinWidth, + h: pinHeight, + imageUrl: pin.imageUrl, + caption: pin.caption, + }, + }) + }) + + // Optionally, create a note with profile info + if (data.profile && data.profile.name) { + editor.createShape({ + id: createShapeId(), + type: 'note-card', + x: baseX, + y: baseY - 150, + props: { + text: `${data.profile.name}\n${data.profile.location || ''}\n${data.profile.linkedin_url || ''}`, + }, + }) + } + + } catch (error) { + console.error('Error fetching LinkedIn data:', error) + alert('Failed to fetch LinkedIn data. Make sure the backend server is running.') + } finally { + setIsLinkedInSearching(false) + } + }, [editor]) + const handleImageUpload = useCallback(async (file: File | string) => { if (!editor) return @@ -1187,7 +1279,9 @@ export function DetectiveBoard() { {/* Lead Branching Factor Slider */} diff --git a/src/components/SearchPanel.css b/src/components/SearchPanel.css index 73fff13..7d8cb22 100644 --- a/src/components/SearchPanel.css +++ b/src/components/SearchPanel.css @@ -165,3 +165,67 @@ .search-tips li { margin-bottom: 6px; } + +.text-search-form { + display: flex; + gap: 8px; + margin-bottom: 20px; +} + +.text-search-input { + flex: 1; + padding: 10px 12px; + border: 2px solid rgba(139, 69, 19, 0.3); + border-radius: 6px; + font-size: 14px; + background: white; + color: #333; + transition: border-color 0.2s; +} + +.text-search-input:focus { + outline: none; + border-color: #8b4513; +} + +.text-search-input:disabled { + background: #f5f5f5; + cursor: not-allowed; +} + +.text-search-input::placeholder { + color: #999; +} + +.text-search-button { + padding: 10px 20px; + background: #8b4513; + color: white; + border: none; + border-radius: 6px; + cursor: pointer; + font-weight: 600; + font-size: 14px; + transition: background 0.2s; + white-space: nowrap; +} + +.text-search-button:hover:not(:disabled) { + background: #654321; +} + +.text-search-button:disabled { + background: #ccc; + cursor: not-allowed; +} + +.search-panel h3 { + margin: 20px 0 12px 0; + font-size: 16px; + color: #8b4513; + font-weight: 600; +} + +.search-panel h3:first-of-type { + margin-top: 0; +} diff --git a/src/components/SearchPanel.tsx b/src/components/SearchPanel.tsx index 89bd04f..467d39e 100644 --- a/src/components/SearchPanel.tsx +++ b/src/components/SearchPanel.tsx @@ -6,10 +6,12 @@ interface SearchPanelProps { isSearching: boolean onTextSearch: (searchTerm: string) => void onExpandChange?: (isExpanded: boolean) => void + onLinkedInSearch?: (isExpanded: boolean) => void + isLinkedInSearching: boolean } -export function SearchPanel({ onImageUpload, isSearching, onTextSearch, onExpandChange }: SearchPanelProps) { +export function SearchPanel({ onImageUpload, isSearching, onTextSearch, onExpandChange, onLinkedInSearch, isLinkedInSearching }: SearchPanelProps) { const [dragActive, setDragActive] = useState(false) const [isExpanded, setIsExpanded] = useState(true) @@ -89,6 +91,39 @@ export function SearchPanel({ onImageUpload, isSearching, onTextSearch, onExpand +

+ LinkedIn Profile Search +

+
+
{ + e.preventDefault(); + const input = e.currentTarget.querySelector('input[name="linkedin-username"]') as HTMLInputElement; + if (input && input.value.trim()) { + onLinkedInSearch(input.value.trim()) + input.value = '' + } + }} + className="text-search-form" + > + + +
+
+