Greasy Fork is available in English.

Audible Metadata to MAM JSON script (with multiple AI model options)

Copies audiobook metadata to JSON and opens MAM upload page. Optionally offers multiple AI models for category selection.

// ==UserScript==
// @name         Audible Metadata to MAM JSON script (with multiple AI model options)
// @namespace    https://greasyfork.org/en/scripts/511491
// @version      1.4.2
// @license      MIT
// @description  Copies audiobook metadata to JSON and opens MAM upload page. Optionally offers multiple AI models for category selection.
// @author       SnowmanNurse (Parts from and inspired by script by Dr.Blank)
// @include      https://www.audible.*/pd/*
// @include      https://www.audible.*/ac/*
// @grant        none
// ==/UserScript==

const RIPPER = "MusicFab"; // yours can be Libation, OpenAudible, MusicFab, InAudible etc. or Blank if Encoded
const CHAPTERIZED = true; // yours will be false if not properly ripped

// Category selection method: 1 for direct mapping, 2 for scoring method, 3 for AI-based selection
const CATEGORY_SELECTION_METHOD = 3;

// AI model selection (only used if CATEGORY_SELECTION_METHOD is 3)
// 0: None (fall back to direct mapping), 1: ChatGPT (OpenAI), 2: Meta Llama, 3: Google Gemini 128K,
// 4: Mistral AI, 5: Google Gemini 1M, 6: Anthropic Claude 3
const AI_MODEL = 1;

// API keys for different AI models
const CHATGPT_API_KEY = "";
const META_API_KEY = "";
const GOOGLE_API_KEY = "";
const MISTRAL_API_KEY = "";
const ANTHROPIC_API_KEY = "";

const AVAILABLE_CATEGORIES = [
    "Audiobooks - Art", "Audiobooks - Biographical", "Audiobooks - Business", "Audiobooks - Crafts",
    "Audiobooks - Fantasy", "Audiobooks - Food", "Audiobooks - History", "Audiobooks - Horror",
    "Audiobooks - Humor", "Audiobooks - Instructional", "Audiobooks - Juvenile", "Audiobooks - Language",
    "Audiobooks - Medical", "Audiobooks - Mystery", "Audiobooks - Nature", "Audiobooks - Philosophy",
    "Audiobooks - Recreation", "Audiobooks - Romance", "Audiobooks - Self-Help", "Audiobooks - Western",
    "Audiobooks - Young Adult", "Audiobooks - Historical Fiction", "Audiobooks - Literary Classics",
    "Audiobooks - Science Fiction", "Audiobooks - True Crime", "Audiobooks - Urban Fantasy",
    "Audiobooks - Action/Adventure", "Audiobooks - Computer/Internet", "Audiobooks - Crime/Thriller",
    "Audiobooks - Home/Garden", "Audiobooks - Math/Science/Tech", "Audiobooks - Travel/Adventure",
    "Audiobooks - Pol/Soc/Relig", "Audiobooks - General Fiction", "Audiobooks - General Non-Fic"
];

const AUDIBLE_TO_MAM_CATEGORY_MAP = {
    "Arts & Entertainment": "Audiobooks - Art",
    "Biographies & Memoirs": "Audiobooks - Biographical",
    "Business & Careers": "Audiobooks - Business",
    "Children's Audiobooks": "Audiobooks - Juvenile",
    "Comedy & Humor": "Audiobooks - Humor",
    "Computers & Technology": "Audiobooks - Computer/Internet",
    "Education & Learning": "Audiobooks - Instructional",
    "Erotica": "Audiobooks - Romance",
    "Health & Wellness": "Audiobooks - Medical",
    "History": "Audiobooks - History",
    "Home & Garden": "Audiobooks - Home/Garden",
    "LGBTQ+": "Audiobooks - General Fiction",
    "Literature & Fiction": "Audiobooks - General Fiction",
    "Money & Finance": "Audiobooks - Business",
    "Mystery, Thriller & Suspense": "Audiobooks - Mystery",
    "Politics & Social Sciences": "Audiobooks - Pol/Soc/Relig",
    "Relationships, Parenting & Personal Development": "Audiobooks - Self-Help",
    "Religion & Spirituality": "Audiobooks - Pol/Soc/Relig",
    "Romance": "Audiobooks - Romance",
    "Science & Engineering": "Audiobooks - Math/Science/Tech",
    "Science Fiction & Fantasy": "Audiobooks - Science Fiction",
    "Sports & Outdoors": "Audiobooks - Recreation",
    "Teen & Young Adult": "Audiobooks - Young Adult",
    "Travel & Tourism": "Audiobooks - Travel/Adventure"
};

