init
This commit is contained in:
42
.gitea/workflows/update_data.yaml
Normal file
42
.gitea/workflows/update_data.yaml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: Update Data
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 */6 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-data:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: nikolaik/python-nodejs:python3.12-nodejs22-slim
|
||||||
|
steps:
|
||||||
|
- name: Install system dependencies
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y git
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||||
|
|
||||||
|
- name: Run download script
|
||||||
|
run: python download_data.py
|
||||||
|
|
||||||
|
- name: Check for changes and commit
|
||||||
|
run: |
|
||||||
|
git config --global user.name 'Gitea Actions'
|
||||||
|
git config --global user.email 'actions@noreply.gitea.io'
|
||||||
|
|
||||||
|
# Check if new_data.json has changed
|
||||||
|
if [[ -n $(git status -s chrome-extension/new_data.json) ]]; then
|
||||||
|
echo "Changes detected in new_data.json"
|
||||||
|
git add chrome-extension/new_data.json
|
||||||
|
git commit -m "Auto-update new_data.json"
|
||||||
|
git push
|
||||||
|
else
|
||||||
|
echo "No changes detected"
|
||||||
|
fi
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
response.html
|
||||||
18
README.md
Normal file
18
README.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# RuneScape Wiki Ely Prices
|
||||||
|
|
||||||
|
This extension integrates Ely.gg price data directly into the official RuneScape Wiki, allowing players to view real-time market information seamlessly while browsing data.
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Enhance your RuneScape Wiki experience with live pricing data. This extension fetches the latest street prices from Ely.gg and displays them right next to the item title on the Wiki page. It helps you stay informed about the current market value, whether you are checking for an upgrade or planning your next flip.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* **Live Price Display**: Shows the most recent transaction price, including whether it was an Instant Buy (inb) or Instant Sell (ins), and the date of the transaction.
|
||||||
|
* **Sales History Popup**: Click on the price to open a details popup showing the last 10 recorded sales for that item.
|
||||||
|
* **Seamless Integration**: Designed to look optimal on the Wiki without cluttering the interface.
|
||||||
|
* **Custom Data Source**: Includes an options menu to configure a custom data URL if needed.
|
||||||
|
|
||||||
|
## Privacy
|
||||||
|
|
||||||
|
This extension does not collect any personal data. It only fetches public price information corresponding to the Wiki page you are currently viewing.
|
||||||
37
build_release.py
Normal file
37
build_release.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import os
|
||||||
|
import zipfile
|
||||||
|
import shutil
|
||||||
|
import json
|
||||||
|
|
||||||
|
def create_zip(source_dir, output_filename, manifest_filename='manifest.json'):
|
||||||
|
with zipfile.ZipFile(output_filename, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
||||||
|
for root, dirs, files in os.walk(source_dir):
|
||||||
|
for file in files:
|
||||||
|
if file.startswith('manifest') and file.endswith('.json'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
file_path = os.path.join(root, file)
|
||||||
|
arcname = os.path.relpath(file_path, source_dir)
|
||||||
|
zipf.write(file_path, arcname)
|
||||||
|
|
||||||
|
manifest_path = os.path.join(source_dir, manifest_filename)
|
||||||
|
zipf.write(manifest_path, 'manifest.json')
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if os.path.exists('release'):
|
||||||
|
shutil.rmtree('release')
|
||||||
|
os.makedirs('release')
|
||||||
|
|
||||||
|
# Userscript
|
||||||
|
shutil.copy('userscript.js', 'release/ely-userscript.user.js')
|
||||||
|
|
||||||
|
# Chrome Extension (Manifest V3)
|
||||||
|
create_zip('chrome-extension', 'release/ely-extension-chrome.zip', 'manifest-v3.json')
|
||||||
|
|
||||||
|
# Firefox Extension (Manifest V2)
|
||||||
|
create_zip('chrome-extension', 'release/ely-extension-firefox.zip', 'manifest.json')
|
||||||
|
|
||||||
|
print("Build complete in /release folder.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
35
chrome-extension/background.js
Normal file
35
chrome-extension/background.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// CORS
|
||||||
|
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||||
|
if (request.action === 'fetchPrice') {
|
||||||
|
fetch(`https://www.ely.gg/chart/${request.itemId}/prices`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => sendResponse({ success: true, data }))
|
||||||
|
.catch(error => sendResponse({ success: false, error: error.message }));
|
||||||
|
return true;
|
||||||
|
} else if (request.action === 'fetchData') {
|
||||||
|
fetch(request.url)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => sendResponse({ success: true, data }))
|
||||||
|
.catch(error => sendResponse({ success: false, error: error.message }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//Firefox compatibility
|
||||||
|
if (typeof browser !== 'undefined') {
|
||||||
|
browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||||
|
if (request.action === 'fetchPrice') {
|
||||||
|
fetch(`https://www.ely.gg/chart/${request.itemId}/prices`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => sendResponse({ success: true, data }))
|
||||||
|
.catch(error => sendResponse({ success: false, error: error.message }));
|
||||||
|
return true;
|
||||||
|
} else if (request.action === 'fetchData') {
|
||||||
|
fetch(request.url)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => sendResponse({ success: true, data }))
|
||||||
|
.catch(error => sendResponse({ success: false, error: error.message }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
257
chrome-extension/content.js
Normal file
257
chrome-extension/content.js
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
(async function() {
|
||||||
|
const browserAPI = typeof browser !== 'undefined' ? browser : chrome;
|
||||||
|
try {
|
||||||
|
const getStorage = (key) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const api = (typeof chrome !== 'undefined' && chrome.storage) ? chrome : browserAPI;
|
||||||
|
if (api.storage && api.storage.sync) {
|
||||||
|
api.storage.sync.get([key], (result) => {
|
||||||
|
resolve(result ? result[key] : null);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendMessage = (message) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const api = (typeof chrome !== 'undefined' && chrome.runtime) ? chrome : browserAPI;
|
||||||
|
try {
|
||||||
|
api.runtime.sendMessage(message, (response) => {
|
||||||
|
// Chrome callback
|
||||||
|
if (api.runtime.lastError) {
|
||||||
|
resolve(null);
|
||||||
|
} else {
|
||||||
|
resolve(response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch(e) {
|
||||||
|
if (browserAPI && browserAPI.runtime && browserAPI.runtime.sendMessage) {
|
||||||
|
browserAPI.runtime.sendMessage(message).then(resolve).catch(() => resolve(null));
|
||||||
|
} else {
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const customUrl = await getStorage('customDataUrl');
|
||||||
|
let itemData;
|
||||||
|
|
||||||
|
if (customUrl) {
|
||||||
|
const response = await sendMessage({
|
||||||
|
action: 'fetchData',
|
||||||
|
url: customUrl
|
||||||
|
});
|
||||||
|
if (response && response.success) {
|
||||||
|
itemData = response.data;
|
||||||
|
} else {
|
||||||
|
console.error('Failed to fetch custom data, falling back to local');
|
||||||
|
const localResponse = await fetch(browserAPI.runtime.getURL('new_data.json'));
|
||||||
|
itemData = await localResponse.json();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const response = await fetch(browserAPI.runtime.getURL('new_data.json'));
|
||||||
|
itemData = await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
const pageTitleElement = document.querySelector('.mw-page-title-main');
|
||||||
|
if (!pageTitleElement) return;
|
||||||
|
|
||||||
|
const originalCursor = pageTitleElement.style.cursor;
|
||||||
|
|
||||||
|
const pageTitle = pageTitleElement.textContent.trim();
|
||||||
|
|
||||||
|
let itemId = null;
|
||||||
|
|
||||||
|
if (itemData[pageTitle]) {
|
||||||
|
itemId = itemData[pageTitle];
|
||||||
|
} else {
|
||||||
|
const urlPath = window.location.pathname;
|
||||||
|
const urlTitle = urlPath.replace('/w/', '').replace(/_/g, ' ');
|
||||||
|
|
||||||
|
if (itemData[urlTitle]) {
|
||||||
|
itemId = itemData[urlTitle];
|
||||||
|
} else {
|
||||||
|
for (const [itemName, id] of Object.entries(itemData)) {
|
||||||
|
const itemNameBase = itemName.split('(')[0].trim();
|
||||||
|
if (itemName.toLowerCase() === pageTitle.toLowerCase() ||
|
||||||
|
itemName.toLowerCase() === urlTitle.toLowerCase() ||
|
||||||
|
itemNameBase.toLowerCase() === pageTitle.toLowerCase() ||
|
||||||
|
itemNameBase.toLowerCase() === urlTitle.toLowerCase()) {
|
||||||
|
itemId = id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemId) {
|
||||||
|
try {
|
||||||
|
const response = await browserAPI.runtime.sendMessage({
|
||||||
|
action: 'fetchPrice',
|
||||||
|
itemId: itemId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.success && response.data.items && response.data.items.length > 0) {
|
||||||
|
const lastItem = response.data.items[response.data.items.length - 1];
|
||||||
|
const price = lastItem.price.toLocaleString();
|
||||||
|
const date = new Date(lastItem.date).toLocaleDateString();
|
||||||
|
const saleType = lastItem.purchase;
|
||||||
|
var saleTypeShort = saleType;
|
||||||
|
if (saleType.toLowerCase() === "sold") {
|
||||||
|
saleTypeShort = "inb"
|
||||||
|
} else if (saleType.toLowerCase() === "bought") {
|
||||||
|
saleTypeShort = "ins"
|
||||||
|
}
|
||||||
|
|
||||||
|
const priceDisplay = document.createElement('span');
|
||||||
|
priceDisplay.textContent = ` (${price} gp - ${saleTypeShort} - ${date})`;
|
||||||
|
priceDisplay.style.color = '#5a8c5a';
|
||||||
|
priceDisplay.style.fontWeight = 'bold';
|
||||||
|
priceDisplay.style.fontSize = '0.9em';
|
||||||
|
|
||||||
|
pageTitleElement.appendChild(priceDisplay);
|
||||||
|
}
|
||||||
|
} catch (priceError) {
|
||||||
|
console.error('Error fetching price data:', priceError);
|
||||||
|
|
||||||
|
const idDisplay = document.createElement('span');
|
||||||
|
idDisplay.textContent = ` (ID: ${itemId}) | Failed to get price.`;
|
||||||
|
idDisplay.style.color = '#5a8c5a';
|
||||||
|
idDisplay.style.fontWeight = 'bold';
|
||||||
|
idDisplay.style.fontSize = '0.9em';
|
||||||
|
|
||||||
|
pageTitleElement.appendChild(idDisplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
pageTitleElement.style.cursor = 'pointer';
|
||||||
|
pageTitleElement.addEventListener('click', async () => {
|
||||||
|
try {
|
||||||
|
const response = await sendMessage({
|
||||||
|
action: 'fetchPrice',
|
||||||
|
itemId: itemId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.success && response.data.items && response.data.items.length > 0) {
|
||||||
|
showSalesPopup(response.data.items, itemId);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching sales data:', error);
|
||||||
|
window.open(`https://www.ely.gg/view_item/${itemId}`, '_blank');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading item data:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSalesPopup(items, itemId) {
|
||||||
|
const existingPopup = document.getElementById('ely-sales-popup');
|
||||||
|
if (existingPopup) {
|
||||||
|
existingPopup.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const popup = document.createElement('div');
|
||||||
|
popup.id = 'ely-sales-popup';
|
||||||
|
popup.style.position = 'fixed';
|
||||||
|
popup.style.top = '50%';
|
||||||
|
popup.style.left = '50%';
|
||||||
|
popup.style.transform = 'translate(-50%, -50%)';
|
||||||
|
popup.style.backgroundColor = '#fff';
|
||||||
|
popup.style.border = '1px solid #ccc';
|
||||||
|
popup.style.borderRadius = '8px';
|
||||||
|
popup.style.boxShadow = '0 4px 8px rgba(0,0,0,0.1)';
|
||||||
|
popup.style.padding = '20px';
|
||||||
|
popup.style.zIndex = '10000';
|
||||||
|
popup.style.maxWidth = '500px';
|
||||||
|
popup.style.width = '90%';
|
||||||
|
popup.style.maxHeight = '70vh';
|
||||||
|
popup.style.overflowY = 'auto';
|
||||||
|
|
||||||
|
const header = document.createElement('h3');
|
||||||
|
header.textContent = 'Recent Sales';
|
||||||
|
header.style.marginTop = '0';
|
||||||
|
header.style.marginBottom = '15px';
|
||||||
|
header.style.color = '#333';
|
||||||
|
popup.appendChild(header);
|
||||||
|
|
||||||
|
const salesList = document.createElement('div');
|
||||||
|
salesList.style.marginBottom = '20px';
|
||||||
|
|
||||||
|
const recentSales = items.slice(-10).reverse();
|
||||||
|
|
||||||
|
recentSales.forEach(sale => {
|
||||||
|
const saleItem = document.createElement('div');
|
||||||
|
saleItem.style.display = 'flex';
|
||||||
|
saleItem.style.justifyContent = 'space-between';
|
||||||
|
saleItem.style.padding = '8px 0';
|
||||||
|
saleItem.style.borderBottom = '1px solid #eee';
|
||||||
|
|
||||||
|
const price = document.createElement('span');
|
||||||
|
price.textContent = `${parseInt(sale.price).toLocaleString()} gp`;
|
||||||
|
price.style.fontWeight = 'bold';
|
||||||
|
|
||||||
|
const date = document.createElement('span');
|
||||||
|
date.textContent = new Date(sale.date).toLocaleDateString();
|
||||||
|
date.style.color = '#666';
|
||||||
|
|
||||||
|
const type = document.createElement('span');
|
||||||
|
type.textContent = sale.purchase.toLowerCase() === 'sold' ? 'inb' : 'ins';
|
||||||
|
type.style.color = sale.purchase === 'sold' ? '#d9534f' : '#5cb85c';
|
||||||
|
type.style.fontWeight = 'bold';
|
||||||
|
|
||||||
|
saleItem.appendChild(price);
|
||||||
|
saleItem.appendChild(type);
|
||||||
|
saleItem.appendChild(date);
|
||||||
|
salesList.appendChild(saleItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
popup.appendChild(salesList);
|
||||||
|
|
||||||
|
const buttonContainer = document.createElement('div');
|
||||||
|
buttonContainer.style.display = 'flex';
|
||||||
|
buttonContainer.style.justifyContent = 'space-between';
|
||||||
|
buttonContainer.style.marginTop = '10px';
|
||||||
|
|
||||||
|
const elyButton = document.createElement('button');
|
||||||
|
elyButton.textContent = 'Go to Ely';
|
||||||
|
elyButton.style.backgroundColor = '#5a8c5a';
|
||||||
|
elyButton.style.color = 'white';
|
||||||
|
elyButton.style.border = 'none';
|
||||||
|
elyButton.style.padding = '8px 16px';
|
||||||
|
elyButton.style.borderRadius = '4px';
|
||||||
|
elyButton.style.cursor = 'pointer';
|
||||||
|
elyButton.addEventListener('click', () => {
|
||||||
|
window.open(`https://www.ely.gg/view_item/${itemId}`, '_blank');
|
||||||
|
popup.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
const closeButton = document.createElement('button');
|
||||||
|
closeButton.textContent = 'Close';
|
||||||
|
closeButton.style.backgroundColor = '#6c757d';
|
||||||
|
closeButton.style.color = 'white';
|
||||||
|
closeButton.style.border = 'none';
|
||||||
|
closeButton.style.padding = '8px 16px';
|
||||||
|
closeButton.style.borderRadius = '4px';
|
||||||
|
closeButton.style.cursor = 'pointer';
|
||||||
|
closeButton.addEventListener('click', () => {
|
||||||
|
popup.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonContainer.appendChild(elyButton);
|
||||||
|
buttonContainer.appendChild(closeButton);
|
||||||
|
popup.appendChild(buttonContainer);
|
||||||
|
|
||||||
|
document.body.appendChild(popup);
|
||||||
|
|
||||||
|
document.addEventListener('click', function closePopup(e) {
|
||||||
|
if (!popup.contains(e.target) && e.target !== pageTitleElement) {
|
||||||
|
popup.remove();
|
||||||
|
document.removeEventListener('click', closePopup);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
BIN
chrome-extension/img/128.png
Normal file
BIN
chrome-extension/img/128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
chrome-extension/img/64.png
Normal file
BIN
chrome-extension/img/64.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
38
chrome-extension/manifest-firefox.json
Normal file
38
chrome-extension/manifest-firefox.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 2,
|
||||||
|
"name": "RS Wiki Ely.gg Price Display",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "Displays item IDs from ely.gg on RuneScape Wiki pages",
|
||||||
|
"permissions": [
|
||||||
|
"storage",
|
||||||
|
"https://www.ely.gg/*"
|
||||||
|
],
|
||||||
|
"icons": {
|
||||||
|
"48": "img/64.png",
|
||||||
|
"96": "img/128.png"
|
||||||
|
},
|
||||||
|
"browser_action": {
|
||||||
|
"default_icon": "img/64.png",
|
||||||
|
"default_title": "Configure Ely Extension",
|
||||||
|
"default_popup": "options.html"
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"scripts": [
|
||||||
|
"background.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"matches": [
|
||||||
|
"https://runescape.wiki/*"
|
||||||
|
],
|
||||||
|
"js": [
|
||||||
|
"content.js"
|
||||||
|
],
|
||||||
|
"run_at": "document_idle"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"web_accessible_resources": [
|
||||||
|
"new_data.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
51
chrome-extension/manifest-v3.json
Normal file
51
chrome-extension/manifest-v3.json
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 3,
|
||||||
|
"name": "RS Wiki Ely.gg Price Display",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "Displays item IDs from ely.gg on RuneScape Wiki pages",
|
||||||
|
"permissions": [
|
||||||
|
"storage"
|
||||||
|
],
|
||||||
|
"host_permissions": [
|
||||||
|
"https://www.ely.gg/*",
|
||||||
|
"http://*/*",
|
||||||
|
"https://*/*"
|
||||||
|
],
|
||||||
|
"icons": {
|
||||||
|
"48": "img/64.png",
|
||||||
|
"96": "img/128.png"
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"default_icon": "img/64.png",
|
||||||
|
"default_title": "Configure Ely Extension",
|
||||||
|
"default_popup": "options.html"
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"service_worker": "background.js"
|
||||||
|
},
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"matches": [
|
||||||
|
"https://runescape.wiki/*"
|
||||||
|
],
|
||||||
|
"js": [
|
||||||
|
"content.js"
|
||||||
|
],
|
||||||
|
"run_at": "document_idle"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"web_accessible_resources": [
|
||||||
|
{
|
||||||
|
"resources": [
|
||||||
|
"new_data.json"
|
||||||
|
],
|
||||||
|
"matches": [
|
||||||
|
"https://runescape.wiki/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options_ui": {
|
||||||
|
"page": "options.html",
|
||||||
|
"open_in_tab": false
|
||||||
|
}
|
||||||
|
}
|
||||||
44
chrome-extension/manifest.json
Normal file
44
chrome-extension/manifest.json
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 2,
|
||||||
|
"name": "RS Wiki Ely.gg Price Display",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "Displays item IDs from ely.gg on RuneScape Wiki pages",
|
||||||
|
"permissions": [
|
||||||
|
"storage",
|
||||||
|
"https://www.ely.gg/*",
|
||||||
|
"http://*/*",
|
||||||
|
"https://*/*"
|
||||||
|
],
|
||||||
|
"icons": {
|
||||||
|
"48": "img/64.png",
|
||||||
|
"96": "img/128.png"
|
||||||
|
},
|
||||||
|
"browser_action": {
|
||||||
|
"default_icon": "img/64.png",
|
||||||
|
"default_title": "Configure Ely Extension",
|
||||||
|
"default_popup": "options.html"
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"scripts": [
|
||||||
|
"background.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"matches": [
|
||||||
|
"https://runescape.wiki/*"
|
||||||
|
],
|
||||||
|
"js": [
|
||||||
|
"content.js"
|
||||||
|
],
|
||||||
|
"run_at": "document_idle"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"web_accessible_resources": [
|
||||||
|
"new_data.json"
|
||||||
|
],
|
||||||
|
"options_ui": {
|
||||||
|
"page": "options.html",
|
||||||
|
"open_in_tab": false
|
||||||
|
}
|
||||||
|
}
|
||||||
1090
chrome-extension/new_data.json
Normal file
1090
chrome-extension/new_data.json
Normal file
File diff suppressed because it is too large
Load Diff
69
chrome-extension/options.html
Normal file
69
chrome-extension/options.html
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Ely Wiki Extension Options</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
padding: 20px;
|
||||||
|
min-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: #5a8c5a;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: #487048;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note {
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<label for="dataUrl">Custom Data JSON URL:</label>
|
||||||
|
<input type="text" id="dataUrl" placeholder="Leave empty to use default local data">
|
||||||
|
|
||||||
|
<button id="save">Save</button>
|
||||||
|
<div id="status" class="status"></div>
|
||||||
|
|
||||||
|
<div class="note">
|
||||||
|
Leave empty to use the bundled extension data. <br>
|
||||||
|
Should link to the raw json file of items.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="options.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
19
chrome-extension/options.js
Normal file
19
chrome-extension/options.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
document.getElementById('save').addEventListener('click', () => {
|
||||||
|
const dataUrl = document.getElementById('dataUrl').value.trim();
|
||||||
|
|
||||||
|
chrome.storage.sync.set({ customDataUrl: dataUrl }, () => {
|
||||||
|
const status = document.getElementById('status');
|
||||||
|
status.textContent = 'Options saved.';
|
||||||
|
setTimeout(() => {
|
||||||
|
status.textContent = '';
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
chrome.storage.sync.get(['customDataUrl'], (items) => {
|
||||||
|
if (items.customDataUrl) {
|
||||||
|
document.getElementById('dataUrl').value = items.customDataUrl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
61
download_data.py
Normal file
61
download_data.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import requests
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
import ast
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
def extract_data_from_page(url):
|
||||||
|
response = requests.get(url)
|
||||||
|
response.raise_for_status()
|
||||||
|
with open("response.html", "w", encoding="utf-8") as f:
|
||||||
|
f.write(response.text)
|
||||||
|
soup = BeautifulSoup(response.text, 'html.parser')
|
||||||
|
script_tags = soup.find_all('script')
|
||||||
|
|
||||||
|
for script in script_tags:
|
||||||
|
if script.string:
|
||||||
|
lines = script.string.split('\n')
|
||||||
|
for line in lines:
|
||||||
|
if re.match(r'^\s*data\s*=', line):
|
||||||
|
match = re.search(r'data\s*=\s*(.+)', line)
|
||||||
|
if match:
|
||||||
|
data_str = match.group(1).rstrip(';').strip()
|
||||||
|
try:
|
||||||
|
data = ast.literal_eval(data_str)
|
||||||
|
return data
|
||||||
|
except (ValueError, SyntaxError):
|
||||||
|
continue
|
||||||
|
raise ValueError("Data object not found in script tags")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
url = "https://ely.gg"
|
||||||
|
data = extract_data_from_page(url)
|
||||||
|
new_data = {}
|
||||||
|
|
||||||
|
replacement_map = {
|
||||||
|
'greater chain codex': 'Greater Chain ability codex',
|
||||||
|
'Fractured Armadyl Symbol (Kerapac)': 'Fractured Armadyl Symbol',
|
||||||
|
'Fractured Stabilization Gem (Kerapac)': 'Fractured Stabilisation Gem',
|
||||||
|
'Loved Up Walk Override': 'Loved Up Walk Override Token',
|
||||||
|
'Mizyuyari': 'Mizuyari',
|
||||||
|
'O lantern title scroll': "'o'-lantern' title scroll",
|
||||||
|
'OG Gem Cape Token': 'Gem cape token',
|
||||||
|
'Robin': 'Robin (item)',
|
||||||
|
'Red Santa Hat': 'Santa Hat',
|
||||||
|
"One of the many title scroll": "'O ne of the many' title scroll",
|
||||||
|
"Party Title Scroll": "'Party' title scroll",
|
||||||
|
}
|
||||||
|
|
||||||
|
for item in data:
|
||||||
|
if 'inverted' in item['value'].lower():
|
||||||
|
item['value'] = item['value'].replace('(120)', 'token')
|
||||||
|
|
||||||
|
for original_key, replacement_value in replacement_map.items():
|
||||||
|
if item['value'].lower() == original_key.lower():
|
||||||
|
item['value'] = replacement_value
|
||||||
|
break
|
||||||
|
|
||||||
|
new_data[item['value'].strip()] = item['id']
|
||||||
|
|
||||||
|
with open('chrome-extension/new_data.json', 'w') as f:
|
||||||
|
json.dump(new_data, f, indent=4)
|
||||||
1090
new_data.json
Normal file
1090
new_data.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
release/ely-extension-chrome.zip
Normal file
BIN
release/ely-extension-chrome.zip
Normal file
Binary file not shown.
BIN
release/ely-extension-firefox.zip
Normal file
BIN
release/ely-extension-firefox.zip
Normal file
Binary file not shown.
258
release/ely-userscript.user.js
Normal file
258
release/ely-userscript.user.js
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name RuneScape Wiki Ely Prices
|
||||||
|
// @namespace https://ely.gg/
|
||||||
|
// @version 1.0.1
|
||||||
|
// @description Show Ely prices on RuneScape Wiki pages
|
||||||
|
// @match https://runescape.wiki/w/*
|
||||||
|
// @grant GM_xmlhttpRequest
|
||||||
|
// @grant GM_setValue
|
||||||
|
// @grant GM_getValue
|
||||||
|
// @grant GM_registerMenuCommand
|
||||||
|
// @connect www.ely.gg
|
||||||
|
// @connect git.yorgei.dev
|
||||||
|
// @connect *
|
||||||
|
// @downloadURL https://git.yorgei.dev/yorgei/rs-wiki-ely/raw/branch/main/script.user.js
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
(async function () {
|
||||||
|
const defaultUrl = 'https://git.yorgei.dev/yorgei/rs-wiki-ely/raw/branch/main/new_data.json';
|
||||||
|
const dataUrl = GM_getValue('elyDataUrl', defaultUrl);
|
||||||
|
|
||||||
|
GM_registerMenuCommand('Set Custom Data URL', () => {
|
||||||
|
const currentUrl = GM_getValue('elyDataUrl', defaultUrl);
|
||||||
|
const newUrl = prompt('Enter custom data JSON URL:', currentUrl);
|
||||||
|
if (newUrl) {
|
||||||
|
GM_setValue('elyDataUrl', newUrl);
|
||||||
|
alert('Data URL updated! Refresh the page to apply changes.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function getCacheBustingUrl(url) {
|
||||||
|
return `${url}?t=${Date.now()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function gmFetchJson(url) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
GM_xmlhttpRequest({
|
||||||
|
method: 'GET',
|
||||||
|
url,
|
||||||
|
onload: res => {
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(res.responseText);
|
||||||
|
resolve(json);
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onerror: err => reject(err)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchPriceData(itemId) {
|
||||||
|
const url = `https://www.ely.gg/chart/${itemId}/prices`;
|
||||||
|
const data = await gmFetchJson(url);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pageTitleElement = document.querySelector('.mw-page-title-main');
|
||||||
|
if (!pageTitleElement) return;
|
||||||
|
|
||||||
|
const originalCursor = pageTitleElement.style.cursor;
|
||||||
|
|
||||||
|
const itemData = await gmFetchJson(getCacheBustingUrl(dataUrl));
|
||||||
|
|
||||||
|
const pageTitle = pageTitleElement.textContent.trim();
|
||||||
|
const urlPath = window.location.pathname;
|
||||||
|
const urlTitle = urlPath.replace('/w/', '').replace(/_/g, ' ');
|
||||||
|
console.log(pageTitle, urlTitle, urlPath);
|
||||||
|
|
||||||
|
let itemId = null;
|
||||||
|
|
||||||
|
if (itemData[pageTitle]) {
|
||||||
|
itemId = itemData[pageTitle];
|
||||||
|
} else if (itemData[urlTitle]) {
|
||||||
|
itemId = itemData[urlTitle];
|
||||||
|
} else {
|
||||||
|
for (const [itemName, id] of Object.entries(itemData)) {
|
||||||
|
const itemNameBase = itemName.split('(')[0].trim();
|
||||||
|
const lowerItem = itemName.toLowerCase();
|
||||||
|
const lowerBase = itemNameBase.toLowerCase();
|
||||||
|
const lowerTitle = pageTitle.toLowerCase();
|
||||||
|
const lowerUrlTitle = urlTitle.toLowerCase();
|
||||||
|
if (
|
||||||
|
lowerItem === lowerTitle ||
|
||||||
|
lowerItem === lowerUrlTitle ||
|
||||||
|
lowerBase === lowerTitle ||
|
||||||
|
lowerBase === lowerUrlTitle
|
||||||
|
) {
|
||||||
|
itemId = id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!itemId) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const priceData = await fetchPriceData(itemId);
|
||||||
|
if (!priceData.items || !priceData.items.length) return;
|
||||||
|
|
||||||
|
const lastItem = priceData.items[priceData.items.length - 1];
|
||||||
|
const price = Number(lastItem.price).toLocaleString();
|
||||||
|
const date = new Date(lastItem.date).toLocaleDateString();
|
||||||
|
const saleType = String(lastItem.purchase || '').toLowerCase();
|
||||||
|
let saleTypeShort = saleType;
|
||||||
|
if (saleType === 'sold') {
|
||||||
|
saleTypeShort = 'inb';
|
||||||
|
} else if (saleType === 'bought') {
|
||||||
|
saleTypeShort = 'ins';
|
||||||
|
}
|
||||||
|
|
||||||
|
const priceDisplay = document.createElement('span');
|
||||||
|
priceDisplay.textContent = ` (${price} gp - ${saleTypeShort} - ${date})`;
|
||||||
|
priceDisplay.style.color = '#5a8c5a';
|
||||||
|
priceDisplay.style.fontWeight = 'bold';
|
||||||
|
priceDisplay.style.fontSize = '0.9em';
|
||||||
|
|
||||||
|
pageTitleElement.appendChild(priceDisplay);
|
||||||
|
|
||||||
|
pageTitleElement.style.cursor = 'pointer';
|
||||||
|
pageTitleElement.addEventListener('click', async () => {
|
||||||
|
try {
|
||||||
|
const latestPriceData = await fetchPriceData(itemId);
|
||||||
|
if (latestPriceData.items && latestPriceData.items.length > 0) {
|
||||||
|
showSalesPopup(latestPriceData.items, itemId, pageTitleElement, originalCursor);
|
||||||
|
} else {
|
||||||
|
window.open(`https://www.ely.gg/view_item/${itemId}`, '_blank');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
window.open(`https://www.ely.gg/view_item/${itemId}`, '_blank');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
const idDisplay = document.createElement('span');
|
||||||
|
idDisplay.textContent = ` (ID: ${itemId}) | Failed to get price.`;
|
||||||
|
idDisplay.style.color = '#5a8c5a';
|
||||||
|
idDisplay.style.fontWeight = 'bold';
|
||||||
|
idDisplay.style.fontSize = '0.9em';
|
||||||
|
pageTitleElement.appendChild(idDisplay);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading Ely userscript data:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSalesPopup(items, itemId, pageTitleElement, originalCursor) {
|
||||||
|
const existingPopup = document.getElementById('ely-sales-popup');
|
||||||
|
if (existingPopup) {
|
||||||
|
existingPopup.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const popup = document.createElement('div');
|
||||||
|
popup.id = 'ely-sales-popup';
|
||||||
|
popup.style.position = 'fixed';
|
||||||
|
popup.style.top = '50%';
|
||||||
|
popup.style.left = '50%';
|
||||||
|
popup.style.transform = 'translate(-50%, -50%)';
|
||||||
|
popup.style.backgroundColor = '#fff';
|
||||||
|
popup.style.border = '1px solid #ccc';
|
||||||
|
popup.style.borderRadius = '8px';
|
||||||
|
popup.style.boxShadow = '0 4px 8px rgba(0,0,0,0.1)';
|
||||||
|
popup.style.padding = '20px';
|
||||||
|
popup.style.zIndex = '10000';
|
||||||
|
popup.style.maxWidth = '500px';
|
||||||
|
popup.style.width = '90%';
|
||||||
|
popup.style.maxHeight = '70vh';
|
||||||
|
popup.style.overflowY = 'auto';
|
||||||
|
|
||||||
|
const header = document.createElement('h3');
|
||||||
|
header.textContent = 'Recent Sales';
|
||||||
|
header.style.marginTop = '0';
|
||||||
|
header.style.marginBottom = '15px';
|
||||||
|
header.style.color = '#333';
|
||||||
|
popup.appendChild(header);
|
||||||
|
|
||||||
|
const salesList = document.createElement('div');
|
||||||
|
salesList.style.marginBottom = '20px';
|
||||||
|
|
||||||
|
const recentSales = items.slice(-10).reverse();
|
||||||
|
|
||||||
|
recentSales.forEach(sale => {
|
||||||
|
const saleItem = document.createElement('div');
|
||||||
|
saleItem.style.display = 'flex';
|
||||||
|
saleItem.style.justifyContent = 'space-between';
|
||||||
|
saleItem.style.padding = '8px 0';
|
||||||
|
saleItem.style.borderBottom = '1px solid #eee';
|
||||||
|
|
||||||
|
const price = document.createElement('span');
|
||||||
|
price.textContent = `${parseInt(sale.price, 10).toLocaleString()} gp`;
|
||||||
|
price.style.fontWeight = 'bold';
|
||||||
|
|
||||||
|
const type = document.createElement('span');
|
||||||
|
const purchaseLower = String(sale.purchase || '').toLowerCase();
|
||||||
|
type.textContent = purchaseLower === 'sold' ? 'inb' : 'ins';
|
||||||
|
type.style.color = purchaseLower === 'sold' ? '#d9534f' : '#5cb85c';
|
||||||
|
type.style.fontWeight = 'bold';
|
||||||
|
|
||||||
|
const date = document.createElement('span');
|
||||||
|
date.textContent = new Date(sale.date).toLocaleDateString();
|
||||||
|
date.style.color = '#666';
|
||||||
|
|
||||||
|
saleItem.appendChild(price);
|
||||||
|
saleItem.appendChild(type);
|
||||||
|
saleItem.appendChild(date);
|
||||||
|
salesList.appendChild(saleItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
popup.appendChild(salesList);
|
||||||
|
|
||||||
|
const buttonContainer = document.createElement('div');
|
||||||
|
buttonContainer.style.display = 'flex';
|
||||||
|
buttonContainer.style.justifyContent = 'space-between';
|
||||||
|
buttonContainer.style.marginTop = '10px';
|
||||||
|
|
||||||
|
const elyButton = document.createElement('button');
|
||||||
|
elyButton.textContent = 'Go to Ely';
|
||||||
|
elyButton.style.backgroundColor = '#5a8c5a';
|
||||||
|
elyButton.style.color = 'white';
|
||||||
|
elyButton.style.border = 'none';
|
||||||
|
elyButton.style.padding = '8px 16px';
|
||||||
|
elyButton.style.borderRadius = '4px';
|
||||||
|
elyButton.style.cursor = 'pointer';
|
||||||
|
elyButton.addEventListener('click', () => {
|
||||||
|
window.open(`https://www.ely.gg/view_item/${itemId}`, '_blank');
|
||||||
|
popup.remove();
|
||||||
|
pageTitleElement.style.cursor = originalCursor;
|
||||||
|
});
|
||||||
|
|
||||||
|
const closeButton = document.createElement('button');
|
||||||
|
closeButton.textContent = 'Close';
|
||||||
|
closeButton.style.backgroundColor = '#6c757d';
|
||||||
|
closeButton.style.color = 'white';
|
||||||
|
closeButton.style.border = 'none';
|
||||||
|
closeButton.style.padding = '8px 16px';
|
||||||
|
closeButton.style.borderRadius = '4px';
|
||||||
|
closeButton.style.cursor = 'pointer';
|
||||||
|
closeButton.addEventListener('click', () => {
|
||||||
|
popup.remove();
|
||||||
|
pageTitleElement.style.cursor = originalCursor;
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonContainer.appendChild(elyButton);
|
||||||
|
buttonContainer.appendChild(closeButton);
|
||||||
|
popup.appendChild(buttonContainer);
|
||||||
|
|
||||||
|
document.body.appendChild(popup);
|
||||||
|
|
||||||
|
function closePopupOnOutsideClick(e) {
|
||||||
|
if (!popup.contains(e.target) && e.target !== pageTitleElement) {
|
||||||
|
popup.remove();
|
||||||
|
document.removeEventListener('click', closePopupOnOutsideClick);
|
||||||
|
pageTitleElement.style.cursor = originalCursor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('click', closePopupOnOutsideClick);
|
||||||
|
}
|
||||||
|
})();
|
||||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
requests
|
||||||
|
beautifulsoup4
|
||||||
258
userscript.js
Normal file
258
userscript.js
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name RuneScape Wiki Ely Prices
|
||||||
|
// @namespace https://ely.gg/
|
||||||
|
// @version 1.0.1
|
||||||
|
// @description Show Ely prices on RuneScape Wiki pages
|
||||||
|
// @match https://runescape.wiki/w/*
|
||||||
|
// @grant GM_xmlhttpRequest
|
||||||
|
// @grant GM_setValue
|
||||||
|
// @grant GM_getValue
|
||||||
|
// @grant GM_registerMenuCommand
|
||||||
|
// @connect www.ely.gg
|
||||||
|
// @connect git.yorgei.dev
|
||||||
|
// @connect *
|
||||||
|
// @downloadURL https://git.yorgei.dev/yorgei/rs-wiki-ely/raw/branch/main/script.user.js
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
(async function () {
|
||||||
|
const defaultUrl = 'https://git.yorgei.dev/yorgei/rs-wiki-ely/raw/branch/main/new_data.json';
|
||||||
|
const dataUrl = GM_getValue('elyDataUrl', defaultUrl);
|
||||||
|
|
||||||
|
GM_registerMenuCommand('Set Custom Data URL', () => {
|
||||||
|
const currentUrl = GM_getValue('elyDataUrl', defaultUrl);
|
||||||
|
const newUrl = prompt('Enter custom data JSON URL:', currentUrl);
|
||||||
|
if (newUrl) {
|
||||||
|
GM_setValue('elyDataUrl', newUrl);
|
||||||
|
alert('Data URL updated! Refresh the page to apply changes.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function getCacheBustingUrl(url) {
|
||||||
|
return `${url}?t=${Date.now()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function gmFetchJson(url) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
GM_xmlhttpRequest({
|
||||||
|
method: 'GET',
|
||||||
|
url,
|
||||||
|
onload: res => {
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(res.responseText);
|
||||||
|
resolve(json);
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onerror: err => reject(err)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchPriceData(itemId) {
|
||||||
|
const url = `https://www.ely.gg/chart/${itemId}/prices`;
|
||||||
|
const data = await gmFetchJson(url);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pageTitleElement = document.querySelector('.mw-page-title-main');
|
||||||
|
if (!pageTitleElement) return;
|
||||||
|
|
||||||
|
const originalCursor = pageTitleElement.style.cursor;
|
||||||
|
|
||||||
|
const itemData = await gmFetchJson(getCacheBustingUrl(dataUrl));
|
||||||
|
|
||||||
|
const pageTitle = pageTitleElement.textContent.trim();
|
||||||
|
const urlPath = window.location.pathname;
|
||||||
|
const urlTitle = urlPath.replace('/w/', '').replace(/_/g, ' ');
|
||||||
|
console.log(pageTitle, urlTitle, urlPath);
|
||||||
|
|
||||||
|
let itemId = null;
|
||||||
|
|
||||||
|
if (itemData[pageTitle]) {
|
||||||
|
itemId = itemData[pageTitle];
|
||||||
|
} else if (itemData[urlTitle]) {
|
||||||
|
itemId = itemData[urlTitle];
|
||||||
|
} else {
|
||||||
|
for (const [itemName, id] of Object.entries(itemData)) {
|
||||||
|
const itemNameBase = itemName.split('(')[0].trim();
|
||||||
|
const lowerItem = itemName.toLowerCase();
|
||||||
|
const lowerBase = itemNameBase.toLowerCase();
|
||||||
|
const lowerTitle = pageTitle.toLowerCase();
|
||||||
|
const lowerUrlTitle = urlTitle.toLowerCase();
|
||||||
|
if (
|
||||||
|
lowerItem === lowerTitle ||
|
||||||
|
lowerItem === lowerUrlTitle ||
|
||||||
|
lowerBase === lowerTitle ||
|
||||||
|
lowerBase === lowerUrlTitle
|
||||||
|
) {
|
||||||
|
itemId = id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!itemId) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const priceData = await fetchPriceData(itemId);
|
||||||
|
if (!priceData.items || !priceData.items.length) return;
|
||||||
|
|
||||||
|
const lastItem = priceData.items[priceData.items.length - 1];
|
||||||
|
const price = Number(lastItem.price).toLocaleString();
|
||||||
|
const date = new Date(lastItem.date).toLocaleDateString();
|
||||||
|
const saleType = String(lastItem.purchase || '').toLowerCase();
|
||||||
|
let saleTypeShort = saleType;
|
||||||
|
if (saleType === 'sold') {
|
||||||
|
saleTypeShort = 'inb';
|
||||||
|
} else if (saleType === 'bought') {
|
||||||
|
saleTypeShort = 'ins';
|
||||||
|
}
|
||||||
|
|
||||||
|
const priceDisplay = document.createElement('span');
|
||||||
|
priceDisplay.textContent = ` (${price} gp - ${saleTypeShort} - ${date})`;
|
||||||
|
priceDisplay.style.color = '#5a8c5a';
|
||||||
|
priceDisplay.style.fontWeight = 'bold';
|
||||||
|
priceDisplay.style.fontSize = '0.9em';
|
||||||
|
|
||||||
|
pageTitleElement.appendChild(priceDisplay);
|
||||||
|
|
||||||
|
pageTitleElement.style.cursor = 'pointer';
|
||||||
|
pageTitleElement.addEventListener('click', async () => {
|
||||||
|
try {
|
||||||
|
const latestPriceData = await fetchPriceData(itemId);
|
||||||
|
if (latestPriceData.items && latestPriceData.items.length > 0) {
|
||||||
|
showSalesPopup(latestPriceData.items, itemId, pageTitleElement, originalCursor);
|
||||||
|
} else {
|
||||||
|
window.open(`https://www.ely.gg/view_item/${itemId}`, '_blank');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
window.open(`https://www.ely.gg/view_item/${itemId}`, '_blank');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
const idDisplay = document.createElement('span');
|
||||||
|
idDisplay.textContent = ` (ID: ${itemId}) | Failed to get price.`;
|
||||||
|
idDisplay.style.color = '#5a8c5a';
|
||||||
|
idDisplay.style.fontWeight = 'bold';
|
||||||
|
idDisplay.style.fontSize = '0.9em';
|
||||||
|
pageTitleElement.appendChild(idDisplay);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading Ely userscript data:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSalesPopup(items, itemId, pageTitleElement, originalCursor) {
|
||||||
|
const existingPopup = document.getElementById('ely-sales-popup');
|
||||||
|
if (existingPopup) {
|
||||||
|
existingPopup.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const popup = document.createElement('div');
|
||||||
|
popup.id = 'ely-sales-popup';
|
||||||
|
popup.style.position = 'fixed';
|
||||||
|
popup.style.top = '50%';
|
||||||
|
popup.style.left = '50%';
|
||||||
|
popup.style.transform = 'translate(-50%, -50%)';
|
||||||
|
popup.style.backgroundColor = '#fff';
|
||||||
|
popup.style.border = '1px solid #ccc';
|
||||||
|
popup.style.borderRadius = '8px';
|
||||||
|
popup.style.boxShadow = '0 4px 8px rgba(0,0,0,0.1)';
|
||||||
|
popup.style.padding = '20px';
|
||||||
|
popup.style.zIndex = '10000';
|
||||||
|
popup.style.maxWidth = '500px';
|
||||||
|
popup.style.width = '90%';
|
||||||
|
popup.style.maxHeight = '70vh';
|
||||||
|
popup.style.overflowY = 'auto';
|
||||||
|
|
||||||
|
const header = document.createElement('h3');
|
||||||
|
header.textContent = 'Recent Sales';
|
||||||
|
header.style.marginTop = '0';
|
||||||
|
header.style.marginBottom = '15px';
|
||||||
|
header.style.color = '#333';
|
||||||
|
popup.appendChild(header);
|
||||||
|
|
||||||
|
const salesList = document.createElement('div');
|
||||||
|
salesList.style.marginBottom = '20px';
|
||||||
|
|
||||||
|
const recentSales = items.slice(-10).reverse();
|
||||||
|
|
||||||
|
recentSales.forEach(sale => {
|
||||||
|
const saleItem = document.createElement('div');
|
||||||
|
saleItem.style.display = 'flex';
|
||||||
|
saleItem.style.justifyContent = 'space-between';
|
||||||
|
saleItem.style.padding = '8px 0';
|
||||||
|
saleItem.style.borderBottom = '1px solid #eee';
|
||||||
|
|
||||||
|
const price = document.createElement('span');
|
||||||
|
price.textContent = `${parseInt(sale.price, 10).toLocaleString()} gp`;
|
||||||
|
price.style.fontWeight = 'bold';
|
||||||
|
|
||||||
|
const type = document.createElement('span');
|
||||||
|
const purchaseLower = String(sale.purchase || '').toLowerCase();
|
||||||
|
type.textContent = purchaseLower === 'sold' ? 'inb' : 'ins';
|
||||||
|
type.style.color = purchaseLower === 'sold' ? '#d9534f' : '#5cb85c';
|
||||||
|
type.style.fontWeight = 'bold';
|
||||||
|
|
||||||
|
const date = document.createElement('span');
|
||||||
|
date.textContent = new Date(sale.date).toLocaleDateString();
|
||||||
|
date.style.color = '#666';
|
||||||
|
|
||||||
|
saleItem.appendChild(price);
|
||||||
|
saleItem.appendChild(type);
|
||||||
|
saleItem.appendChild(date);
|
||||||
|
salesList.appendChild(saleItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
popup.appendChild(salesList);
|
||||||
|
|
||||||
|
const buttonContainer = document.createElement('div');
|
||||||
|
buttonContainer.style.display = 'flex';
|
||||||
|
buttonContainer.style.justifyContent = 'space-between';
|
||||||
|
buttonContainer.style.marginTop = '10px';
|
||||||
|
|
||||||
|
const elyButton = document.createElement('button');
|
||||||
|
elyButton.textContent = 'Go to Ely';
|
||||||
|
elyButton.style.backgroundColor = '#5a8c5a';
|
||||||
|
elyButton.style.color = 'white';
|
||||||
|
elyButton.style.border = 'none';
|
||||||
|
elyButton.style.padding = '8px 16px';
|
||||||
|
elyButton.style.borderRadius = '4px';
|
||||||
|
elyButton.style.cursor = 'pointer';
|
||||||
|
elyButton.addEventListener('click', () => {
|
||||||
|
window.open(`https://www.ely.gg/view_item/${itemId}`, '_blank');
|
||||||
|
popup.remove();
|
||||||
|
pageTitleElement.style.cursor = originalCursor;
|
||||||
|
});
|
||||||
|
|
||||||
|
const closeButton = document.createElement('button');
|
||||||
|
closeButton.textContent = 'Close';
|
||||||
|
closeButton.style.backgroundColor = '#6c757d';
|
||||||
|
closeButton.style.color = 'white';
|
||||||
|
closeButton.style.border = 'none';
|
||||||
|
closeButton.style.padding = '8px 16px';
|
||||||
|
closeButton.style.borderRadius = '4px';
|
||||||
|
closeButton.style.cursor = 'pointer';
|
||||||
|
closeButton.addEventListener('click', () => {
|
||||||
|
popup.remove();
|
||||||
|
pageTitleElement.style.cursor = originalCursor;
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonContainer.appendChild(elyButton);
|
||||||
|
buttonContainer.appendChild(closeButton);
|
||||||
|
popup.appendChild(buttonContainer);
|
||||||
|
|
||||||
|
document.body.appendChild(popup);
|
||||||
|
|
||||||
|
function closePopupOnOutsideClick(e) {
|
||||||
|
if (!popup.contains(e.target) && e.target !== pageTitleElement) {
|
||||||
|
popup.remove();
|
||||||
|
document.removeEventListener('click', closePopupOnOutsideClick);
|
||||||
|
pageTitleElement.style.cursor = originalCursor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('click', closePopupOnOutsideClick);
|
||||||
|
}
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user