Jump to content

User:Polygnotus/Scripts/ListGenerator.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// Universal Wikipedia List Copier - Refactored for simplicity and maintainability

const CONFIG = {
    API_DELAY: 500,
    MAX_RETRIES: 3,
    BASE_URL: 'https://en.wikipedia.org',
    API_URL: 'https://en.wikipedia.org/w/api.php'
};

// ===== CORE UTILITIES =====

function addTooltip(element, text) {
    element.title = text;
}

function formatItems(items, includeUrls, baseUrl = `${CONFIG.BASE_URL}/wiki/`) {
    if (!includeUrls) return items.join('\n');
    return items.map(item => `${baseUrl}${encodeURIComponent(item.replace(/ /g, '_'))}`).join('\n');
}

async function copyToClipboardOrDownload(text, filename, statusElement) {
    const success = await tryClipboardCopy(text);
    if (!success) {
        statusElement.innerHTML = `<p>Clipboard access failed. Click the link below to download items:</p>`;
        offerTextAsDownload(text, filename, statusElement);
    }
    return success;
}

function offerTextAsDownload(text, filename, statusElement) {
    const blob = new Blob([text], {type: 'text/plain'});
    const url = URL.createObjectURL(blob);
    const downloadLink = document.createElement('a');
    Object.assign(downloadLink, {
        href: url,
        download: filename || 'wikipedia-items.txt',
        textContent: `Download ${filename || 'items'} as text file`,
        style: 'display: block; margin-top: 10px;'
    });
    statusElement.appendChild(downloadLink);
}

async function tryClipboardCopy(text) {
    if (navigator.clipboard?.writeText) {
        try {
            await navigator.clipboard.writeText(text);
            return true;
        } catch {}
    }
    
    // Fallback method
    try {
        const textarea = document.createElement('textarea');
        Object.assign(textarea.style, {
            position: 'fixed',
            left: '-999999px',
            top: '-999999px'
        });
        textarea.value = text;
        document.body.appendChild(textarea);
        textarea.focus();
        textarea.select();
        const success = document.execCommand('copy');
        document.body.removeChild(textarea);
        return success;
    } catch {
        return false;
    }
}

// ===== API UTILITIES =====

async function makeApiRequest(url, retryCount = 0) {
    await new Promise(resolve => setTimeout(resolve, CONFIG.API_DELAY));
    
    try {
        const response = await fetch(url);
        
        if (response.status === 429 || response.status >= 500) {
            if (retryCount < CONFIG.MAX_RETRIES) {
                await new Promise(resolve => setTimeout(resolve, Math.pow(2, retryCount) * 1000));
                return makeApiRequest(url, retryCount + 1);
            }
            throw new Error(`Request failed after ${CONFIG.MAX_RETRIES} retries: ${response.status}`);
        }
        
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }
        
        const data = await response.json();
        
        if (data.error?.code === 'maxlag') {
            const waitTime = (data.error.lag || 5 + 2) * 1000;
            await new Promise(resolve => setTimeout(resolve, waitTime));
            return makeApiRequest(url, retryCount);
        }
        
        if (data.error) {
            throw new Error(`API Error: ${data.error.code} - ${data.error.info}`);
        }
        
        return data;
    } catch (error) {
        if (retryCount < CONFIG.MAX_RETRIES) {
            await new Promise(resolve => setTimeout(resolve, 1000));
            return makeApiRequest(url, retryCount + 1);
        }
        throw error;
    }
}

// Generic paginated API fetcher
async function fetchAllPages(apiConfig, statusCallback) {
    let allItems = [];
    let continueToken = null;
    let pagesProcessed = 0;
    
    do {
        const url = apiConfig.buildUrl(continueToken);
        statusCallback(`${apiConfig.progressMessage} (page ${pagesProcessed + 1})...`);
        
        const data = await makeApiRequest(url);
        const { items, continueToken: nextToken } = apiConfig.parseResponse(data);
        
        allItems = allItems.concat(items);
        continueToken = nextToken;
        pagesProcessed++;
        
        statusCallback(`Retrieved ${allItems.length} ${apiConfig.itemType} (page ${pagesProcessed})...`);
    } while (continueToken);
    
    return allItems;
}