const KEYWORD_TO_MAM_CATEGORY_MAP = {
    "science fiction": "Audiobooks - Science Fiction",
    "sci-fi": "Audiobooks - Science Fiction",
    "fantasy": "Audiobooks - Fantasy",
    "magic": "Audiobooks - Fantasy",
    "mystery": "Audiobooks - Mystery",
    "detective": "Audiobooks - Mystery",
    "crime": "Audiobooks - Crime/Thriller",
    "thriller": "Audiobooks - Crime/Thriller",
    "suspense": "Audiobooks - Crime/Thriller",
    "horror": "Audiobooks - Horror",
    "romance": "Audiobooks - Romance",
    "love story": "Audiobooks - Romance",
    "historical": "Audiobooks - Historical Fiction",
    "history": "Audiobooks - History",
    "biography": "Audiobooks - Biographical",
    "memoir": "Audiobooks - Biographical",
    "business": "Audiobooks - Business",
    "finance": "Audiobooks - Business",
    "self-help": "Audiobooks - Self-Help",
    "personal development": "Audiobooks - Self-Help",
    "science": "Audiobooks - Math/Science/Tech",
    "technology": "Audiobooks - Math/Science/Tech",
    "computer": "Audiobooks - Computer/Internet",
    "programming": "Audiobooks - Computer/Internet",
    "travel": "Audiobooks - Travel/Adventure",
    "adventure": "Audiobooks - Travel/Adventure",
    "cooking": "Audiobooks - Food",
    "recipe": "Audiobooks - Food",
    "health": "Audiobooks - Medical",
    "wellness": "Audiobooks - Medical",
    "fitness": "Audiobooks - Medical",
    "sports": "Audiobooks - Recreation",
    "outdoor": "Audiobooks - Recreation",
    "philosophy": "Audiobooks - Philosophy",
    "religion": "Audiobooks - Pol/Soc/Relig",
    "spirituality": "Audiobooks - Pol/Soc/Relig",
    "politics": "Audiobooks - Pol/Soc/Relig",
    "social science": "Audiobooks - Pol/Soc/Relig",
    "art": "Audiobooks - Art",
    "music": "Audiobooks - Art",
    "photography": "Audiobooks - Art",
    "craft": "Audiobooks - Crafts",
    "diy": "Audiobooks - Crafts",
    "humor": "Audiobooks - Humor",
    "comedy": "Audiobooks - Humor",
    "funny": "Audiobooks - Humor",
    "children": "Audiobooks - Juvenile",
    "kid": "Audiobooks - Juvenile",
    "young adult": "Audiobooks - Young Adult",
    "teen": "Audiobooks - Young Adult",
    "language": "Audiobooks - Language",
    "linguistics": "Audiobooks - Language",
    "nature": "Audiobooks - Nature",
    "environment": "Audiobooks - Nature",
    "western": "Audiobooks - Western"
};

