LinkedIn Direct Post Link

Adds a direct link icon next to each LinkedIn post to copy the post URL

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// ==UserScript==
// @name         LinkedIn Direct Post Link
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  Adds a direct link icon next to each LinkedIn post to copy the post URL
// @author       You
// @match        https://www.linkedin.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=linkedin.com
// @license      MIT
// @grant        GM_setClipboard
// @grant        GM_notification
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    console.log('🔵 LinkedIn Direct Link v3 - Script Starting...');

    let buttonsAdded = 0;

    // Comprehensive function to get the marketing URL
    function getPostUrl(postElement) {
        console.log('🔍 Searching for URL in post:', postElement);

        // Priority 1: Look for the "Copy link to post" in the menu data
        // Sometimes LinkedIn stores the marketing URL in button attributes
        const menuButtons = postElement.querySelectorAll('button[aria-label*="menu"], button[data-dropdown-trigger-id]');
        for (let btn of menuButtons) {
            console.log('Found menu button:', btn);
        }

        // Priority 2: Check all links and log them
        const allLinks = postElement.querySelectorAll('a[href]');
        console.log(`📎 Total links found: ${allLinks.length}`);

        let bestUrl = null;

        for (let link of allLinks) {
            const href = link.href;
            console.log('🔗 Link:', href);

            // Marketing URL format: /posts/username_slug-activity-ID-hash
            if (href.includes('/posts/') && href.includes('activity-') && href.includes('-', href.indexOf('activity-') + 9)) {
                const cleanUrl = href.split('?')[0];
                console.log('✅ FOUND MARKETING URL:', cleanUrl);
                return cleanUrl;
            }

            // Store feed update URL as fallback
            if (href.includes('/feed/update/') && !bestUrl) {
                bestUrl = href.split('?')[0];
            }
        }

        // Priority 3: Try to find in span or text content that might have the URL
        const spans = postElement.querySelectorAll('span[dir="ltr"]');
        for (let span of spans) {
            if (span.textContent.includes('linkedin.com/posts/')) {
                console.log('Found URL in text:', span.textContent);
            }
        }

        if (bestUrl) {
            console.log('✅ Using feed URL as fallback:', bestUrl);
            return bestUrl;
        }

        // Priority 4: Construct from data-urn
        const urn = postElement.getAttribute('data-urn');
        if (urn && urn.includes('activity')) {
            const match = urn.match(/activity[:-](\d+)/);
            if (match) {
                const url = `https://www.linkedin.com/feed/update/urn:li:activity:${match[1]}/`;
                console.log('✅ Constructed feed URL from URN:', url);
                return url;
            }
        }

        console.log('❌ No URL found for this post');
        return null;
    }

    // Create a subtle link button
    function createLinkButton(postUrl) {
        const link = document.createElement('a');
        link.className = 'linkedin-direct-link-btn';
        link.href = postUrl;
        link.innerHTML = '🔗';
        link.title = 'Copy link (click) or open in new tab (middle-click/ctrl-click)';

        // Subtle styling - smaller and less visible
        link.style.cssText = `
            position: absolute !important;
            top: 6px !important;
            right: 90px !important;
            background: rgba(0, 0, 0, 0.05) !important;
            color: rgba(0, 0, 0, 0.6) !important;
            width: 28px !important;
            height: 28px !important;
            border-radius: 50% !important;
            display: flex !important;
            align-items: center !important;
            justify-content: center !important;
            cursor: pointer !important;
            font-size: 14px !important;
            z-index: 999 !important;
            transition: all 0.2s !important;
            opacity: 0.7 !important;
            text-decoration: none !important;
        `;

        link.onmouseenter = function() {
            this.style.transform = 'scale(1.15)';
            this.style.background = 'rgba(0, 0, 0, 0.1) !important';
            this.style.opacity = '1 !important';
        };

        link.onmouseleave = function() {
            this.style.transform = 'scale(1)';
            this.style.background = 'rgba(0, 0, 0, 0.05) !important';
            this.style.opacity = '0.7 !important';
        };

        link.onclick = function(e) {
            // Allow middle-click and ctrl/cmd-click to open in new tab
            if (e.button === 1 || e.ctrlKey || e.metaKey) {
                return true; // Let the default behavior happen
            }

            // For normal left-click, copy to clipboard
            e.preventDefault();
            e.stopPropagation();

            // Copy to clipboard
            if (typeof GM_setClipboard !== 'undefined') {
                GM_setClipboard(postUrl);
                console.log('📋 Copied with GM_setClipboard:', postUrl);
            } else if (navigator.clipboard) {
                navigator.clipboard.writeText(postUrl);
                console.log('📋 Copied with navigator.clipboard:', postUrl);
            }

            // Show subtle feedback
            const original = this.innerHTML;
            this.innerHTML = '✓';
            this.style.background = '#0a66c2 !important';
            this.style.color = 'white !important';

            setTimeout(() => {
                this.innerHTML = original;
                this.style.background = 'rgba(0, 0, 0, 0.05) !important';
                this.style.color = 'rgba(0, 0, 0, 0.6) !important';
            }, 1200);
        };

        // Handle middle-click specifically
        link.onauxclick = function(e) {
            if (e.button === 1) { // Middle mouse button
                e.stopPropagation();
                window.open(postUrl, '_blank');
                e.preventDefault();
            }
        };

        return link;
    }

    // Add button to post
    function addButtonToPost(post) {
        // Skip if already has button
        if (post.querySelector('.linkedin-direct-link-btn')) {
            return;
        }

        // Make post position relative so absolute positioning works
        if (window.getComputedStyle(post).position === 'static') {
            post.style.position = 'relative';
        }

        const url = getPostUrl(post);
        if (url) {
            const button = createLinkButton(url);
            post.appendChild(button);
            buttonsAdded++;
            console.log(`✅ Button #${buttonsAdded} added to post`);
        }
    }

    // Process all posts
    function processAllPosts() {
        console.log('🔍 Searching for posts...');

        // Try multiple selectors
        const selectors = [
            '[data-urn*="activity"]',
            '.feed-shared-update-v2',
            '[data-id*="urn:li:activity"]',
            'div[class*="feed-shared-update"]'
        ];

        let allPosts = [];
        for (let selector of selectors) {
            const posts = document.querySelectorAll(selector);
            if (posts.length > 0) {
                console.log(`📝 Found ${posts.length} posts with selector: ${selector}`);
                allPosts = posts;
                break;
            }
        }

        if (allPosts.length === 0) {
            console.log('⚠️ No posts found with any selector');
            return;
        }

        allPosts.forEach(post => addButtonToPost(post));
        console.log(`✨ Total buttons added: ${buttonsAdded}`);
    }

    // Initialize
    function init() {
        console.log('🚀 Initializing script...');

        // Wait a bit for page to load
        setTimeout(() => {
            console.log('⏰ Running initial scan...');
            processAllPosts();
        }, 3000);

        // Watch for new posts
        const observer = new MutationObserver(() => {
            processAllPosts();
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        console.log('👀 Observer started, watching for new posts');

        // Also check on scroll
        let scrollTimer;
        window.addEventListener('scroll', () => {
            clearTimeout(scrollTimer);
            scrollTimer = setTimeout(processAllPosts, 1000);
        });
    }

    // Start
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    console.log('✅ Script setup complete');

})();