// ===== CONSOLIDATED FETCH METHODS =====

// Generic paginated fetcher - handles all simple list-based APIs
async function fetchPaginatedList(listType, params, statusCallback = () => {}) {
    const configs = {
        categoryMembers: {
            list: 'categorymembers',
            titleParam: 'cmtitle',
            continueParam: 'cmcontinue',
            limitParam: 'cmlimit',
            namespaceParam: 'cmnamespace',
            dataPath: 'categorymembers',
            defaultNamespaces: '0|1|2|3|4|5|6|7|8|9|10|11|12|13|15'
        },
        categorySubcategories: {
            list: 'categorymembers',
            titleParam: 'cmtitle',
            continueParam: 'cmcontinue',
            limitParam: 'cmlimit',
            namespaceParam: 'cmnamespace',
            dataPath: 'categorymembers',
            defaultNamespaces: '14'
        },
        backlinks: {
            list: 'backlinks',
            titleParam: 'bltitle',
            continueParam: 'blcontinue',
            limitParam: 'bllimit',
            namespaceParam: 'blnamespace',
            dataPath: 'backlinks'
        },
        prefixPages: {
            list: 'allpages',
            titleParam: 'apprefix',
            continueParam: 'apcontinue',
            limitParam: 'aplimit',
            namespaceParam: 'apnamespace',
            dataPath: 'allpages'
        },
        search: {
            list: 'search',
            titleParam: 'srsearch',
            continueParam: 'sroffset',
            limitParam: 'srlimit',
            dataPath: 'search'
        }
    };
    
    const config = configs[listType];
    if (!config) {
        throw new Error(`Unknown list type: ${listType}`);
    }
    
    return fetchAllPages({
        buildUrl: (continueToken) => {
            let url = `${CONFIG.API_URL}?action=query&list=${config.list}&${config.limitParam}=max&maxlag=5&format=json&origin=*`;
            
            // Add title/search parameter
            if (config.titleParam && params.title) {
                if (listType === 'categoryMembers' || listType === 'categorySubcategories') {
                    url += `&${config.titleParam}=Category:${encodeURIComponent(params.title)}`;
                } else {
                    url += `&${config.titleParam}=${encodeURIComponent(params.title)}`;
                }
            }
            
            // Add namespace parameter
            if (config.namespaceParam) {
                const namespace = params.namespace !== undefined ? params.namespace : config.defaultNamespaces;
                if (namespace !== null) {
                    url += `&${config.namespaceParam}=${namespace}`;
                }
            }
            
            // Add continuation token
            if (continueToken) {
                url += `&${config.continueParam}=${continueToken}`;
            }
            
            return url;
        },
        parseResponse: (data) => ({
            items: data.query?.[config.dataPath]?.map(item => item.title) || [],
            continueToken: data.continue?.[config.continueParam] || null
        }),
        progressMessage: params.progressMessage || `Fetching ${listType}`,
        itemType: params.itemType || 'items'
    }, statusCallback);
}

// Individual fetch methods using the consolidated approach
async function fetchCategoryMembers(categoryTitle) {
    return fetchPaginatedList('categoryMembers', {
        title: categoryTitle,
        progressMessage: `Fetching items for: ${categoryTitle}`,
        itemType: 'items'
    });
}

async function fetchCategorySubcategories(categoryTitle) {
    return fetchPaginatedList('categorySubcategories', {
        title: categoryTitle,
        progressMessage: `Fetching subcategories for: ${categoryTitle}`,
        itemType: 'subcategories'
    });
}