function cleanName(name) {
    const titlesToRemove = [
        "PhD", "MD", "JD", "MBA", "MA", "MS", "MSc", "MFA", "MEd", "ScD", "DrPH", "MPH", "LLM", "DDS", "DVM", "EdD", "PsyD", "ThD", "DO", "PharmD", "DSc", "DBA", "RN", "CPA", "Esq.", "LCSW", "PE", "AIA", "FAIA", "CSP", "CFP", "Jr.", "Sr.", "I", "II", "III", "IV", "Dr.", "Mr.", "Mrs.", "Ms.", "Prof.", "Rev.", "Fr.", "Sr.", "Capt.", "Col.", "Gen.", "Lt.", "Cmdr.", "Adm.", "Sir", "Dame", "Hon.", "Amb.", "Gov.", "Sen.", "Rep.", "BSN", "MSN", "RN", "MS", "MN"
    ];

    let cleanedName = name.trim();

    titlesToRemove.forEach(title => {
        const regexBefore = new RegExp(`^${title}\\b`, 'i');
        const regexAfter = new RegExp(`\\b${title}$`, 'i');
        cleanedName = cleanedName.replace(regexBefore, '').replace(regexAfter, '');
    });

    // Remove any remaining titles that might be in the middle of the name
    titlesToRemove.forEach(title => {
        const regexMiddle = new RegExp(`\\s${title}\\s`, 'gi');
        cleanedName = cleanedName.replace(regexMiddle, ' ');
    });

    // Remove any extra spaces
    cleanedName = cleanedName.replace(/\s+/g, ' ').trim();

    return cleanedName;
}


function cleanSeriesName(seriesName) {
    const wordsToRemove = ["series", "an", "the", "novel"];
    let cleanedName = seriesName.toLowerCase();

    wordsToRemove.forEach(word => {
        const regex = new RegExp(`\\b${word}\\b`, 'gi');
        cleanedName = cleanedName.replace(regex, '');
    });

    cleanedName = cleanedName.replace(/\s+/g, ' ').trim();
    return cleanedName.replace(/\b\w/g, l => l.toUpperCase());
}

function getLanguage() {
    let languageElement = document.querySelector(".languageLabel");
    let patt = /\s*(\w+)$/g;
    let matches = patt.exec(languageElement.innerHTML.trim());
    return matches[1];
}

function getSeriesInfo() {
    let seriesElements = document.querySelectorAll(".seriesLabel");
    let seriesInfo = [];

    seriesElements.forEach(element => {
        let seriesLink = element.querySelector("a");
        if (seriesLink) {
            let seriesName = cleanSeriesName(seriesLink.textContent);
            let bookNumberMatch = element.textContent.match(/Book\s*?(\d+\.?\d*-?\d*\.?\d*)/);
            let bookNumber = bookNumberMatch ? bookNumberMatch[1] : "";

            seriesInfo.push({ name: seriesName, number: bookNumber });
        }
    });

    return seriesInfo;
}

function getAudibleCategory() {
    let categoryElement = document.querySelector(".categoriesLabel");
    if (categoryElement) return categoryElement.innerText;

    categoryElement = document.querySelector("nav.bc-breadcrumb");
    if (categoryElement) return categoryElement.innerText;

    return "";
}

function smartCategoryMatcher(audibleCategory, title, description) {
    let possibleCategories = {};

    // Start with the Audible category mapping
    if (AUDIBLE_TO_MAM_CATEGORY_MAP[audibleCategory]) {
        possibleCategories[AUDIBLE_TO_MAM_CATEGORY_MAP[audibleCategory]] = 5;
    }

    // Function to add scores based on keyword matches
    const addScoreForKeywords = (text, weight) => {
        text = text.toLowerCase();
        for (let [keyword, category] of Object.entries(KEYWORD_TO_MAM_CATEGORY_MAP)) {
            if (text.includes(keyword)) {
                possibleCategories[category] = (possibleCategories[category] || 0) + weight;
                // Give extra weight to romance keywords
                if (category === "Audiobooks - Romance") {
                    possibleCategories[category] += 2;
                }
            }
        }
    };

    // Check title (higher weight)
    addScoreForKeywords(title, 3);

    // Check description (lower weight)
    addScoreForKeywords(description, 2);

    // Find the category with the highest score
    let bestMatch = Object.entries(possibleCategories).reduce((a, b) => a[1] > b[1] ? a : b)[0];

    // If no match found, default to "General Fiction" or "General Non-Fic"
    return bestMatch || (audibleCategory.includes("Fiction") ? "Audiobooks - General Fiction" : "Audiobooks - General Non-Fic");
}

async function getCategoryFromAI(title, description, audibleCategory) {
    const prompt = `Given the following audiobook information, select the most appropriate category from this list: ${AVAILABLE_CATEGORIES.join(", ")}

Title: ${title}
Description: ${description}
Audible Category: ${audibleCategory}

Please respond with only the category name, nothing else.`;

    switch (AI_MODEL) {
        case 1: return await getCategoryFromChatGPT(prompt);
        case 2: return await getCategoryFromMetaLlama(prompt);
        case 3: return await getCategoryFromGoogleGemini(prompt, false);
        case 4: return await getCategoryFromMistralAI(prompt);
        case 5: return await getCategoryFromGoogleGemini(prompt, true);
        case 6: return await getCategoryFromAnthropicClaude(prompt);
        default: return null;
    }
}

async function getCategoryFromChatGPT(prompt) {
    if (!OPENAI_API_KEY) return null;
    const response = await fetch("https://api.openai.com/v1/chat/completions", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
            "Authorization": `Bearer ${OPENAI_API_KEY}`
        },
        body: JSON.stringify({
            model: "gpt-4o-mini-2024-07-18",
            messages: [{ role: "user", content: prompt }],
            temperature: 0.7,
            max_tokens: 50
        })
    });
    const data = await response.json();
    return data.choices[0].message.content.trim();
}

async function getCategoryFromChatGPT(prompt) {
    if (!CHATGPT_API_KEY) return null;
    const response = await fetch("https://api.openai.com/v1/chat/completions", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
            "Authorization": `Bearer ${CHATGPT_API_KEY}`
        },
        body: JSON.stringify({
            model: "gpt-3.5-turbo",
            messages: [{ role: "user", content: prompt }],
            temperature: 0.7,
            max_tokens: 50
        })
    });
    const data = await response.json();
    return data.choices[0].message.content.trim();
}

async function getCategoryFromMetaLlama(prompt) {
    if (!META_API_KEY) return null;
    const response = await fetch("https://api.meta.com/v1/text/completions", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
            "Authorization": `Bearer ${META_API_KEY}`
        },
        body: JSON.stringify({
            model: "llama-2-13b-chat",
            prompt: prompt,
            max_tokens: 50
        })
    });
    const data = await response.json();
    return data.choices[0].text.trim();
}

