├── .gitignore
├── packager
├── suffix.js
├── bump.sh
├── package.json
├── bump.js
├── prefix.js
├── run.js
└── moduleRegistry.js
├── features
├── authToast.js
├── versionWarning.js
├── scriptRegistry.js
├── questDisabler.js
├── actionEnabler.js
├── petHighlighter.js
├── debugService.js
├── recipeClickthrough.js
├── marketListingLimitWarning.js
├── dropChanceDisplay.js
├── conversionHider.js
├── petRenamer.js
├── estimatorActivity.js
├── changelog.js
├── targetAmountCrafting.js
├── _marketCompetition.js
├── estimatorOutskirts.js
├── petFilter.js
├── marketPriceButtons.js
├── targetAmountMarket.js
├── marksGrouper.js
├── dataForwarder.js
├── craftCheatSheet.js
├── petStatHighlighter.js
├── ui.js
├── petStatRedesign.js
├── configurationPage.js
├── itemHover.js
├── _skillOverviewPage.js
└── guildSorts.js
├── caches
├── actionCache.js
├── monsterCache.js
├── skillSetCache.js
├── structuresCache.js
├── recipeCache.js
├── expeditionCache.js
├── traitCache.js
├── masteryCache.js
├── expeditionDropCache.js
├── ingredientCache.js
├── petPassiveCache.js
├── skillCache.js
├── statNameCache.js
├── petCache.js
└── dropCache.js
├── stores
├── localConfigurationStore.js
├── expStateStore.js
├── configurationStore.js
├── variousStateStore.js
├── lootStore.js
├── abstractStateStore.js
├── equipmentStateStore.js
├── customItemPriceStore.js
├── masteryStateStore.js
└── petStateStore.js
├── libraries
├── interceptor.js
├── colorMapper.js
├── logService.js
├── assetUtil.js
├── polyfill.js
├── events.js
├── hotkey.js
├── itemUtil.js
├── elementCreator.js
├── configuration.js
├── Promise.js
├── toast.js
├── elementWatcher.js
├── modal.js
├── pageDetector.js
├── request.js
├── localDatabase.js
└── EstimationGenerator.js
├── readers
├── settingsReader.js
├── lootReader.js
├── guildReader.js
├── structuresReader.js
├── enchantmentsReader.js
├── guildStructuresReader.js
├── equipmentReader.js
├── variousReader.js
├── guildEventReader.js
├── questReader.js
├── inventoryReader.js
├── marksReader.js
├── expReader.js
├── marketReader.js
├── traitsReader.js
├── masteryReader.js
└── petReader.js
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | **/node_modules/*
2 | **/package-lock.json
--------------------------------------------------------------------------------
/packager/suffix.js:
--------------------------------------------------------------------------------
1 | window.moduleRegistry.build();
2 |
--------------------------------------------------------------------------------
/features/authToast.js:
--------------------------------------------------------------------------------
1 | (toast) => {
2 |
3 | function initialise() {
4 | toast.create({
5 | text: 'Pancake-Scripts initialised!',
6 | image: 'https://img.icons8.com/?size=48&id=1ODJ62iG96gX&format=png'
7 | });
8 | }
9 |
10 | initialise();
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/packager/bump.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | git checkout main
4 | git merge development
5 | version=$(node.exe bump.js $1)
6 | node.exe run.js
7 | git add ../*
8 | git commit -m "Version $version"
9 | git tag "v$version"
10 | git push
11 | git push origin --tags
12 | git checkout development
13 | git merge main
14 | git push
15 |
16 | read -p "Press any key to resume ..."
17 |
--------------------------------------------------------------------------------
/caches/actionCache.js:
--------------------------------------------------------------------------------
1 | (request) => {
2 |
3 | const exports = {
4 | list: [],
5 | byId: {},
6 | byName: {}
7 | };
8 |
9 | async function initialise() {
10 | const actions = await request.listActions();
11 | for(const action of actions) {
12 | exports.list.push(action);
13 | exports.byId[action.id] = action;
14 | exports.byName[action.name] = action;
15 | }
16 | return exports;
17 | }
18 |
19 | return initialise();
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/caches/monsterCache.js:
--------------------------------------------------------------------------------
1 | (request) => {
2 |
3 | const exports = {
4 | list: [],
5 | byId: {},
6 | byName: {}
7 | };
8 |
9 | async function initialise() {
10 | const monsters = await request.listMonsters();
11 | for(const monster of monsters) {
12 | exports.list.push(monster);
13 | exports.byId[monster.id] = monster;
14 | exports.byName[monster.name] = monster;
15 | }
16 | return exports;
17 | }
18 |
19 | return initialise();
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/caches/skillSetCache.js:
--------------------------------------------------------------------------------
1 | (request) => {
2 |
3 | const exports = {
4 | list: [],
5 | byId: {},
6 | byName: {}
7 | };
8 |
9 | async function initialise() {
10 | const skillSets = await request.listSkillSets();
11 | for(const skillSet of skillSets) {
12 | exports.list.push(skillSet);
13 | exports.byId[skillSet.id] = skillSet;
14 | exports.byName[skillSet.name] = skillSet;
15 | }
16 | return exports;
17 | }
18 |
19 | return initialise();
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/caches/structuresCache.js:
--------------------------------------------------------------------------------
1 | (request) => {
2 |
3 | const exports = {
4 | list: [],
5 | byId: {},
6 | byName: {}
7 | };
8 |
9 | async function initialise() {
10 | const structures = await request.listStructures();
11 | for(const structure of structures) {
12 | exports.list.push(structure);
13 | exports.byId[structure.id] = structure;
14 | exports.byName[structure.name] = structure;
15 | }
16 | return exports;
17 | }
18 |
19 | return initialise();
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/caches/recipeCache.js:
--------------------------------------------------------------------------------
1 | (request) => {
2 |
3 | const exports = {
4 | list: [],
5 | byId: {},
6 | byName: {},
7 | byImage: {}
8 | };
9 |
10 | async function initialise() {
11 | exports.list = await request.listRecipes();
12 | for(const recipe of exports.list) {
13 | exports.byId[recipe.id] = recipe;
14 | exports.byName[recipe.name] = recipe;
15 | const lastPart = recipe.image.split('/').at(-1);
16 | exports.byImage[lastPart] = recipe;
17 | }
18 | return exports;
19 | }
20 |
21 | return initialise();
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/stores/localConfigurationStore.js:
--------------------------------------------------------------------------------
1 | (localDatabase) => {
2 |
3 | const exports = {
4 | load,
5 | save
6 | };
7 |
8 | const STORE_NAME = 'settings';
9 |
10 | async function load() {
11 | const entries = await localDatabase.getAllEntries(STORE_NAME);
12 | const configurations = {};
13 | for(const entry of entries) {
14 | configurations[entry.key] = entry.value;
15 | }
16 | return configurations;
17 | }
18 |
19 | async function save(key, value) {
20 | await localDatabase.saveEntry(STORE_NAME, {key, value});
21 | }
22 |
23 | return exports;
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/caches/expeditionCache.js:
--------------------------------------------------------------------------------
1 | (request) => {
2 |
3 | const exports = {
4 | list: [],
5 | byId: {},
6 | byName: {},
7 | byTier: {}
8 | };
9 |
10 | async function initialise() {
11 | const expeditions = await request.listExpeditions();
12 | for(const expedition of expeditions) {
13 | exports.list.push(expedition);
14 | exports.byId[expedition.id] = expedition;
15 | exports.byName[expedition.name] = expedition;
16 | exports.byTier[expedition.tier] = expedition;
17 | }
18 | return exports;
19 | }
20 |
21 | return initialise();
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/caches/traitCache.js:
--------------------------------------------------------------------------------
1 | (request) => {
2 |
3 | const exports = {
4 | list: [],
5 | byId: {},
6 | byName: {},
7 | byImage: {}
8 | };
9 |
10 | async function initialise() {
11 | const traits = await request.listTraits();
12 | for(const trait of traits) {
13 | exports.list.push(trait);
14 | exports.byId[trait.id] = trait;
15 | exports.byName[trait.name] = trait;
16 | const lastPart = trait.image.split('/').at(-1);
17 | exports.byImage[lastPart] = trait;
18 | }
19 | return exports;
20 | }
21 |
22 | return initialise();
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/caches/masteryCache.js:
--------------------------------------------------------------------------------
1 | (request) => {
2 |
3 | const exports = {
4 | list: [],
5 | byId: {},
6 | bySkill: {},
7 | byImage: {}
8 | };
9 |
10 | async function initialise() {
11 | const masteries = await request.listMasteries();
12 | for(const mastery of masteries) {
13 | exports.list.push(mastery);
14 | exports.byId[mastery.id] = mastery;
15 | exports.bySkill[mastery.skill] = mastery;
16 | const lastPart = mastery.image.split('/').at(-1);
17 | exports.byImage[lastPart] = mastery;
18 | }
19 | return exports;
20 | }
21 |
22 | return initialise();
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/libraries/interceptor.js:
--------------------------------------------------------------------------------
1 | (events) => {
2 |
3 | function initialise() {
4 | registerInterceptorUrlChange();
5 | events.emit('url', window.location.href);
6 | }
7 |
8 | function registerInterceptorUrlChange() {
9 | const pushState = history.pushState;
10 | history.pushState = function() {
11 | pushState.apply(history, arguments);
12 | events.emit('url', arguments[2]);
13 | };
14 | const replaceState = history.replaceState;
15 | history.replaceState = function() {
16 | replaceState.apply(history, arguments);
17 | events.emit('url', arguments[2]);
18 | }
19 | }
20 |
21 | initialise();
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/libraries/colorMapper.js:
--------------------------------------------------------------------------------
1 | () => {
2 |
3 | const colorMappings = {
4 | // https://colorswall.com/palette/3
5 | primary: '#0275d8',
6 | success: '#5cb85c',
7 | info: '#5bc0de',
8 | warning: '#f0ad4e',
9 | danger: '#d9534f',
10 | inverse: '#292b2c',
11 | // custom
12 | focus: '#fff021',
13 | // component styling
14 | componentLight: '#393532',
15 | componentRegular: '#28211b',
16 | componentDark: '#211a12',
17 | componentHover: '#3c2f26',
18 | componentSelected: '#1c1916'
19 | };
20 |
21 | function mapColor(color) {
22 | return colorMappings[color] || color;
23 | }
24 |
25 | return mapColor;
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/features/versionWarning.js:
--------------------------------------------------------------------------------
1 | (request, toast) => {
2 |
3 | function initialise() {
4 | setInterval(run, 1000 * 60 * 5);
5 | run();
6 | }
7 |
8 | async function run() {
9 | const version = await request.getVersion();
10 | if(!window.PANCAKE_VERSION || version === window.PANCAKE_VERSION) {
11 | return;
12 | }
13 | toast.create({
14 | text: `Consider updating Pancake-Scripts to ${version}!
Click here to go to GreasyFork {
2 |
3 | const exports = {
4 | list: [],
5 | byExpedition: {},
6 | byItem: {}
7 | };
8 |
9 | async function initialise() {
10 | const drops = await request.listExpeditionDrops();
11 | for(const drop of drops) {
12 | exports.list.push(drop);
13 | if(!exports.byExpedition[drop.expedition]) {
14 | exports.byExpedition[drop.expedition] = [];
15 | }
16 | exports.byExpedition[drop.expedition].push(drop);
17 | if(!exports.byItem[drop.item]) {
18 | exports.byItem[drop.item] = [];
19 | }
20 | exports.byItem[drop.item].push(drop);
21 | }
22 | return exports;
23 | }
24 |
25 | return initialise();
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/features/scriptRegistry.js:
--------------------------------------------------------------------------------
1 | (Promise, elementCreator) => {
2 |
3 | const loaded = new Promise.Deferred('scriptRegistry');
4 |
5 | const exports = {
6 | isLoaded
7 | };
8 |
9 | async function initialise() {
10 | const promises = [
11 | elementCreator.addScript('https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js'),
12 | elementCreator.addScript('https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.20.0/matter.min.js'),
13 | elementCreator.addScript('https://code.jquery.com/ui/1.14.1/jquery-ui.js'),
14 | ];
15 | await window.Promise.all(promises);
16 | loaded.resolve();
17 | }
18 |
19 | function isLoaded() {
20 | return loaded;
21 | }
22 |
23 | initialise();
24 |
25 | return exports;
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/packager/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "packager",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "run.js",
6 | "scripts": {
7 | "start": "node run.js",
8 | "watch": "nodemon -w ../ -i plugin.js",
9 | "bump-patch": "bump.sh patch",
10 | "bump-minor": "bump.sh minor",
11 | "bump-major": "bump.sh major"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/Boldy97/ironwood-scripts.git"
16 | },
17 | "author": "",
18 | "license": "MIT",
19 | "bugs": {
20 | "url": "https://github.com/Boldy97/ironwood-scripts/issues"
21 | },
22 | "homepage": "https://github.com/Boldy97/ironwood-scripts#readme",
23 | "dependencies": {
24 | "node-fetch": "^3.3.2",
25 | "nodemon": "^3.1.4",
26 | "semver": "^7.5.4"
27 | },
28 | "type": "module"
29 | }
30 |
--------------------------------------------------------------------------------
/libraries/logService.js:
--------------------------------------------------------------------------------
1 | () => {
2 |
3 | const exports = {
4 | error,
5 | get
6 | };
7 |
8 | const errors = [];
9 |
10 | function initialise() {
11 | window.onerror = function(message, url, lineNumber, columnNumber, error) {
12 | errors.push({
13 | time: Date.now(),
14 | message,
15 | url,
16 | lineNumber,
17 | columnNumber,
18 | error
19 | });
20 | return false;
21 | };
22 | }
23 |
24 | function error() {
25 | errors.push({
26 | time: Date.now(),
27 | value: [...arguments]
28 | });
29 | }
30 |
31 | function get() {
32 | return errors;
33 | }
34 |
35 | initialise();
36 |
37 | return exports;
38 |
39 | }
--------------------------------------------------------------------------------
/features/questDisabler.js:
--------------------------------------------------------------------------------
1 | (configuration, elementWatcher) => {
2 |
3 | function initialise() {
4 | configuration.registerCheckbox({
5 | category: 'UI Features',
6 | key: 'quest-disabler',
7 | name: 'Quest Disabler',
8 | default: false,
9 | handler: toggle
10 | });
11 | }
12 |
13 | async function toggle(state) {
14 | await elementWatcher.exists('nav-component button[routerLink="/quests"]');
15 | $('nav-component button[routerLink="/quests"]')
16 | .attr('disabled', state)
17 | .css('pointer-events', state ? 'none' : '')
18 | .find('.name')
19 | .css('color', state ? '#db6565' : 'white')
20 | .css('text-decoration', state ? 'line-through' : '');
21 | }
22 |
23 | initialise();
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/caches/ingredientCache.js:
--------------------------------------------------------------------------------
1 | (request) => {
2 |
3 | const exports = {
4 | list: [],
5 | byAction: {},
6 | byItem: {}
7 | };
8 |
9 | async function initialise() {
10 | const ingredients = await request.listIngredients();
11 | for(const ingredient of ingredients) {
12 | exports.list.push(ingredient);
13 | if(!exports.byAction[ingredient.action]) {
14 | exports.byAction[ingredient.action] = [];
15 | }
16 | exports.byAction[ingredient.action].push(ingredient);
17 | if(!exports.byItem[ingredient.item]) {
18 | exports.byItem[ingredient.item] = [];
19 | }
20 | exports.byItem[ingredient.item].push(ingredient);
21 | }
22 | return exports;
23 | }
24 |
25 | return initialise();
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/readers/settingsReader.js:
--------------------------------------------------------------------------------
1 | (events) => {
2 |
3 | const emitEvent = events.emit.bind(null, 'reader-settings');
4 |
5 | function initialise() {
6 | events.register('page', update);
7 | window.setInterval(update, 1000);
8 | }
9 |
10 | function update() {
11 | const page = events.getLast('page');
12 | if(!page) {
13 | return;
14 | }
15 | if(page.type === 'settings') {
16 | readScreen();
17 | }
18 | }
19 |
20 | function readScreen() {
21 | const data = {
22 | name: $('settings-page .name:contains("Username")').next().text()
23 | };
24 | if(!data.name) {
25 | return;
26 | }
27 | emitEvent({
28 | type: 'full',
29 | value: data
30 | });
31 | }
32 |
33 | initialise();
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/libraries/assetUtil.js:
--------------------------------------------------------------------------------
1 | (itemCache) => {
2 | const loadedImages = new Map();
3 |
4 | const exports = {
5 | loadImageFromUrl,
6 | loadItemImage
7 | };
8 |
9 | async function loadImageFromUrl(url) {
10 | if (loadedImages.has(url)) {
11 | return loadedImages.get(url);
12 | }
13 |
14 | return await new Promise((res) => {
15 | const img = new Image();
16 | img.onload = () => {
17 | loadedImages.set(url, img);
18 | res(img);
19 | };
20 | img.onerror = () => res(null);
21 | img.src = url;
22 | });
23 | }
24 |
25 | async function loadItemImage(itemId) {
26 | const item = itemCache.byId[itemId];
27 | if (!item) return null;
28 | return await loadImageFromUrl('assets/' + item.image);
29 | }
30 |
31 | return exports;
32 | }
33 |
--------------------------------------------------------------------------------
/readers/lootReader.js:
--------------------------------------------------------------------------------
1 | (events, itemUtil) => {
2 |
3 | function initialise() {
4 | events.register('page', update);
5 | window.setInterval(update, 500);
6 | }
7 |
8 | function update() {
9 | const page = events.getLast('page');
10 | if(!page || page.type !== 'action') {
11 | return;
12 | }
13 | const lootCard = $('skill-page .card:not(:first-child) .header > .name:contains("Loot")')
14 | .closest('.card');
15 | if(!lootCard.length) {
16 | return;
17 | }
18 | const loot = {};
19 | lootCard.find('.row').each((i,element) => {
20 | itemUtil.extractItem(element, loot);
21 | });
22 | events.emit('reader-loot', {
23 | skill: page.skill,
24 | action: page.action,
25 | loot
26 | });
27 | }
28 |
29 | initialise();
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/readers/guildReader.js:
--------------------------------------------------------------------------------
1 | (events, util) => {
2 |
3 | const emitEvent = events.emit.bind(null, 'reader-guild');
4 |
5 | function initialise() {
6 | events.register('page', update);
7 | window.setInterval(update, 1000);
8 | }
9 |
10 | function update() {
11 | const page = events.getLast('page');
12 | if(!page) {
13 | return;
14 | }
15 | if(page.type === 'guild') {
16 | readScreen();
17 | }
18 | }
19 |
20 | function readScreen() {
21 | const data = {
22 | name: $('guild-page .tracker .name').text(),
23 | level: util.parseNumber($('guild-page .tracker .level').text())
24 | };
25 | if(!data.name) {
26 | return;
27 | }
28 | emitEvent({
29 | type: 'full',
30 | value: data
31 | });
32 | }
33 |
34 | initialise();
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/caches/petPassiveCache.js:
--------------------------------------------------------------------------------
1 | (util, request) => {
2 |
3 | const exports = {
4 | list: [],
5 | byId: {},
6 | byName: {},
7 | idToIndex: {}
8 | };
9 |
10 | async function initialise() {
11 | const petPassives = await request.listPetPassives();
12 | for(const petPassive of petPassives) {
13 | exports.list.push(petPassive);
14 | exports.byId[petPassive.id] = petPassive;
15 | exports.byName[petPassive.name] = petPassive;
16 | exports.idToIndex[petPassive.id] = exports.list.length-1;
17 | petPassive.stats = {
18 | name: petPassive.statName,
19 | value: petPassive.statValue,
20 | level: util.parseNumber(petPassive.name)
21 | };
22 | delete petPassive.statName;
23 | delete petPassive.statValue;
24 | }
25 | return exports;
26 | }
27 |
28 | return initialise();
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/features/actionEnabler.js:
--------------------------------------------------------------------------------
1 | (configuration, events) => {
2 |
3 | let enabled = false;
4 |
5 | function initialise() {
6 | configuration.registerCheckbox({
7 | category: 'UI Features',
8 | key: 'action-enabler',
9 | name: 'Action Enabler',
10 | default: true,
11 | handler: handleConfigStateChange
12 | });
13 | events.register('page', handlePage);
14 | }
15 |
16 | function handleConfigStateChange(state) {
17 | enabled = state;
18 | }
19 |
20 | function handlePage(page) {
21 | if(!enabled || page.type !== 'action') {
22 | return;
23 | }
24 | $('skill-page .header > .name:contains("Actions")')
25 | .closest('.card')
26 | .find('button[disabled]')
27 | .not('.container > button')
28 | .removeAttr('disabled')
29 | .find('.level')
30 | .css('color', '#db6565');
31 | }
32 |
33 | initialise();
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/stores/expStateStore.js:
--------------------------------------------------------------------------------
1 | (events, util) => {
2 |
3 | const emitEvent = events.emit.bind(null, 'state-exp');
4 | const state = {};
5 |
6 | function initialise() {
7 | events.register('reader-exp', handleExpReader);
8 | }
9 |
10 | function handleExpReader(event) {
11 | let updated = false;
12 | for(const skill of event) {
13 | if(!state[skill.id]) {
14 | state[skill.id] = {
15 | id: skill.id,
16 | exp: 0,
17 | level: 1
18 | };
19 | }
20 | const level = util.expToLevel(skill.exp);
21 | if(skill.exp > state[skill.id].exp || level !== state[skill.id].level) {
22 | updated = true;
23 | state[skill.id].exp = skill.exp;
24 | state[skill.id].level = level;
25 | }
26 | }
27 | if(updated) {
28 | emitEvent(state);
29 | }
30 | }
31 |
32 | initialise();
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/caches/skillCache.js:
--------------------------------------------------------------------------------
1 | (request) => {
2 |
3 | const exports = {
4 | list: [],
5 | byId: {},
6 | byName: {},
7 | byTechnicalName: {},
8 | match,
9 | };
10 |
11 | async function initialise() {
12 | const skills = await request.listSkills();
13 | for(const skill of skills) {
14 | exports.list.push(skill);
15 | exports.byId[skill.id] = skill;
16 | exports.byName[skill.displayName] = skill;
17 | exports.byTechnicalName[skill.technicalName] = skill;
18 | }
19 | return exports;
20 | }
21 |
22 | function match(name) {
23 | name = name.toLowerCase();
24 | for(let skill of exports.list) {
25 | if(name === skill.displayName.toLowerCase()) {
26 | return skill;
27 | }
28 | if(name === skill.technicalName.toLowerCase()) {
29 | return skill;
30 | }
31 | }
32 | }
33 |
34 | return initialise();
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/packager/bump.js:
--------------------------------------------------------------------------------
1 | import { promises as fs } from 'fs';
2 | import path, { dirname } from 'path';
3 | import { fileURLToPath } from 'url';
4 | import semverInc from 'semver/functions/inc.js';
5 |
6 | const __filename = fileURLToPath(import.meta.url);
7 | const __dirname = dirname(__filename);
8 |
9 | async function run() {
10 | let content = await readFile('prefix.js');
11 | const from = /@version\W+(.*)/.exec(content)[1];
12 | const to = semverInc(from, process.argv[2]);
13 | content = content
14 | .split('[\r\n]+')
15 | .map(line => !line.toLowerCase().includes('version') ? line : line.replaceAll(from, to))
16 | .join('\n');
17 | writeFile('prefix.js', content.replaceAll(from, to));
18 | process.stdout.write(to);
19 | }
20 |
21 | async function readFile(filename) {
22 | return await fs.readFile(path.resolve(__dirname, filename), 'utf8');
23 | }
24 |
25 | async function writeFile(filename, content) {
26 | fs.writeFile(path.resolve(__dirname, filename), content);
27 | }
28 |
29 | run();
30 |
--------------------------------------------------------------------------------
/stores/configurationStore.js:
--------------------------------------------------------------------------------
1 | (Promise, localConfigurationStore, _remoteConfigurationStore) => {
2 |
3 | const initialised = new Promise.Expiring(2000, 'configurationStore');
4 | let configs = null;
5 |
6 | const exports = {
7 | save,
8 | getConfigs
9 | };
10 |
11 | const configurationStore = _remoteConfigurationStore || localConfigurationStore;
12 |
13 | async function initialise() {
14 | configs = await configurationStore.load();
15 | for (const key in configs) {
16 | try {
17 | configs[key] = JSON.parse(configs[key]);
18 | } catch(e){
19 | console.error(e);
20 | }
21 | }
22 | initialised.resolve(exports);
23 | }
24 |
25 | async function save(key, value) {
26 | await configurationStore.save(key, value);
27 | configs[key] = value;
28 | }
29 |
30 | function getConfigs() {
31 | return configs;
32 | }
33 |
34 | initialise();
35 |
36 | return initialised;
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/features/petHighlighter.js:
--------------------------------------------------------------------------------
1 | (events) => {
2 |
3 | const exports = {
4 | highlight
5 | };
6 |
7 | let currentColor = null;
8 | let currentNames = null;
9 |
10 | function initialise() {
11 | events.register('page', update);
12 | events.register('state-pet', update);
13 | }
14 |
15 | function highlight(color, names) {
16 | currentColor = color;
17 | currentNames = names;
18 | }
19 |
20 | function update() {
21 | if(!currentColor || !currentNames || !currentNames.length) {
22 | return;
23 | }
24 | const page = events.getLast('page');
25 | if(page?.type === 'taming' && page.menu === 'pets') {
26 | events.getLast('state-pet')
27 | .filter(pet => currentNames.includes(pet.name) && pet.element)
28 | .forEach(pet => {
29 | $(pet.element).css('box-shadow', `inset 0px 0px 8px 0px ${currentColor}`)
30 | });
31 | }
32 | }
33 |
34 | initialise();
35 |
36 | return exports;
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/features/debugService.js:
--------------------------------------------------------------------------------
1 | (request, toast, statsStore, EstimationGenerator, logService, events, util) => {
2 |
3 | const exports = {
4 | submit
5 | };
6 |
7 | async function submit() {
8 | const data = get();
9 | try {
10 | await forward(data);
11 | } catch(e) {
12 | exportToClipboard(data);
13 | }
14 | }
15 |
16 | function get() {
17 | return {
18 | stats: statsStore.get(),
19 | state: (new EstimationGenerator()).export(),
20 | logs: logService.get(),
21 | events: events.getLastCache()
22 | };
23 | }
24 |
25 | async function forward(data) {
26 | await request.report(data);
27 | toast.create({
28 | text: 'Forwarded debug data',
29 | image: 'https://img.icons8.com/?size=48&id=13809'
30 | });
31 | }
32 |
33 | function exportToClipboard(data) {
34 | toast.copyToClipboard(JSON.stringify(data), 'Failed to forward, exported to clipboard instead');
35 | }
36 |
37 | return exports;
38 |
39 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Boldy97
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packager/prefix.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Ironwood RPG - Pancake-Scripts
3 | // @namespace http://tampermonkey.net/
4 | // @version 6.3.0
5 | // @description A collection of scripts to enhance Ironwood RPG - https://github.com/Boldy97/ironwood-scripts
6 | // @author Pancake
7 | // @match https://ironwoodrpg.com/*
8 | // @icon https://www.google.com/s2/favicons?sz=64&domain=ironwoodrpg.com
9 | // @grant none
10 | // @require https://code.jquery.com/jquery-3.6.4.min.js
11 | // ==/UserScript==
12 |
13 | window.PANCAKE_ROOT = 'https://iwrpg.vectordungeon.com';
14 | window.PANCAKE_VERSION = '6.3.0';
15 | Object.defineProperty(Array.prototype, '_groupBy', {
16 | enumerable: false,
17 | value: function(selector) {
18 | return Object.values(this.reduce(function(rv, x) {
19 | (rv[selector(x)] = rv[selector(x)] || []).push(x);
20 | return rv;
21 | }, {}));
22 | }
23 | });
24 | Object.defineProperty(Array.prototype, '_distinct', {
25 | enumerable: false,
26 | value: function() {
27 | return [...new Set(this)];
28 | }
29 | });
30 |
--------------------------------------------------------------------------------
/libraries/polyfill.js:
--------------------------------------------------------------------------------
1 | () => {
2 |
3 | const exports = {
4 | requestIdleCallback
5 | };
6 |
7 | function requestIdleCallback() {
8 | if(!window.requestIdleCallback) {
9 | window.requestIdleCallback = function(callback, options) {
10 | var options = options || {};
11 | var relaxation = 1;
12 | var timeout = options.timeout || relaxation;
13 | var start = performance.now();
14 | return setTimeout(function () {
15 | callback({
16 | get didTimeout() {
17 | return options.timeout ? false : (performance.now() - start) - relaxation > timeout;
18 | },
19 | timeRemaining: function () {
20 | return Math.max(0, relaxation + (performance.now() - start));
21 | },
22 | });
23 | }, relaxation);
24 | };
25 | }
26 | return window.requestIdleCallback(...arguments);
27 | }
28 |
29 | return exports;
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/caches/statNameCache.js:
--------------------------------------------------------------------------------
1 | (request) => {
2 |
3 | const exports = {
4 | list: [],
5 | byName: {},
6 | validate
7 | };
8 |
9 | async function initialise() {
10 | const stats = await request.listItemStats();
11 | // frontend only
12 | stats.push('MAX_AMOUNT');
13 | stats.push('MASTERY_PET_PASSIVE');
14 | stats.push('MASTERY_DUNGEON_RUNE');
15 | stats.push('MASTERY_AUTOMATION'); // currently not used
16 | stats.push('MASTERY_BOUNTIFUL_HARVEST');
17 | stats.push('MASTERY_OPULENT_CRAFTING');
18 | stats.push('MASTERY_SAVAGE_LOOTING');
19 | stats.push('MASTERY_INSATIABLE_POWER');
20 | stats.push('MASTERY_POTENT_CONCOCTION');
21 | stats.push('MASTERY_RUNIC_WISDOM');
22 | for(const stat of stats) {
23 | exports.list.push(stat);
24 | exports.byName[stat] = stat;
25 | }
26 | return exports;
27 | }
28 |
29 | function validate(name) {
30 | if(!exports.byName[name]) {
31 | throw `Unsupported stat usage : ${name}`;
32 | }
33 | }
34 |
35 | return initialise();
36 |
37 | }
--------------------------------------------------------------------------------
/stores/variousStateStore.js:
--------------------------------------------------------------------------------
1 | (events) => {
2 |
3 | const emitEvent = events.emit.bind(null, 'state-various');
4 | const state = {};
5 |
6 | function initialise() {
7 | events.register('reader-various', handleReader);
8 | }
9 |
10 | function handleReader(event) {
11 | const updated = merge(state, event);
12 | if(updated) {
13 | emitEvent(state);
14 | }
15 | }
16 |
17 | function merge(target, source) {
18 | let updated = false;
19 | for(const key in source) {
20 | if(!(key in target)) {
21 | target[key] = source[key];
22 | updated = true;
23 | continue;
24 | }
25 | if(typeof target[key] === 'object' && typeof source[key] === 'object') {
26 | updated |= merge(target[key], source[key]);
27 | continue;
28 | }
29 | if(target[key] !== source[key]) {
30 | target[key] = source[key];
31 | updated = true;
32 | continue;
33 | }
34 | }
35 | return updated;
36 | }
37 |
38 | initialise();
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/caches/petCache.js:
--------------------------------------------------------------------------------
1 | (request) => {
2 |
3 | const exports = {
4 | list: [],
5 | byId: {},
6 | byName: {},
7 | byImage: {},
8 | idToIndex: {}
9 | };
10 |
11 | async function initialise() {
12 | const pets = await request.listPets();
13 | for(const pet of pets) {
14 | exports.list.push(pet);
15 | exports.byId[pet.id] = pet;
16 | exports.byName[pet.name] = pet;
17 | exports.idToIndex[pet.id] = exports.list.length-1;
18 | const lastPart = pet.image.split('/').at(-1);
19 | exports.byImage[lastPart] = pet;
20 | pet.abilities = [{
21 | [pet.abilityName1]: pet.abilityValue1
22 | }];
23 | if(pet.abilityName2) {
24 | pet.abilities.push({
25 | [pet.abilityName2]: pet.abilityValue2
26 | });
27 | }
28 | delete pet.abilityName1;
29 | delete pet.abilityValue1;
30 | delete pet.abilityName2;
31 | delete pet.abilityValue2;
32 | }
33 | return exports;
34 | }
35 |
36 | return initialise();
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/stores/lootStore.js:
--------------------------------------------------------------------------------
1 | (events, util) => {
2 |
3 | let state = null;
4 |
5 | function initialise() {
6 | events.register('reader-loot', handle);
7 | }
8 |
9 | function handle(event) {
10 | // first time
11 | if(state == null) {
12 | return emit(event, false);
13 | }
14 | // compare action and skill
15 | if(state.skill !== event.skill || state.action !== event.action) {
16 | return emit(event, false);
17 | }
18 | // check updated amounts
19 | if(Object.keys(event.loot).length !== Object.keys(state.loot).length) {
20 | return emit(event, true);
21 | }
22 | for(const key in event.loot) {
23 | if(event.loot[key] !== state.loot[key] || event.loot[key] !== state.loot[key]) {
24 | return emit(event, true);
25 | }
26 | }
27 | }
28 |
29 | function emit(event, includePartialDelta) {
30 | if(includePartialDelta) {
31 | event.delta = util.deltaObjects(state.loot, event.loot);
32 | } else {
33 | event.delta = event.loot;
34 | }
35 | state = event;
36 | events.emit('state-loot', state);
37 | }
38 |
39 | initialise();
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/readers/structuresReader.js:
--------------------------------------------------------------------------------
1 | (events, util, structuresCache) => {
2 |
3 | const emitEvent = events.emit.bind(null, 'reader-structures');
4 |
5 | function initialise() {
6 | events.register('page', update);
7 | window.setInterval(update, 1000);
8 | }
9 |
10 | function update() {
11 | const page = events.getLast('page');
12 | if(!page) {
13 | return;
14 | }
15 | if(page.type === 'structure' && $('home-page .categories .category-active').text() === 'Build') {
16 | readStructuresScreen();
17 | }
18 | }
19 |
20 | function readStructuresScreen() {
21 | const structures = {};
22 | $('home-page .categories + .card button').each((i,element) => {
23 | element = $(element);
24 | const name = element.find('.name').text();
25 | const structure = structuresCache.byName[name];
26 | if(!structure) {
27 | return;
28 | }
29 | const level = util.parseNumber(element.find('.level').text());
30 | structures[structure.id] = level;
31 | });
32 | emitEvent({
33 | type: 'full',
34 | value: structures
35 | });
36 | }
37 |
38 | initialise();
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/libraries/events.js:
--------------------------------------------------------------------------------
1 | () => {
2 |
3 | const exports = {
4 | register,
5 | emit,
6 | getLast,
7 | getLastCache
8 | };
9 |
10 | const handlers = {};
11 | const lastCache = {};
12 |
13 | function register(name, handler) {
14 | if(!handlers[name]) {
15 | handlers[name] = [];
16 | }
17 | handlers[name].push(handler);
18 | if(lastCache[name]) {
19 | handle(handler, lastCache[name], name);
20 | }
21 | }
22 |
23 | // options = { skipCache }
24 | function emit(name, data, options) {
25 | if(!options?.skipCache) {
26 | lastCache[name] = data;
27 | }
28 | if(!handlers[name]) {
29 | return;
30 | }
31 | for(const handler of handlers[name]) {
32 | handle(handler, data, name);
33 | }
34 | }
35 |
36 | function handle(handler, data, name) {
37 | try {
38 | handler(data, name);
39 | } catch(e) {
40 | console.error('Something went wrong', e);
41 | }
42 | }
43 |
44 | function getLast(name) {
45 | return lastCache[name];
46 | }
47 |
48 | function getLastCache() {
49 | return lastCache;
50 | }
51 |
52 | return exports;
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/readers/enchantmentsReader.js:
--------------------------------------------------------------------------------
1 | (events, util, structuresCache) => {
2 |
3 | const emitEvent = events.emit.bind(null, 'reader-enchantments');
4 |
5 | function initialise() {
6 | events.register('page', update);
7 | window.setInterval(update, 1000);
8 | }
9 |
10 | function update() {
11 | const page = events.getLast('page');
12 | if(!page) {
13 | return;
14 | }
15 | if(page.type === 'enchantment' && $('home-page .categories .category-active').text() === 'Enchant') {
16 | readEnchantmentsScreen();
17 | }
18 | }
19 |
20 | function readEnchantmentsScreen() {
21 | const enchantments = {};
22 | $('home-page .categories + .card button').each((i,element) => {
23 | element = $(element);
24 | const name = element.find('.name').text();
25 | const structure = structuresCache.byName[name];
26 | if(!structure) {
27 | return;
28 | }
29 | const level = util.parseNumber(element.find('.level').text());
30 | enchantments[structure.id] = level;
31 | });
32 | emitEvent({
33 | type: 'full',
34 | value: enchantments
35 | });
36 | }
37 |
38 | initialise();
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/readers/guildStructuresReader.js:
--------------------------------------------------------------------------------
1 | (events, util, structuresCache) => {
2 |
3 | const emitEvent = events.emit.bind(null, 'reader-structures-guild');
4 |
5 | function initialise() {
6 | events.register('page', update);
7 | window.setInterval(update, 1000);
8 | }
9 |
10 | function update() {
11 | const page = events.getLast('page');
12 | if(!page) {
13 | return;
14 | }
15 | if(page.type === 'guild' && $('guild-page .tracker ~ div button.row-active .name').text() === 'Buildings') {
16 | readGuildStructuresScreen();
17 | }
18 | }
19 |
20 | function readGuildStructuresScreen() {
21 | const structures = {};
22 | $('guild-page .card').first().find('button').each((i,element) => {
23 | element = $(element);
24 | const name = element.find('.name').text();
25 | const structure = structuresCache.byName[name];
26 | if(!structure) {
27 | return;
28 | }
29 | const level = util.parseNumber(element.find('.amount').text());
30 | structures[structure.id] = level;
31 | });
32 | emitEvent({
33 | type: 'full',
34 | value: structures
35 | });
36 | }
37 |
38 | initialise();
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/features/recipeClickthrough.js:
--------------------------------------------------------------------------------
1 | (recipeCache, configuration, util) => {
2 |
3 | let enabled = false;
4 |
5 | function initialise() {
6 | configuration.registerCheckbox({
7 | category: 'UI Features',
8 | key: 'recipe-click',
9 | name: 'Recipe clickthrough',
10 | default: true,
11 | handler: handleConfigStateChange
12 | });
13 | $(document).on('click', 'div.image > img', handleClick);
14 | }
15 |
16 | function handleConfigStateChange(state) {
17 | enabled = state;
18 | }
19 |
20 | function handleClick(event) {
21 | if(!enabled) {
22 | return;
23 | }
24 | if($(event.currentTarget).closest('button').length) {
25 | return;
26 | }
27 | event.stopPropagation();
28 | const name = $(event.relatedTarget).find('.name').text();
29 | const nameMatch = recipeCache.byName[name];
30 | if(nameMatch) {
31 | return followRecipe(nameMatch);
32 | }
33 |
34 | const parts = event.target.src.split('/');
35 | const lastPart = parts[parts.length-1];
36 | const imageMatch = recipeCache.byImage[lastPart];
37 | if(imageMatch) {
38 | return followRecipe(imageMatch);
39 | }
40 | }
41 |
42 | function followRecipe(recipe) {
43 | util.goToPage(recipe.url);
44 | }
45 |
46 | initialise();
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/stores/abstractStateStore.js:
--------------------------------------------------------------------------------
1 | (events, util) => {
2 |
3 | const SOURCES = [
4 | 'inventory',
5 | 'equipment-runes',
6 | 'equipment-tomes',
7 | 'structures',
8 | 'enchantments',
9 | 'structures-guild',
10 | 'marks',
11 | 'traits'
12 | ];
13 |
14 | const stateBySource = {};
15 |
16 | function initialise() {
17 | for(const source of SOURCES) {
18 | stateBySource[source] = {};
19 | events.register(`reader-${source}`, handleReader.bind(null, source));
20 | }
21 | }
22 |
23 | function handleReader(source, event) {
24 | let updated = false;
25 | if(event.type === 'full' || event.type === 'cache') {
26 | if(util.compareObjects(stateBySource[source], event.value)) {
27 | return;
28 | }
29 | updated = true;
30 | stateBySource[source] = event.value;
31 | }
32 | if(event.type === 'partial') {
33 | for(const key of Object.keys(event.value)) {
34 | if(stateBySource[source][key] === event.value[key]) {
35 | continue;
36 | }
37 | updated = true;
38 | stateBySource[source][key] = event.value[key];
39 | }
40 | }
41 | if(updated) {
42 | events.emit(`state-${source}`, stateBySource[source]);
43 | }
44 | }
45 |
46 | initialise();
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/stores/equipmentStateStore.js:
--------------------------------------------------------------------------------
1 | (events, util, itemCache) => {
2 |
3 | let state = {};
4 |
5 | function initialise() {
6 | events.register('reader-equipment-equipment', handleEquipmentReader);
7 | }
8 |
9 | function handleEquipmentReader(event) {
10 | let updated = false;
11 | if(event.type === 'full' || event.type === 'cache') {
12 | if(util.compareObjects(state, event.value)) {
13 | return;
14 | }
15 | updated = true;
16 | state = event.value;
17 | }
18 | if(event.type === 'partial') {
19 | for(const key of Object.keys(event.value)) {
20 | if(state[key] === event.value[key]) {
21 | continue;
22 | }
23 | updated = true;
24 | // remove items of similar type
25 | for(const itemType in itemCache.specialIds) {
26 | if(Array.isArray(itemCache.specialIds[itemType]) && itemCache.specialIds[itemType].includes(+key)) {
27 | for(const itemId of itemCache.specialIds[itemType]) {
28 | delete state[itemId];
29 | }
30 | }
31 | }
32 | state[key] = event.value[key];
33 | }
34 | }
35 | if(updated) {
36 | events.emit('state-equipment-equipment', state);
37 | }
38 | }
39 |
40 | initialise();
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/readers/equipmentReader.js:
--------------------------------------------------------------------------------
1 | (events, itemUtil) => {
2 |
3 | function initialise() {
4 | events.register('page', update);
5 | window.setInterval(update, 1000);
6 | }
7 |
8 | function update() {
9 | const page = events.getLast('page');
10 | if(!page) {
11 | return;
12 | }
13 | if(page.type === 'equipment') {
14 | readEquipmentScreen();
15 | }
16 | if(page.type === 'action') {
17 | readActionScreen();
18 | }
19 | }
20 |
21 | function readEquipmentScreen() {
22 | const equipment = {};
23 | const activeTab = $('equipment-page .categories button[disabled]').text().toLowerCase();
24 | $('equipment-page .header + .items > .item > .description').parent().each((i,element) => {
25 | itemUtil.extractItem(element, equipment);
26 | });
27 | events.emit(`reader-equipment-${activeTab}`, {
28 | type: 'full',
29 | value: equipment
30 | });
31 | }
32 |
33 | function readActionScreen() {
34 | const equipment = {};
35 | $('skill-page .header > .name:contains("Consumables")').closest('.card').find('button > .name:not(.placeholder)').parent().each((i,element) => {
36 | itemUtil.extractItem(element, equipment);
37 | });
38 | events.emit('reader-equipment-equipment', {
39 | type: 'partial',
40 | value: equipment
41 | });
42 | }
43 |
44 | initialise();
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/libraries/hotkey.js:
--------------------------------------------------------------------------------
1 | () => {
2 | const keyHandlers = new Map();
3 |
4 | const exports = {
5 | attach,
6 | detach,
7 | detachAll
8 | };
9 |
10 | function initialise() {
11 | $(window).on('keydown._globalKeyManager', onKeydown);
12 | }
13 |
14 | function onKeydown(e) {
15 | const el = document.activeElement;
16 | const isUserFocusable =
17 | el.tagName === 'INPUT' ||
18 | el.tagName === 'TEXTAREA' ||
19 | el.tagName === 'BUTTON' ||
20 | el.isContentEditable;
21 |
22 | const key = e.key.toLowerCase();
23 | const handler = keyHandlers.get(key);
24 |
25 | if (!handler) return;
26 |
27 | if (isUserFocusable && !handler.override) return;
28 |
29 | e.preventDefault();
30 | handler.callback(e);
31 | }
32 |
33 | function attach(key, callback, override = false) {
34 | if (typeof key !== 'string' || typeof callback !== 'function' || key.trim() === '') return;
35 |
36 | const normalizedKey = key.trim().toLowerCase();
37 | keyHandlers.set(normalizedKey, { callback, override });
38 | }
39 |
40 | function detach(key) {
41 | if (typeof key !== 'string' || key.trim() === '') return;
42 |
43 | const normalizedKey = key.trim().toLowerCase();
44 | keyHandlers.delete(normalizedKey);
45 | }
46 |
47 | function detachAll() {
48 | keyHandlers.clear();
49 | }
50 |
51 | initialise();
52 |
53 | return exports;
54 | }
55 |
--------------------------------------------------------------------------------
/features/marketListingLimitWarning.js:
--------------------------------------------------------------------------------
1 | (events, configuration, colorMapper) => {
2 |
3 | const LISTING_LIMIT = 250;
4 | let enabled = false;
5 |
6 | function initialise() {
7 | configuration.registerCheckbox({
8 | category: 'Market',
9 | key: 'market-listing-limit-warning',
10 | name: 'Listing limit warning',
11 | default: true,
12 | handler: handleConfigStateChange
13 | });
14 | events.register('reader-market', update);
15 | }
16 |
17 | function handleConfigStateChange(state) {
18 | enabled = state;
19 | }
20 |
21 | function update(marketData) {
22 | $('.market-listing-limit-warning').remove();
23 | if(!enabled) {
24 | return;
25 | }
26 | if(marketData.type === 'OWN') {
27 | return;
28 | }
29 | if(marketData.count <= LISTING_LIMIT) {
30 | return;
31 | }
32 | if(marketData.listings.length < LISTING_LIMIT) {
33 | return;
34 | }
35 | $('market-page .count').before(`
36 |