async function fetchBacklinks(targetTitle, namespaces, statusText) {
    return fetchPaginatedList('backlinks', {
        title: targetTitle,
        namespace: namespaces,
        progressMessage: `Fetching backlinks for: ${targetTitle}`,
        itemType: 'backlinks'
    }, (msg) => statusText.innerHTML = msg);
}

async function fetchPrefixPages(prefix, namespace, statusText) {
    return fetchPaginatedList('prefixPages', {
        title: prefix,
        namespace: namespace,
        progressMessage: `Fetching pages with prefix "${prefix}" in namespace ${namespace}`,
        itemType: 'pages'
    }, (msg) => statusText.innerHTML = msg);
}

async function fetchSearchResults(query, statusText) {
    return fetchPaginatedList('search', {
        title: query,
        progressMessage: `Searching for: "${query}"`,
        itemType: 'search results'
    }, (msg) => statusText.innerHTML = msg);
}

// Combined category fetch methods
async function fetchCategoryBoth(categoryTitle) {
    const [items, subcategories] = await Promise.all([
        fetchCategoryMembers(categoryTitle),
        fetchCategorySubcategories(categoryTitle)
    ]);
    return [...items, ...subcategories];
}

// Recursive methods
async function fetchCategoryMembersRecursive(categoryTitle, statusText) {
    const visited = new Set();
    const allItems = [];
    const queue = [categoryTitle];
    let totalCategories = 0;
    
    while (queue.length > 0) {
        const currentCategory = queue.shift();
        const categoryKey = `Category:${currentCategory}`;
        
        if (visited.has(categoryKey)) continue;
        visited.add(categoryKey);
        totalCategories++;
        
        statusText.innerHTML = `Getting items from "${currentCategory}" (processed ${totalCategories} categories, found ${allItems.length} items, queue: ${queue.length})...`;
        
        const currentItems = await fetchCategoryMembers(currentCategory);
        allItems.push(...currentItems);
        
        const subcategories = await fetchCategorySubcategories(currentCategory);
        for (const subcategory of subcategories) {
            if (!visited.has(subcategory)) {
                queue.push(subcategory.replace('Category:', ''));
            }
        }
    }
    
    return [...new Set(allItems)];
}

async function fetchCategorySubcategoriesRecursive(categoryTitle, statusText) {
    const visited = new Set();
    const allSubcategories = [];
    const queue = [`Category:${categoryTitle}`];
    
    while (queue.length > 0) {
        const currentCategory = queue.shift();
        
        if (visited.has(currentCategory)) continue;
        visited.add(currentCategory);
        
        statusText.innerHTML = `Exploring subcategories (found ${allSubcategories.length} categories, queue: ${queue.length})...`;
        
        const categoryNameForApi = currentCategory.replace('Category:', '');
        const directSubcategories = await fetchCategorySubcategories(categoryNameForApi);
        
        for (const subcategory of directSubcategories) {
            if (!visited.has(subcategory)) {
                allSubcategories.push(subcategory);
                queue.push(subcategory);
            }
        }
    }
    
    return [...new Set(allSubcategories)];
}

async function fetchCategoryBothRecursive(categoryTitle, statusText) {
    const visited = new Set();
    const allItems = [];
    const allSubcategories = [];
    const queue = [categoryTitle];
    let totalCategories = 0;
    
    while (queue.length > 0) {
        const currentCategory = queue.shift();
        const categoryKey = `Category:${currentCategory}`;
        
        if (visited.has(categoryKey)) continue;
        visited.add(categoryKey);
        totalCategories++;
        
        statusText.innerHTML = `Getting items and subcategories from "${currentCategory}" (processed ${totalCategories} categories, found ${allItems.length} items, ${allSubcategories.length} subcategories, queue: ${queue.length})...`;
        
        const [currentItems, directSubcategories] = await Promise.all([
            fetchCategoryMembers(currentCategory),
            fetchCategorySubcategories(currentCategory)
        ]);
        
        allItems.push(...currentItems);
        
        for (const subcategory of directSubcategories) {
            if (!visited.has(subcategory)) {
                allSubcategories.push(subcategory);
                queue.push(subcategory.replace('Category:', ''));
            }
        }
    }
    
    return [...new Set([...allItems, ...allSubcategories])];
}

// Article links fetcher
async function fetchArticleLinks(statusText) {
    const articleTitle = document.querySelector('.mw-first-heading').textContent;
    const apiUrl = `${CONFIG.API_URL}?action=query&titles=${encodeURIComponent(articleTitle)}&prop=revisions&rvprop=content&format=json&origin=*`;
    
    const data = await makeApiRequest(apiUrl);
    
    if (!data.query?.pages) {
        throw new Error('Could not fetch article content.');
    }
    
    const pages = Object.values(data.query.pages);
    if (pages.length === 0 || !pages[0].revisions) {
        throw new Error('No content found for this article.');
    }
    
    const wikitext = pages[0].revisions[0]['*'];
    const wikilinkRegex = /\[\[([^\]|\[]+)(?:\|[^\]]+)?\]\]/g;
    const links = [];
    let match;
    
    while ((match = wikilinkRegex.exec(wikitext)) !== null) {
        let linkTarget = match[1].trim();
        
        if (linkTarget.includes('#')) {
            linkTarget = linkTarget.split('#')[0];
        }
        
        if (linkTarget && 
            !linkTarget.startsWith('File:') && 
            !linkTarget.startsWith('Image:') &&
            !linkTarget.startsWith('Category:') && 
            !linkTarget.startsWith('Template:') &&
            !linkTarget.startsWith(':') &&
            !linkTarget.includes('|')) {
            links.push(linkTarget);
        }
    }
    
    // Return links in original order (don't sort)
    return [...new Set(links)];
}

// Scrape watchlist items from the current page (no API calls)
async function scrapeWatchlistFromPage() {
    const watchlistItems = [];
    
    // Primary method: Extract from data-target-page attributes
    const targetPageElements = document.querySelectorAll('[data-target-page]');
    console.log(`Found ${targetPageElements.length} data-target-page elements`);
    
    for (const element of targetPageElements) {
        const pageName = element.getAttribute('data-target-page');
        
        if (pageName) {
            // Remove namespace prefixes to get the actual article name
            let articleName = pageName;
            
            // Handle special cases
            if (pageName.startsWith('Talk:')) {
                // For talk pages, extract the main article name
                articleName = pageName.replace('Talk:', '');
            } else if (pageName.includes(':') && 
                      !pageName.startsWith('Category:') && 
                      !pageName.startsWith('File:') && 
                      !pageName.startsWith('Template:') && 
                      !pageName.startsWith('Help:') && 
                      !pageName.startsWith('Portal:') && 
                      !pageName.startsWith('MediaWiki:')) {
                // Skip other namespace pages like Wikipedia:, User:, etc.
                continue;
            }
            
            // Only include if it's a mainspace article (no colon in name)
            if (articleName && !articleName.includes(':') && !watchlistItems.includes(articleName)) {
                watchlistItems.push(articleName);
            }
        }
    }
    
    // Backup method: Look for .mw-changeslist-title links if we didn't get many results
    if (watchlistItems.length < 3) {
        console.log('Using backup method: .mw-changeslist-title');
        const titleLinks = document.querySelectorAll('.mw-changeslist-title');
        
        for (const link of titleLinks) {
            const href = link.getAttribute('href');
            if (href && href.startsWith('/wiki/')) {
                const pageName = decodeURIComponent(href.replace('/wiki/', '').replace(/_/g, ' '));
                
                // Extract mainspace article name
                let articleName = pageName;
                if (pageName.startsWith('Talk:')) {
                    articleName = pageName.replace('Talk:', '');
                } else if (pageName.includes(':')) {
                    continue; // Skip other namespaces
                }
                
                if (articleName && !watchlistItems.includes(articleName)) {
                    watchlistItems.push(articleName);
                }
            }
        }
    }
    
    console.log(`Final watchlist items found: ${watchlistItems.length}`, watchlistItems);
    return [...new Set(watchlistItems)];
}

