diff --git a/css/skinport_styles.css b/css/skinport_styles.css index 16c0f59..890c53a 100644 --- a/css/skinport_styles.css +++ b/css/skinport_styles.css @@ -29,4 +29,5 @@ .betterfloat-sale-tag { font-size: 12px!important; margin: 1px; + border-radius: 3px; } \ No newline at end of file diff --git a/html/skinport.html b/html/skinport.html index 899c124..6dbb568 100644 --- a/html/skinport.html +++ b/html/skinport.html @@ -27,8 +27,8 @@

Currency Conversion

diff --git a/src/skinport/content_script.ts b/src/skinport/content_script.ts index c7755fb..8d13644 100644 --- a/src/skinport/content_script.ts +++ b/src/skinport/content_script.ts @@ -20,16 +20,7 @@ async function init() { await loadMapping(); await loadBuffMapping(); - // //check if url is in supported subpages - // if (url.endsWith('float.com/')) { - // await firstLaunch(); - // } else { - // for (let i = 0; i < supportedSubPages.length; i++) { - // if (url.includes(supportedSubPages[i])) { - // await firstLaunch(); - // } - // } - // } + await firstLaunch(); // mutation observer is only needed once if (!isObserverActive) { @@ -41,6 +32,27 @@ async function init() { } } +async function firstLaunch() { + let url = window.location.href; + console.log('[BetterFloat] First launch on Skinport, url: ', url); + if (url.includes('/market')) { + let catalogItems = document.querySelectorAll('.CatalogPage-item'); + for (let item of catalogItems) { + await adjustItem(item); + } + } else if (url.includes('/cart')) { + let cartContainer = document.querySelector('.Cart-container'); + if (cartContainer) { + await adjustCart(cartContainer); + } + } else if (url.includes('/item')) { + let itemPage = document.querySelectorAll('.ItemPage'); + for (let item of itemPage) { + await adjustItemPage(item); + } + } +} + async function applyMutation() { let observer = new MutationObserver(async (mutations) => { if (extensionSettings.spBuffPrice) { @@ -52,8 +64,14 @@ async function applyMutation() { // console.log('[BetterFloat] Mutation observer triggered, added node:', addedNode); - if (addedNode.className && addedNode.className.toString().includes('CatalogPage-item')) { - await adjustItem(addedNode); + if (addedNode.className) { + if (addedNode.className.toString().includes('CatalogPage-item')) { + await adjustItem(addedNode); + } else if (addedNode.className.toString().includes('Cart-container')) { + await adjustCart(addedNode); + } else if (addedNode.className.toString() == 'ItemPage') { + await adjustItemPage(addedNode); + } } // item popout // if (addedNode.tagName && addedNode.tagName.toLowerCase() == 'item-detail') { @@ -69,8 +87,73 @@ async function applyMutation() { observer.observe(document, { childList: true, subtree: true }); } +async function adjustItemPage(container: Element) { + console.log('[BetterFloat] Adjusting item page: ', container); + let itemRating = container.querySelector('.ItemPage-rating'); + if (itemRating) { + itemRating.remove(); + } + + let btnGroup = container.querySelector('.ItemPage-btnGroup'); + if (!btnGroup) return; + let newGroup = document.createElement('div'); + newGroup.className = btnGroup.className ?? newGroup.className; + // if an item is sold, the original links are unclickable, hence we reproduce them + let links = container.querySelectorAll('.ItemPage-link'); + let linkSteam = (Array.from(links).find((el) => el.innerHTML.includes('Steam')) as HTMLAnchorElement | null)?.href; + let linkInspect = (Array.from(links).find((el) => el.innerHTML.includes('Inspect')) as HTMLAnchorElement | null)?.href; + if (linkInspect) { + newGroup.insertAdjacentHTML('beforeend', ``); + } + if (linkSteam) { + newGroup.insertAdjacentHTML('beforeend', ``); + } + + let item = getFloatItem(container, itemSelectors.page); + console.log('[BetterFloat] Item: ', item); + if (!item) return; + let { buff_name: buff_name, priceListing, priceOrder } = await getBuffPrice(item); + let buffid = await getBuffMapping(buff_name); + let buffLink = buffid > 0 ? `https://buff.163.com/goods/${buffid}` : `https://buff.163.com/market/csgo#tab=selling&page_num=1&search=${encodeURIComponent(buff_name)}`; + newGroup.insertAdjacentHTML('beforeend', ``); + btnGroup.after(newGroup); + + let tooltipLink = container.querySelector('.ItemPage-value .Tooltip-link'); + const currencySymbol = tooltipLink.textContent?.charAt(0); + let suggestedContainer = container.querySelector('.ItemPage-suggested'); + if (suggestedContainer) { + generateBuffContainer(suggestedContainer as HTMLElement, priceListing, priceOrder, currencySymbol ?? '$', true); + } + + const difference = item.price - (extensionSettings.spPriceReference == 0 ? priceOrder : priceListing); + let priceContainer = container.querySelector('.ItemPage-price'); + if (priceContainer) { + let newContainer = document.createElement('div'); + let saleTag = document.createElement('span'); + newContainer.className = 'ItemPage-discount betterfloat-discount-container'; + newContainer.style.background = `linear-gradient(135deg,#0073d5,${difference == 0 ? 'black' : difference < 0 ? 'green' : '#ce0000'})`; + newContainer.style.transform = 'skewX(-15deg)'; + newContainer.style.borderRadius = '3px'; + saleTag.style.margin = '5px'; + saleTag.style.fontWeight = '700'; + saleTag.textContent = difference == 0 ? `-${currencySymbol}0` : (difference > 0 ? '+' : '-') + currencySymbol + Math.abs(difference).toFixed(2); + newContainer.appendChild(saleTag); + priceContainer.appendChild(newContainer); + } +} + +async function adjustCart(container: Element) { + if (extensionSettings.spCheckBoxes) { + let checkboxes = container.querySelectorAll('.Checkbox-input'); + for (let checkbox of checkboxes) { + (checkbox as HTMLInputElement).click(); + await new Promise((r) => setTimeout(r, 50)); // to avoid bot detection + } + } +} + async function adjustItem(container: Element) { - const item = getFloatItem(container); + const item = getFloatItem(container, itemSelectors.preview); if (!item) return; await addBuffPrice(item, container); // if (extensionSettings.stickerPrices) { @@ -81,14 +164,39 @@ async function adjustItem(container: Element) { // } } -function getFloatItem(container: Element): Skinport.Listing | null { - let name = container.querySelector('.ItemPreview-itemName')?.textContent ?? ''; +const itemSelectors = { + preview: { + name: '.ItemPreview-itemName', + title: '.ItemPreview-itemTitle', + text: '.ItemPreview-itemText', + stickers: '.ItemPreview-stickers', + price: '.ItemPreview-price', + }, + page: { + name: '.ItemPage-name', + title: '.ItemPage-title', + text: '.ItemPage-text', + stickers: '.ItemPage-stickers', + price: '.ItemPage-price', + }, +} as const; + +type ItemSelectors = (typeof itemSelectors)[keyof typeof itemSelectors]; + +function getFloatItem(container: Element, selector: ItemSelectors): Skinport.Listing | null { + let name = container.querySelector(selector.name)?.textContent ?? ''; if (name == '') { return null; } - let price = Number(container.querySelector('.ItemPreview-price .Tooltip-link')?.innerHTML.substring(1).replace(',', '')) ?? 0; - let type = container.querySelector('.ItemPreview-itemTitle')?.textContent ?? ''; - let text = container.querySelector('.ItemPreview-itemText')?.innerHTML ?? ''; + let price = + Number( + container + .querySelector(selector.price + ' .Tooltip-link') + ?.innerHTML.substring(1) + .replace(',', '') + ) ?? 0; + let type = container.querySelector(selector.title)?.textContent ?? ''; + let text = container.querySelector(selector.text)?.innerHTML ?? ''; let style: ItemStyle = ''; if (name.includes('Doppler')) { @@ -98,7 +206,7 @@ function getFloatItem(container: Element): Skinport.Listing | null { } let stickers: { name: string }[] = []; - let stickersDiv = container.querySelector('.ItemPreview-stickers'); + let stickersDiv = container.querySelector(selector.stickers); if (stickersDiv) { for (let sticker of stickersDiv.children) { let stickerName = sticker.children[0].getAttribute('alt'); @@ -142,18 +250,16 @@ function getFloatItem(container: Element): Skinport.Listing | null { }; } -async function addBuffPrice(item: Skinport.Listing, container: Element): Promise { - await loadMapping(); - let buff_name = handleSpecialStickerNames(createBuffName(item)); +async function getBuffPrice(item: Skinport.Listing): Promise<{ buff_name: string; priceListing: number; priceOrder: number }> { let priceMapping = await getPriceMapping(); + let buff_name = handleSpecialStickerNames(createBuffName(item)); let helperPrice: number | null = null; if (!priceMapping[buff_name] || !priceMapping[buff_name]['buff163'] || !priceMapping[buff_name]['buff163']['starting_at'] || !priceMapping[buff_name]['buff163']['highest_order']) { - console.debug(`[BetterFloat] No price mapping found for ${buff_name}: `, container); + console.debug(`[BetterFloat] No price mapping found for ${buff_name}`); helperPrice = await getInventoryHelperPrice(buff_name); } - let buff_id = await getBuffMapping(buff_name); // we cannot use the getItemPrice function here as it does not return the correct price for doppler skins let priceListing = 0; let priceOrder = 0; @@ -188,6 +294,58 @@ async function addBuffPrice(item: Skinport.Listing, container: Element): Promise priceOrder = priceOrder * currencyRate; } + return { buff_name, priceListing, priceOrder }; +} + +async function generateBuffContainer(container: HTMLElement, priceListing: number, priceOrder: number, currencySymbol: string, isItemPage: boolean = false) { + container.className += ' betterfloat-buffprice'; + let buffContainer = document.createElement('div'); + buffContainer.style.display = 'flex'; + buffContainer.style.marginTop = '5px'; + buffContainer.style.alignItems = 'center'; + if (!isItemPage) { + buffContainer.style.justifyContent = 'center'; + } + let buffImage = document.createElement('img'); + buffImage.setAttribute('src', runtimePublicURL + '/buff_favicon.png'); + buffImage.setAttribute('style', `height: 20px; margin-right: 5px; ${isItemPage ? 'margin-bottom: 1px;' : ''}`); + buffContainer.appendChild(buffImage); + let buffPrice = document.createElement('div'); + buffPrice.setAttribute('class', 'suggested-price betterfloat-buffprice'); + if (isItemPage) { + buffPrice.style.fontSize = '18px'; + } + let tooltipSpan = document.createElement('span'); + tooltipSpan.setAttribute('class', 'betterfloat-buff-tooltip'); + tooltipSpan.textContent = 'Bid: Highest buy order price; Ask: Lowest listing price'; + buffPrice.appendChild(tooltipSpan); + let buffPriceBid = document.createElement('span'); + buffPriceBid.setAttribute('style', 'color: orange;'); + buffPriceBid.textContent = `Bid ${currencySymbol}${priceOrder.toFixed(2)}`; + buffPrice.appendChild(buffPriceBid); + let buffPriceDivider = document.createElement('span'); + buffPriceDivider.setAttribute('style', 'color: gray;margin: 0 3px 0 3px;'); + buffPriceDivider.textContent = '|'; + buffPrice.appendChild(buffPriceDivider); + let buffPriceAsk = document.createElement('span'); + buffPriceAsk.setAttribute('style', 'color: greenyellow;'); + buffPriceAsk.textContent = `Ask ${currencySymbol}${priceListing.toFixed(2)}`; + buffPrice.appendChild(buffPriceAsk); + buffContainer.appendChild(buffPrice); + if (extensionSettings.spSteamPrice || isItemPage) { + let divider = document.createElement('div'); + container.after(buffContainer); + container.after(divider); + } else { + container.replaceWith(buffContainer); + } +} + +async function addBuffPrice(item: Skinport.Listing, container: Element): Promise { + await loadMapping(); + let { buff_name, priceListing, priceOrder } = await getBuffPrice(item); + let buff_id = await getBuffMapping(buff_name); + const presentationDiv = container.querySelector('.ItemPreview-mainAction'); if (presentationDiv) { let buffLink = document.createElement('a'); @@ -209,41 +367,7 @@ async function addBuffPrice(item: Skinport.Listing, container: Element): Promise const currencySymbol = tooltipLink.textContent?.charAt(0); let priceDiv = container.querySelector('.ItemPreview-oldPrice'); if (priceDiv && !container.querySelector('.betterfloat-buffprice')) { - priceDiv.className += ' betterfloat-buffprice'; - let buffContainer = document.createElement('div'); - buffContainer.style.display = 'flex'; - buffContainer.style.marginTop = '5px'; - buffContainer.style.justifyContent = 'center'; - let buffImage = document.createElement('img'); - buffImage.setAttribute('src', runtimePublicURL + '/buff_favicon.png'); - buffImage.setAttribute('style', 'height: 20px; margin-right: 5px'); - buffContainer.appendChild(buffImage); - let buffPrice = document.createElement('div'); - buffPrice.setAttribute('class', 'suggested-price betterfloat-buffprice'); - let tooltipSpan = document.createElement('span'); - tooltipSpan.setAttribute('class', 'betterfloat-buff-tooltip'); - tooltipSpan.textContent = 'Bid: Highest buy order price; Ask: Lowest listing price'; - buffPrice.appendChild(tooltipSpan); - let buffPriceBid = document.createElement('span'); - buffPriceBid.setAttribute('style', 'color: orange;'); - buffPriceBid.textContent = `Bid ${currencySymbol}${priceOrder.toFixed(2)}`; - buffPrice.appendChild(buffPriceBid); - let buffPriceDivider = document.createElement('span'); - buffPriceDivider.setAttribute('style', 'color: gray;margin: 0 3px 0 3px;'); - buffPriceDivider.textContent = '|'; - buffPrice.appendChild(buffPriceDivider); - let buffPriceAsk = document.createElement('span'); - buffPriceAsk.setAttribute('style', 'color: greenyellow;'); - buffPriceAsk.textContent = `Ask ${currencySymbol}${priceListing.toFixed(2)}`; - buffPrice.appendChild(buffPriceAsk); - buffContainer.appendChild(buffPrice); - if (extensionSettings.spSteamPrice) { - let divider = document.createElement('div'); - priceDiv.after(buffContainer); - priceDiv.after(divider); - } else { - priceDiv.replaceWith(buffContainer); - } + generateBuffContainer(priceDiv as HTMLElement, priceListing, priceOrder, currencySymbol ?? '$'); } if (extensionSettings.spBuffDifference) {