async function getCategoryFromGoogleGemini(prompt, isLargeModel) {
    if (!GOOGLE_API_KEY) return null;
    const model = isLargeModel ? "gemini-1.5-flash-1m" : "gemini-1.5-flash";
    const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${GOOGLE_API_KEY}`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
            contents: [{ parts: [{ text: prompt }] }]
        })
    });
    const data = await response.json();
    return data.candidates[0].content.parts[0].text.trim();
}

async function getCategoryFromMistralAI(prompt) {
    if (!MISTRAL_API_KEY) return null;
    const response = await fetch("https://api.mistral.ai/v1/chat/completions", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
            "Authorization": `Bearer ${MISTRAL_API_KEY}`
        },
        body: JSON.stringify({
            model: "mistral-large-latest",
            messages: [{ role: "user", content: prompt }],
            max_tokens: 50
        })
    });
    const data = await response.json();
    return data.choices[0].message.content.trim();
}

async function getCategoryFromOpenAI(prompt) {
    if (!OPENAI_API_KEY) return null;
    const response = await fetch("https://api.openai.com/v1/chat/completions", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
            "Authorization": `Bearer ${OPENAI_API_KEY}`
        },
        body: JSON.stringify({
            model: "gpt-4",
            messages: [{ role: "user", content: prompt }],
            temperature: 0.7,
            max_tokens: 50
        })
    });
    const data = await response.json();
    return data.choices[0].message.content.trim();
}

async function getCategoryFromAnthropicClaude(prompt) {
    if (!ANTHROPIC_API_KEY) return null;
    const response = await fetch("https://api.anthropic.com/v1/messages", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
            "x-api-key": ANTHROPIC_API_KEY
        },
        body: JSON.stringify({
            model: "claude-3-haiku-20240307",
            max_tokens: 50,
            messages: [{ role: "user", content: prompt }]
        })
    });
    const data = await response.json();
    return data.content[0].text.trim();
}

async function getMAMCategory() {
    let audibleCategory = getAudibleCategory();
    let title = getTitle();
    let description = document.querySelector(".productPublisherSummary>div>div>span") ?
        document.querySelector(".productPublisherSummary>div>div>span").textContent :
        "No description available";

    switch (CATEGORY_SELECTION_METHOD) {
        case 1: // Direct mapping
            return AUDIBLE_TO_MAM_CATEGORY_MAP[audibleCategory] || "";
        case 2: // Scoring method
            return smartCategoryMatcher(audibleCategory, title, description);
        case 3: // AI-based selection
            const aiCategory = await getCategoryFromAI(title, description, audibleCategory);
            if (aiCategory && AVAILABLE_CATEGORIES.includes(aiCategory)) {
                return aiCategory;
            }
            // If AI fails or no API key, fall back to direct mapping
            return AUDIBLE_TO_MAM_CATEGORY_MAP[audibleCategory] || "";
        default:
            return "";
    }
}

function getTitle() {
    let title = document.getElementsByTagName("h1")[0].innerText;
    return title;
}

function getSubtitle() {
    let sLoggedIn = document.querySelector(".subtitle");
    let sLoggedOut = document.querySelector("span.bc-size-medium");
    let subtitle = "";

    if (sLoggedIn) {
        subtitle = sLoggedIn.innerText;
    } else if (sLoggedOut) {
        subtitle = sLoggedOut.innerText;
    }

    if (!subtitle) return "";
    if (subtitle.trim() === "+More") return "";

    let seriesInfo = getSeriesInfo();
    let isSubtitleSeries = seriesInfo.some(series =>
        subtitle.toLowerCase().includes(series.name.toLowerCase())
    );

    if (isSubtitleSeries) return "";

    return subtitle;
}

function getTitleAndSubtitle() {
    let subtitle = getSubtitle();
    if (subtitle) {
        return `${getTitle()}: ${subtitle}`;
    }
    return getTitle();
}

function getReleaseDate() {
    let element = document.querySelector(".releaseDateLabel");
    let patt = /\d{2}-\d{2}-\d{2}/;
    let matches = patt.exec(element.innerText);
    return matches ? matches[0] : "";
}

function getPublisher() {
    let publisherElement = document.querySelector(".publisherLabel>a");
    return publisherElement ? publisherElement.innerText : "Unknown Publisher";
}

function getRunTime() {
    let runtimeElement = document.querySelector(".runtimeLabel");
    let runtime = runtimeElement ? runtimeElement.textContent : "Unknown";
    let patt = new RegExp("Length:\\n\\s+(\\d[^\n]+)");
    let matches = patt.exec(runtime);
    return matches ? matches[1] : "Unknown";
}

function getAdditionalTags() {
    let tags = [];
    tags.push(`Duration: ${getRunTime()}`);
    if (CHAPTERIZED) tags.push("Chapterized");
    if (RIPPER) tags.push(RIPPER);
    tags.push(`Audible Release: ${getReleaseDate()}`);
    tags.push(`Publisher: ${getPublisher()}`);
    tags.push(getAudibleCategory());
    return tags.join(" | ");
}

function getASIN() {
    const urlMatch = window.location.pathname.match(/\/([A-Z0-9]{10})/);
    if (urlMatch && urlMatch[1]) {
        return "ASIN:" + urlMatch[1];
    }

    const productDetails = document.querySelector('#detailsproductInfoSection');
    if (productDetails) {
        const asinMatch = productDetails.textContent.match(/ASIN:\s*([A-Z0-9]{10})/);
        if (asinMatch && asinMatch[1]) {
            return "ASIN:" + asinMatch[1];
        }
    }

    return "";
}

function getAuthors() {
    var authorElements = document.querySelectorAll(".authorLabel a");
    var authors = [];
    for (let element of authorElements) {
        if (element) {
            let authorName = element.textContent.trim();
            authorName = authorName.replace(/ - (foreword|afterword|translator|editor)/gi, "");
            authorName = cleanName(authorName);
            if (authorName &&
                !authors.includes(authorName) &&
                authorName !== "Prologue Projects" &&
                authorName.toLowerCase() !== "title" &&
                authorName.toLowerCase() !== "author + title") {
                authors.push(authorName);
            }
        }
    }
    return authors;
}

function getNarrators() {
    var narratorElements = document.querySelectorAll(".narratorLabel a");
    var narrators = [];
    for (let element of narratorElements) {
        if (element) {
            let narratorName = element.textContent.trim();
            narratorName = cleanName(narratorName);
            if (narratorName && !narrators.includes(narratorName) && narratorName.toLowerCase() !== "full cast") {
                narrators.push(narratorName);
            }
        }
    }
    return narrators;
}

async function generateJson() {
    var imgElement = document.querySelector(".bc-image-inset-border");
    var imageSrc = imgElement ? imgElement.src : "No image available";

    var descriptionElement = document.querySelector(".productPublisherSummary>div>div>span") ||
        document.querySelector("div.bc-col-6 span.bc-text") ||
        document.querySelector("div.bc-text.bc-color-secondary");
    var description = descriptionElement ? descriptionElement.innerHTML : "No description available";
    description = description.replace(/\s+/g, " ").replace(/"/g, '\\"').replace(/<p><\/p>/g, "").replace(/<\/p>/g, "</p><br>").replace(/<\/ul>/g, "</ul><br>");

    var json = {
        "authors": getAuthors(),
        "description": description,
        "narrators": getNarrators(),
        "tags": getAdditionalTags(),
        "thumbnail": imageSrc,
        "title": getTitleAndSubtitle(),
        "language": getLanguage(),
        "series": getSeriesInfo(),
        "category": await getMAMCategory(),
        "isbn": getASIN()
    };

    var strJson = JSON.stringify(json);
    await copyToClipboard(strJson);
}


function addOverlayAndButton() {
    const overlayHtml = `
    <div id="mam-overlay" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); backdrop-filter: blur(5px); z-index: 9999;">
        <div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border-radius: 5px; text-align: center;">
            <p style="font-size: 18px; margin: 0;">Processing JSON data...</p>
            <p style="font-size: 16px; margin: 10px 0 0;">Please wait...</p>
            <div id="ai-gif" style="display: none; margin-top: 20px;">
                <img src="https://c.tenor.com/JDV9WN1QC3kAAAAC/tenor.gif" alt="AI Processing" style="max-width: 200px;">
            </div>
        </div>
    </div>`;

    document.body.insertAdjacentHTML('beforeend', overlayHtml);

    const buyBoxElement = document.querySelector("#adbl-buy-box");
    if (!buyBoxElement) return;

    const isAISelected = CATEGORY_SELECTION_METHOD === 3 && AI_MODEL !== 0;
    const buttonText = isAISelected ? "Copy Book info to JSON with AI" : "Copy Book info to JSON without AI";

    const buttonHtml = `
    <div id="mam-metadata-button" class="bc-row bc-spacing-top-s1">
        <div class="bc-row">
            <div class="bc-trigger bc-pub-block">
                <span class="bc-button bc-button-primary">
                    <button id="copyMetadataButton" class="bc-button-text" type="button" tabindex="0" title="Copy book details as JSON">
                        <span class="bc-text bc-button-text-inner bc-size-action-large">
                            ${buttonText}
                        </span>
                    </button>
                </span>
            </div>
        </div>
    </div>`;

    buyBoxElement.insertAdjacentHTML('beforeend', buttonHtml);

    document.getElementById("copyMetadataButton").addEventListener("click", async function (event) {
        event.preventDefault();
        showOverlay();
        await generateJson();
        hideOverlay();
    });
}

function showOverlay() {
    document.getElementById("mam-overlay").style.display = "block";
    // Show GIF if AI model is selected
    if (CATEGORY_SELECTION_METHOD === 3 && AI_MODEL !== 0) {
        document.getElementById("ai-gif").style.display = "block";
    }
}

function hideOverlay() {
    document.getElementById("mam-overlay").style.display = "none";
    // Hide GIF
    document.getElementById("ai-gif").style.display = "none";
}

async function copyToClipboard(text) {
    try {
        await navigator.clipboard.writeText(text);
        console.log('Copied to clipboard successfully!');
        window.open("https://www.myanonamouse.net/tor/upload.php", "_blank");
    } catch (err) {
        console.error('Could not copy text: ', err);
        alert('Failed to copy metadata. Please check the console for errors.');
    }
}

window.onload = function () {
    addOverlayAndButton();
};