// Helper function to extract target title
function extractTargetTitle() {
    const urlParams = new URLSearchParams(window.location.search);
    let targetTitle = urlParams.get('target');
    
    if (!targetTitle) {
        const headingElement = document.querySelector('.mw-first-heading');
        if (headingElement) {
            const match = headingElement.textContent.match(/Pages that link to "(.+)"/);
            if (match) targetTitle = match[1];
        }
    }
    
    if (!targetTitle) {
        const linkElement = document.querySelector('.mw-whatlinkshere-target a');
        if (linkElement) {
            targetTitle = linkElement.getAttribute('title') || linkElement.textContent;
        }
    }
    
    return targetTitle;
}

// ===== UI UTILITIES =====

function createControlBox(title, insertTarget) {
    const container = document.createElement('div');
    Object.assign(container.style, {
        padding: '10px',
        margin: '10px 0',
        backgroundColor: '#f8f9fa',
        border: '1px solid #a2a9b1',
        borderRadius: '3px'
    });

    const titleElement = document.createElement('h4');
    titleElement.textContent = title;
    Object.assign(titleElement.style, {
        margin: '0 0 10px 0',
        fontSize: '14px',
        fontWeight: 'bold'
    });
    container.appendChild(titleElement);

    const buttonsDiv = document.createElement('div');
    buttonsDiv.style.marginBottom = '10px';

    const urlCheckbox = document.createElement('input');
    urlCheckbox.type = 'checkbox';
    urlCheckbox.id = 'includeUrls';
    urlCheckbox.style.marginLeft = '15px';
    
    const urlLabel = document.createElement('label');
    urlLabel.htmlFor = 'includeUrls';
    urlLabel.textContent = 'Include URLs';
    urlLabel.style.marginLeft = '5px';
    addTooltip(urlLabel, 'Include full Wikipedia URLs for each item');

    buttonsDiv.appendChild(urlCheckbox);
    buttonsDiv.appendChild(urlLabel);
    container.appendChild(buttonsDiv);

    const statusText = document.createElement('div');
    Object.assign(statusText.style, {
        marginTop: '10px',
        color: '#555'
    });
    container.appendChild(statusText);

    const insertLocation = insertTarget || document.querySelector('#content');
    if (insertTarget) {
        insertTarget.parentNode.insertBefore(container, insertTarget.nextSibling);
    } else {
        insertLocation.prepend(container);
    }

    return { container, buttonsDiv, urlCheckbox, statusText };
}

function createButton(text, tooltip, buttonsDiv, urlCheckbox) {
    const btn = document.createElement('button');
    btn.textContent = text;
    Object.assign(btn.style, {
        marginRight: '10px',
        padding: '8px 12px',
        cursor: 'pointer'
    });
    addTooltip(btn, tooltip);
    buttonsDiv.insertBefore(btn, urlCheckbox);
    return btn;
}

// Generic button action handler
async function handleButtonAction(config, statusText, urlCheckbox) {
    statusText.innerHTML = config.startMessage;
    try {
        const items = await config.fetchFunction();
        if (items.length === 0) {
            statusText.innerHTML = config.emptyMessage;
            return;
        }
        
        const includeUrls = urlCheckbox.checked;
        const formattedText = formatItems(items, includeUrls);
        const copySuccess = await copyToClipboardOrDownload(formattedText, config.filename, statusText);
        
        if (copySuccess) {
            statusText.innerHTML = `Successfully copied ${items.length} ${config.itemType} to clipboard.`;
        }
    } catch (error) {
        statusText.innerHTML = `Error: ${error.message}`;
    }
}

// ===== PAGE HANDLERS =====

function initializeCategoryPage() {
    const categoryName = decodeURIComponent(window.location.pathname.split('/Category:')[1]);
    console.log("Category Name:", categoryName);
    
    const pageTitleHeading = document.querySelector('.mw-first-heading');
    const { buttonsDiv, urlCheckbox, statusText } = createControlBox('Category Tools', pageTitleHeading);

    const buttons = [
        {
            text: 'Copy items',
            tooltip: 'Copy all items in this category. Not recursive.',
            action: () => handleButtonAction({
                startMessage: 'Gathering items from this category via API...',
                fetchFunction: () => fetchCategoryMembers(categoryName),
                emptyMessage: 'No items found in this category.',
                filename: categoryName,
                itemType: 'items'
            }, statusText, urlCheckbox)
        },
        {
            text: 'Copy items recursively',
            tooltip: 'Copy all items in this category AND all items in its subcategories.',
            action: () => handleButtonAction({
                startMessage: 'Gathering items from this category and all subcategories recursively via API...',
                fetchFunction: () => fetchCategoryMembersRecursive(categoryName, statusText),
                emptyMessage: 'No items found in this category or its subcategories.',
                filename: `${categoryName}_all_recursive`,
                itemType: 'items'
            }, statusText, urlCheckbox)
        },
        {
            text: 'Copy subcats',
            tooltip: 'Copy all subcategories of this category. Not recursive.',
            action: () => handleButtonAction({
                startMessage: 'Gathering direct subcategories from this category via API...',
                fetchFunction: () => fetchCategorySubcategories(categoryName),
                emptyMessage: 'No direct subcategories found in this category.',
                filename: `${categoryName}_direct_subcats`,
                itemType: 'subcategories'
            }, statusText, urlCheckbox)
        },
        {
            text: 'Copy subcategories recursively',
            tooltip: 'Copy all subcategories of this category and its subcategories.',
            action: () => handleButtonAction({
                startMessage: 'Gathering all subcategories recursively via API...',
                fetchFunction: () => fetchCategorySubcategoriesRecursive(categoryName, statusText),
                emptyMessage: 'No subcategories found.',
                filename: `${categoryName}_subcategories`,
                itemType: 'subcategories'
            }, statusText, urlCheckbox)
        },
        {
            text: 'Copy both',
            tooltip: 'Copy all items and subcategories from this category. Not recursive.',
            action: () => handleButtonAction({
                startMessage: 'Gathering both items and subcategories from this category via API...',
                fetchFunction: () => fetchCategoryBoth(categoryName),
                emptyMessage: 'No items or subcategories found in this category.',
                filename: `${categoryName}_both`,
                itemType: 'items and subcategories'
            }, statusText, urlCheckbox)
        },
        {
            text: 'Copy both recursively',
            tooltip: 'Copy all items and subcategories from this category and all its subcategories.',
            action: () => handleButtonAction({
                startMessage: 'Gathering both items and subcategories recursively via API...',
                fetchFunction: () => fetchCategoryBothRecursive(categoryName, statusText),
                emptyMessage: 'No items or subcategories found in this category or its subcategories.',
                filename: `${categoryName}_both_recursive`,
                itemType: 'items and subcategories'
            }, statusText, urlCheckbox)
        }
    ];

    buttons.forEach(({ text, tooltip, action }) => {
        const btn = createButton(text, tooltip, buttonsDiv, urlCheckbox);
        btn.addEventListener('click', action);
    });
}

