Last active
October 8, 2026 06:30
-
-
Save GaryCraft/db848d0150129a8d41e8d50ffaf4dd79 to your computer and use it in GitHub Desktop.
DogOnlineUtils V0.1.x
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name DogOnlineUtils | |
| // @namespace http://tampermonkey.net/ | |
| // @version 2026-10-08 | |
| // @description try to take over the world! | |
| // @author stargazer | |
| // @match https://dogonline.net/* | |
| // @icon https://www.google.com/s2/favicons?sz=64&domain=dogonline.net | |
| // @grant none | |
| // ==/UserScript== | |
| (async function() { | |
| 'use strict'; | |
| // --- Configuration & LocalStorage Keys --- | |
| const STORAGE_KEY_POS = 'dogUtils_position'; | |
| const STORAGE_KEY_PINNED = 'dogUtils_pinned'; | |
| const STORAGE_KEY_MINIMIZED = 'dogUtils_minimized'; | |
| const STORAGE_KEY_SORT = 'dogUtils_sort'; | |
| // --- State --- | |
| let gameData = { inventory: [], marketConditions: { message: "Loading..." } }; | |
| let pinnedItemIds = JSON.parse(localStorage.getItem(STORAGE_KEY_PINNED) || '[]'); | |
| let isMinimized = JSON.parse(localStorage.getItem(STORAGE_KEY_MINIMIZED) || 'false'); | |
| let menuPos = JSON.parse(localStorage.getItem(STORAGE_KEY_POS) || '{"top": 100, "left": 100, "width": 320, "height": 450}'); | |
| let currentSort = localStorage.getItem(STORAGE_KEY_SORT) || 'name_asc'; | |
| // --- Fetch Game Data --- | |
| async function fetchAuctionData() { | |
| try { | |
| const response = await fetch("https://dogonline.net/api/trpc/auction.getState?batch=1&input=%7B%220%22%3A%7B%22json%22%3Anull%2C%22meta%22%3A%7B%22values%22%3A%5B%22undefined%22%5D%2C%22v%22%3A1%7D%7D%7D", { | |
| "credentials": "include", | |
| "headers": { | |
| "User-Agent": navigator.userAgent, | |
| "Accept": "*/*", | |
| "Sec-Fetch-Dest": "empty", | |
| "Sec-Fetch-Mode": "cors", | |
| "Sec-Fetch-Site": "same-origin", | |
| "Pragma": "no-cache", | |
| "Cache-Control": "no-cache" | |
| }, | |
| "referrer": "https://dogonline.net/auction", | |
| "method": "GET", | |
| "mode": "cors" | |
| }); | |
| const rawJson = await response.json(); | |
| if (rawJson && rawJson[0] && rawJson[0].result && rawJson[0].result.data && rawJson[0].result.data.json) { | |
| gameData = rawJson[0].result.data.json; | |
| } | |
| } catch(err) { | |
| console.error("DogOnlineUtils: Unable to fetch auctions state", err); | |
| gameData.marketConditions.message = "Fetch failed"; | |
| } | |
| } | |
| // --- CSS Injection --- | |
| function injectStyles() { | |
| const style = document.createElement('style'); | |
| style.innerHTML = ` | |
| :root { | |
| --du-bg: #ffffff; | |
| --du-text: #333333; | |
| --du-border: rgba(0,0,0,0.15); | |
| --du-header: #f5f5f5; | |
| --du-item-bg: #f9f9f9; | |
| --du-hover: #e0e0e0; | |
| } | |
| @media (prefers-color-scheme: dark) { | |
| :root { | |
| --du-bg: #1e1e24; | |
| --du-text: #e0e0e0; | |
| --du-border: rgba(255,255,255,0.15); | |
| --du-header: #2b2b36; | |
| --du-item-bg: #2a2a35; | |
| --du-hover: #3f3f4e; | |
| } | |
| } | |
| #dog_utils_menu { | |
| position: fixed; | |
| background-color: var(--du-bg); | |
| color: var(--du-text); | |
| font-family: inherit; | |
| font-size: inherit; | |
| border: 1px solid var(--du-border); | |
| border-radius: 8px; | |
| box-shadow: 0 4px 15px rgba(0,0,0,0.3); | |
| z-index: 999999; | |
| display: flex; | |
| flex-direction: column; | |
| resize: both; | |
| overflow: hidden; | |
| min-width: 280px; | |
| min-height: 40px; | |
| /* Crucial viewport constraints */ | |
| max-width: 95vw; | |
| max-height: 95vh; | |
| } | |
| #dog_utils_menu.minimized { | |
| resize: none !important; | |
| height: auto !important; | |
| min-height: unset; | |
| } | |
| #dog_utils_header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 10px; | |
| background-color: var(--du-header); | |
| cursor: grab; | |
| user-select: none; | |
| } | |
| #dog_utils_header:active { cursor: grabbing; } | |
| #dog_utils_header_title { | |
| font-weight: bold; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| #dog_market_status_bar { | |
| background: rgba(255,152,0,0.1); | |
| color: #ff9800; | |
| font-size: 0.85em; | |
| padding: 4px 10px; | |
| text-align: center; | |
| border-bottom: 1px solid var(--du-border); | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| } | |
| .dog_btn_group { display: flex; gap: 6px; align-items: center; } | |
| .dog_btn, .dog_select { | |
| background: var(--du-item-bg); | |
| color: inherit; | |
| border: 1px solid var(--du-border); | |
| border-radius: 4px; | |
| padding: 4px 6px; | |
| font-family: inherit; | |
| font-size: 0.9em; | |
| cursor: pointer; | |
| transition: background 0.2s, opacity 0.2s; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| min-width: 24px; | |
| min-height: 24px; | |
| } | |
| .dog_btn:hover, .dog_select:hover { background: var(--du-hover); } | |
| .dog_btn img { | |
| width: 16px; | |
| height: 16px; | |
| object-fit: contain; | |
| display: block; | |
| } | |
| #dog_utils_body { | |
| display: flex; | |
| flex-direction: column; | |
| flex: 1; | |
| overflow: hidden; | |
| } | |
| #dog_pinned_area { | |
| padding: 10px; | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 8px; | |
| border-bottom: 1px solid var(--du-border); | |
| /* Allow pinned area to scroll if too many items are pinned */ | |
| max-height: 35%; | |
| overflow-y: auto; | |
| } | |
| .dog_pinned_item { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| background: var(--du-item-bg); | |
| padding: 4px 8px; | |
| border-radius: 6px; | |
| border: 1px solid var(--du-border); | |
| font-size: 0.85em; | |
| } | |
| .dog_pinned_item img, .dog_inv_item_info img { | |
| width: 24px; | |
| height: 24px; | |
| object-fit: contain; | |
| } | |
| .dog_val { color: #4caf50; font-weight: bold; } | |
| #dog_inventory_section { | |
| display: flex; | |
| flex-direction: column; | |
| flex: 1; | |
| overflow: hidden; | |
| } | |
| #dog_controls_bar { | |
| display: flex; | |
| justify-content: space-between; | |
| padding: 8px 10px; | |
| background: var(--du-header); | |
| border-bottom: 1px solid var(--du-border); | |
| align-items: center; | |
| } | |
| #dog_inventory_list { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 10px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 5px; | |
| } | |
| .dog_inv_item { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| background: var(--du-item-bg); | |
| padding: 6px 8px; | |
| border-radius: 4px; | |
| border: 1px solid transparent; | |
| } | |
| .dog_inv_item:hover { border-color: var(--du-border); } | |
| .dog_inv_item_info { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| /* Scrollbar styling for both areas */ | |
| #dog_pinned_area::-webkit-scrollbar, #dog_inventory_list::-webkit-scrollbar { width: 6px; } | |
| #dog_pinned_area::-webkit-scrollbar-thumb, #dog_inventory_list::-webkit-scrollbar-thumb { background: var(--du-border); border-radius: 4px; } | |
| `; | |
| document.head.appendChild(style); | |
| } | |
| // --- UI Building & Logic --- | |
| function initUI() { | |
| const menu = document.createElement('div'); | |
| menu.id = 'dog_utils_menu'; | |
| if (isMinimized) menu.classList.add('minimized'); | |
| menu.innerHTML = ` | |
| <div id="dog_utils_header"> | |
| <div id="dog_utils_header_title"> | |
| <span>DogUtils</span> | |
| </div> | |
| <div class="dog_btn_group"> | |
| <button class="dog_btn" id="dog_auction_btn" title="Go to Auction"> | |
| <img src="/img/map_auction.png" alt="Auction"> | |
| </button> | |
| <button class="dog_btn" id="dog_refresh_btn" title="Refresh Data"> | |
| <img src="https://dogonline.net/img/paw.gif" alt="Refresh"> | |
| </button> | |
| <button class="dog_btn" id="dog_minimize_btn" title="Minimize/Expand" style="font-weight:bold;"> | |
| ${isMinimized ? '+' : '−'} | |
| </button> | |
| </div> | |
| </div> | |
| <div id="dog_market_status_bar"> | |
| <span>Market: <strong id="dog_market_status_text">${gameData.marketConditions.message || 'Unknown'}</strong></span> | |
| </div> | |
| <div id="dog_utils_body" style="display: ${isMinimized ? 'none' : 'flex'};"> | |
| <div id="dog_pinned_area"></div> | |
| <div id="dog_inventory_section"> | |
| <div id="dog_controls_bar"> | |
| <label style="font-size: 0.9em; font-weight: bold;">Inventory</label> | |
| <select id="dog_sort_select" class="dog_select"> | |
| <option value="name_asc">A-Z</option> | |
| <option value="name_desc">Z-A</option> | |
| <option value="price_desc">Price (High to Low)</option> | |
| <option value="price_asc">Price (Low to High)</option> | |
| <option value="qty_desc">Quantity (High to Low)</option> | |
| <option value="qty_asc">Quantity (Low to High)</option> | |
| </select> | |
| </div> | |
| <div id="dog_inventory_list"></div> | |
| </div> | |
| </div> | |
| `; | |
| document.body.appendChild(menu); | |
| const header = document.getElementById('dog_utils_header'); | |
| const body = document.getElementById('dog_utils_body'); | |
| const minBtn = document.getElementById('dog_minimize_btn'); | |
| const refreshBtn = document.getElementById('dog_refresh_btn'); | |
| const auctionBtn = document.getElementById('dog_auction_btn'); | |
| const sortSelect = document.getElementById('dog_sort_select'); | |
| // Apply Saved Dimensions and Position | |
| menu.style.top = menuPos.top + 'px'; | |
| menu.style.left = menuPos.left + 'px'; | |
| if (menuPos.width) menu.style.width = menuPos.width + 'px'; | |
| if (menuPos.height) menu.style.height = menuPos.height + 'px'; | |
| // Event: Minimize | |
| minBtn.onclick = () => { | |
| isMinimized = !isMinimized; | |
| body.style.display = isMinimized ? 'none' : 'flex'; | |
| minBtn.innerText = isMinimized ? '+' : '−'; | |
| menu.classList.toggle('minimized', isMinimized); | |
| localStorage.setItem(STORAGE_KEY_MINIMIZED, JSON.stringify(isMinimized)); | |
| clampToScreen(menu); // Re-evaluate bounds after resizing | |
| }; | |
| // Event: Go to Auction | |
| auctionBtn.onclick = () => { | |
| window.location.href = 'https://dogonline.net/auction'; | |
| }; | |
| // Event: Refresh | |
| refreshBtn.onclick = async () => { | |
| refreshBtn.style.opacity = '0.5'; | |
| await fetchAuctionData(); | |
| document.getElementById('dog_market_status_text').innerText = gameData.marketConditions?.message || 'Unknown'; | |
| renderData(); | |
| refreshBtn.style.opacity = '1'; | |
| }; | |
| // Event: Sorting | |
| sortSelect.value = currentSort; | |
| sortSelect.onchange = (e) => { | |
| currentSort = e.target.value; | |
| localStorage.setItem(STORAGE_KEY_SORT, currentSort); | |
| renderData(); | |
| }; | |
| // Observer for saving resize dimensions | |
| const resizeObserver = new ResizeObserver(() => { | |
| if (!isMinimized) { | |
| // Ignore dimensions if they are clamped by max-width/height | |
| menuPos.width = menu.offsetWidth; | |
| menuPos.height = menu.offsetHeight; | |
| localStorage.setItem(STORAGE_KEY_POS, JSON.stringify(menuPos)); | |
| } | |
| }); | |
| resizeObserver.observe(menu); | |
| // Render Data | |
| renderData(); | |
| // Initialize Dragging and bounds clamping | |
| setupDraggable(menu, header); | |
| clampToScreen(menu); | |
| // Keep it on screen when resizing window | |
| window.addEventListener('resize', () => clampToScreen(menu)); | |
| } | |
| function renderData() { | |
| const pinnedArea = document.getElementById('dog_pinned_area'); | |
| const invList = document.getElementById('dog_inventory_list'); | |
| let inventory = [...(gameData.inventory || [])]; | |
| pinnedArea.innerHTML = ''; | |
| invList.innerHTML = ''; | |
| if (pinnedItemIds.length === 0) { | |
| pinnedArea.innerHTML = `<span style="color:inherit; opacity:0.6; font-size:0.85em; font-style:italic;">No items pinned.</span>`; | |
| } | |
| // --- Apply Sorting Logic --- | |
| inventory.sort((a, b) => { | |
| if (currentSort === 'name_asc') return a.name.localeCompare(b.name); | |
| if (currentSort === 'name_desc') return b.name.localeCompare(a.name); | |
| if (currentSort === 'price_desc') return b.price - a.price; | |
| if (currentSort === 'price_asc') return a.price - b.price; | |
| if (currentSort === 'qty_desc') return b.count - a.count; | |
| if (currentSort === 'qty_asc') return a.count - b.count; | |
| return 0; | |
| }); | |
| // --- Render Items --- | |
| inventory.forEach(item => { | |
| const isPinned = pinnedItemIds.includes(item.id); | |
| const imgSrc = item.image.startsWith('http') ? item.image : `/${item.image}`; | |
| if (isPinned) { | |
| const pItem = document.createElement('div'); | |
| pItem.className = 'dog_pinned_item'; | |
| pItem.innerHTML = ` | |
| <img src="${imgSrc}" alt="${item.name}" title="${item.name}"> | |
| <span>x${item.count}</span> | |
| <span class="dog_val">$${item.price}</span> | |
| `; | |
| pinnedArea.appendChild(pItem); | |
| } | |
| const iItem = document.createElement('div'); | |
| iItem.className = 'dog_inv_item'; | |
| iItem.innerHTML = ` | |
| <div class="dog_inv_item_info"> | |
| <img src="${imgSrc}" alt="${item.name}"> | |
| <span style="font-size:0.9em; line-height: 1.2;"> | |
| ${item.name} <br> | |
| <span style="opacity: 0.7; font-size:0.85em;">x${item.count} | <span class="dog_val">$${item.price}</span></span> | |
| </span> | |
| </div> | |
| <button class="dog_btn" style="background:${isPinned ? '#d32f2f' : '#388e3c'}; color:white; border:none; padding:4px 8px;"> | |
| ${isPinned ? 'Unpin' : 'Pin'} | |
| </button> | |
| `; | |
| const pinBtn = iItem.querySelector('button'); | |
| pinBtn.onclick = () => { | |
| if (isPinned) { | |
| pinnedItemIds = pinnedItemIds.filter(id => id !== item.id); | |
| } else { | |
| pinnedItemIds.push(item.id); | |
| } | |
| localStorage.setItem(STORAGE_KEY_PINNED, JSON.stringify(pinnedItemIds)); | |
| renderData(); | |
| }; | |
| invList.appendChild(iItem); | |
| }); | |
| } | |
| // --- Drag & Drop & Bounds Logic --- | |
| function setupDraggable(element, handle) { | |
| let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; | |
| handle.onmousedown = (e) => { | |
| if (e.target.closest('.dog_btn')) return; | |
| e.preventDefault(); | |
| pos3 = e.clientX; | |
| pos4 = e.clientY; | |
| document.onmouseup = closeDragElement; | |
| document.onmousemove = elementDrag; | |
| }; | |
| function elementDrag(e) { | |
| e.preventDefault(); | |
| pos1 = pos3 - e.clientX; | |
| pos2 = pos4 - e.clientY; | |
| pos3 = e.clientX; | |
| pos4 = e.clientY; | |
| element.style.top = (element.offsetTop - pos2) + "px"; | |
| element.style.left = (element.offsetLeft - pos1) + "px"; | |
| } | |
| function closeDragElement() { | |
| document.onmouseup = null; | |
| document.onmousemove = null; | |
| clampToScreen(element); | |
| menuPos.top = element.offsetTop; | |
| menuPos.left = element.offsetLeft; | |
| localStorage.setItem(STORAGE_KEY_POS, JSON.stringify(menuPos)); | |
| } | |
| } | |
| function clampToScreen(element) { | |
| // Enforce physical constraints if screen was resized smaller than current menu size | |
| if (element.offsetHeight > window.innerHeight) { | |
| element.style.height = (window.innerHeight - 20) + "px"; | |
| } | |
| if (element.offsetWidth > window.innerWidth) { | |
| element.style.width = (window.innerWidth - 20) + "px"; | |
| } | |
| const rect = element.getBoundingClientRect(); | |
| let newTop = rect.top; | |
| let newLeft = rect.left; | |
| // Prevent off-screen placement | |
| const maxTop = Math.max(0, window.innerHeight - rect.height); | |
| const maxLeft = Math.max(0, window.innerWidth - rect.width); | |
| if (newTop < 0) newTop = 0; | |
| if (newLeft < 0) newLeft = 0; | |
| if (newTop > maxTop) newTop = maxTop; | |
| if (newLeft > maxLeft) newLeft = maxLeft; | |
| element.style.top = newTop + "px"; | |
| element.style.left = newLeft + "px"; | |
| } | |
| // --- Execution --- | |
| await fetchAuctionData(); | |
| injectStyles(); | |
| if (document.body) { | |
| initUI(); | |
| } else { | |
| window.addEventListener('DOMContentLoaded', initUI); | |
| } | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment