Skip to content

Instantly share code, notes, and snippets.

@GaryCraft
Last active October 8, 2026 06:30
Show Gist options
  • Select an option

  • Save GaryCraft/db848d0150129a8d41e8d50ffaf4dd79 to your computer and use it in GitHub Desktop.

Select an option

Save GaryCraft/db848d0150129a8d41e8d50ffaf4dd79 to your computer and use it in GitHub Desktop.
DogOnlineUtils V0.1.x
// ==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