function initializeWhatLinksHerePage() {
    const targetTitle = extractTargetTitle();
    console.log("Target Title:", targetTitle);
    
    const targetHeading = document.querySelector('.mw-first-heading');
    const { buttonsDiv, urlCheckbox, statusText } = createControlBox('What Links Here Tools', targetHeading);

    const buttons = [
        {
            text: 'Copy all links',
            tooltip: 'Copy all pages that link to this page',
            namespace: null,
            type: 'all backlinks'
        },
        {
            text: 'Copy mainspace links',
            tooltip: 'Copy only mainspace (article) pages that link to this page',
            namespace: '0',
            type: 'mainspace backlinks'
        }
    ];

    buttons.forEach(({ text, tooltip, namespace, type }) => {
        const btn = createButton(text, tooltip, buttonsDiv, urlCheckbox);
        btn.addEventListener('click', () => handleButtonAction({
            startMessage: `Gathering ${type} via API...`,
            fetchFunction: () => fetchBacklinks(targetTitle, namespace, statusText),
            emptyMessage: namespace === '0' ? 'No mainspace pages link to this page.' : 'No pages link to this page.',
            filename: namespace === '0' ? 'mainspace_backlinks' : 'all_backlinks',
            itemType: 'backlinks'
        }, statusText, urlCheckbox));
    });

    // Non-mainspace button (special case)
    const nonMainspaceBtn = createButton('Copy non-mainspace links', 'Copy only non-mainspace pages (talk, user, etc.) that link to this page', buttonsDiv, urlCheckbox);
    nonMainspaceBtn.addEventListener('click', () => handleButtonAction({
        startMessage: 'Gathering non-mainspace backlinks via API...',
        fetchFunction: async () => {
            const allBacklinks = await fetchBacklinks(targetTitle, null, statusText);
            statusText.innerHTML = 'Filtering out mainspace backlinks...';
            const mainspaceBacklinks = await fetchBacklinks(targetTitle, '0', statusText);
            const mainspaceSet = new Set(mainspaceBacklinks);
            return allBacklinks.filter(link => !mainspaceSet.has(link));
        },
        emptyMessage: 'No non-mainspace pages link to this page.',
        filename: 'non_mainspace_backlinks',
        itemType: 'non-mainspace backlinks'
    }, statusText, urlCheckbox));
}

function initializePrefixPage() {
    const urlParams = new URLSearchParams(window.location.search);
    let prefix = urlParams.get('prefix') || urlParams.get('from') || '';
    let namespace = urlParams.get('namespace') || '0';
    
    // Extract prefix from URL path for Special:PrefixIndex/PREFIX format
    if (!prefix && window.location.pathname.includes('Special:PrefixIndex/')) {
        const pathParts = window.location.pathname.split('Special:PrefixIndex/');
        if (pathParts.length > 1) {
            const fullPrefix = decodeURIComponent(pathParts[1]);
            
            // Handle namespace prefixes like "User:Polygnotus"
            if (fullPrefix.includes(':')) {
                const [namespaceName, actualPrefix] = fullPrefix.split(':', 2);
                const namespaceMap = {
                    'User': '2',
                    'Wikipedia': '4',
                    'File': '6',
                    'MediaWiki': '8',
                    'Template': '10',
                    'Help': '12',
                    'Category': '14',
                    'Portal': '100',
                    'Draft': '118'
                };
                
                if (namespaceMap[namespaceName]) {
                    namespace = namespaceMap[namespaceName];
                    prefix = actualPrefix;
                } else {
                    prefix = fullPrefix;
                }
            } else {
                prefix = fullPrefix;
            }
        }
    }
    
    console.log("Prefix:", prefix, "Namespace:", namespace);
    
    const targetHeading = document.querySelector('.mw-first-heading');
    const { buttonsDiv, urlCheckbox, statusText } = createControlBox('Prefix Search Tools', targetHeading);

    const btn = createButton('Copy all pages', 'Copy all pages with this prefix using API', buttonsDiv, urlCheckbox);
    btn.addEventListener('click', () => {
        if (!prefix) {
            statusText.innerHTML = 'Error: No prefix found in URL parameters or path.';
            return;
        }
        
        handleButtonAction({
            startMessage: 'Gathering all pages with this prefix via API...',
            fetchFunction: () => fetchPrefixPages(prefix, namespace, statusText),
            emptyMessage: `No pages found with prefix "${prefix}" in namespace ${namespace}.`,
            filename: `prefix_${prefix.replace(/[^a-zA-Z0-9]/g, '_')}`,
            itemType: 'pages'
        }, statusText, urlCheckbox);
    });
}

function initializeWatchlistPage() {
    const targetHeading = document.querySelector('.mw-first-heading');
    const { buttonsDiv, urlCheckbox, statusText } = createControlBox('Watchlist Tools', targetHeading);

    const btn = createButton('Copy all watchlist items', 'Copy all pages from your watchlist by scraping the page', buttonsDiv, urlCheckbox);
    btn.addEventListener('click', () => handleButtonAction({
        startMessage: 'Scraping watchlist items from page...',
        fetchFunction: () => scrapeWatchlistFromPage(),
        emptyMessage: 'No items found in your watchlist.',
        filename: 'complete_watchlist',
        itemType: 'watchlist items'
    }, statusText, urlCheckbox));
}

function initializeSearchPage() {
    const urlParams = new URLSearchParams(window.location.search);
    const searchQuery = urlParams.get('search') || '';
    
    console.log("Search Query:", searchQuery);
    
    const targetHeading = document.querySelector('.mw-first-heading');
    const { buttonsDiv, urlCheckbox, statusText } = createControlBox('Search Results Tools', targetHeading);

    const btn = createButton('Copy all search results', 'Copy all search results using API', buttonsDiv, urlCheckbox);
    btn.addEventListener('click', () => {
        if (!searchQuery) {
            statusText.innerHTML = 'Error: No search query found in URL parameters.';
            return;
        }
        
        handleButtonAction({
            startMessage: 'Gathering all search results via API...',
            fetchFunction: () => fetchSearchResults(searchQuery, statusText),
            emptyMessage: `No search results found for "${searchQuery}".`,
            filename: `search_${searchQuery.replace(/[^a-zA-Z0-9]/g, '_')}`,
            itemType: 'search results'
        }, statusText, urlCheckbox);
    });
}

function initializeArticlePage() {
    const targetHeading = document.querySelector('.mw-first-heading');
    const { buttonsDiv, urlCheckbox, statusText } = createControlBox('Article Links Tools', targetHeading);

    const btn = createButton('Copy wikitext links', 'Copy all wikilink targets from this article\'s source', buttonsDiv, urlCheckbox);
    btn.addEventListener('click', () => handleButtonAction({
        startMessage: 'Fetching article wikitext...',
        fetchFunction: () => fetchArticleLinks(statusText),
        emptyMessage: 'No wikilinks found in this article.',
        filename: 'article_wikilinks',
        itemType: 'wikilinks'
    }, statusText, urlCheckbox));
}

// ===== INITIALIZATION =====

function initializePageHandler() {
    const currentUrl = window.location.href;
    const currentPath = window.location.pathname;
    
    if (currentUrl.includes('/wiki/Category:')) {
        initializeCategoryPage();
    } else if (currentUrl.includes('Special:WhatLinksHere') || currentPath.includes('Special:WhatLinksHere')) {
        initializeWhatLinksHerePage();
    } else if (currentUrl.includes('Special:PrefixIndex') || currentPath.includes('Special:PrefixIndex') || currentUrl.includes('Special:AllPages')) {
        initializePrefixPage();
    } else if (currentUrl.includes('Special:Watchlist')) {
        initializeWatchlistPage();
    } else if (currentUrl.includes('Special:Search') || currentPath.includes('Special:Search') || currentUrl.includes('search=')) {
        initializeSearchPage();
    } else if (currentPath.startsWith('/wiki/') && !currentPath.includes(':')) {
        initializeArticlePage();
    }
}

if (!mw.config.get('wgIsMainPage')) {
    initializePageHandler();
}
console.log('Universal Wikipedia List Copier script loaded successfully!');