master e3b7beab64ec cached
56 files
113.3 KB
34.2k tokens
136 symbols
1 requests
Download .txt
Repository: Mardaneus86/futwebapp-tampermonkey
Branch: master
Commit: e3b7beab64ec
Files: 56
Total size: 113.3 KB

Directory structure:
gitextract_atpnmaik/

├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github/
│   ├── FUNDING.yml
│   └── ISSUE_TEMPLATE/
│       ├── bug_report.md
│       └── feature_request.md
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── analytics/
│   ├── LICENSE
│   ├── config.js
│   └── index.js
├── app/
│   ├── core/
│   │   ├── analytics.js
│   │   ├── base-script.js
│   │   ├── browser.js
│   │   ├── db.js
│   │   ├── index.js
│   │   ├── queue.js
│   │   ├── settings-entry.js
│   │   └── settings.js
│   ├── futbin/
│   │   ├── futbin-player-links.js
│   │   ├── futbin-prices.js
│   │   ├── index.js
│   │   ├── settings-entry.js
│   │   └── style/
│   │       └── futbin-prices.scss
│   ├── index.js
│   ├── index.scss
│   ├── instant-bin-confirm/
│   │   ├── index.js
│   │   ├── instant-bin-confirm.js
│   │   └── settings-entry.js
│   ├── settings/
│   │   ├── html/
│   │   │   └── index/
│   │   │       └── settings.html
│   │   ├── index.js
│   │   └── index.scss
│   └── transferlist/
│       ├── card-info.js
│       ├── index.js
│       ├── list-size.js
│       ├── min-bin.js
│       ├── refresh-list.js
│       ├── style/
│       │   ├── card-info.scss
│       │   ├── refresh-list.scss
│       │   └── transfer-totals.scss
│       └── transfer-totals.js
├── fut/
│   ├── club.js
│   ├── errors/
│   │   └── index.js
│   ├── index.js
│   ├── logger.js
│   ├── pinEvent.js
│   ├── priceTiers.js
│   ├── store.js
│   ├── transferMarket.js
│   └── utils.js
├── package.json
├── tampermonkey-headers.js
├── webpack.config.js
└── webpack.config.prd.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# top-most EditorConfig file
root = true

[*]
end_of_line = lf
insert_final_newline = true

[*.js]
indent_style = space
indent_size = 2


================================================
FILE: .eslintignore
================================================
dist/
tampermonkey-headers.js
webpack.config.js
webpack.config.prd.js


================================================
FILE: .eslintrc
================================================
{
  "extends": "airbnb",
  "parser": "babel-eslint",
  "rules": {
    "func-names": ["error", "never"],
    "import/prefer-default-export": "off",
    "no-underscore-dangle": "off"
  },
  "globals": {
    "UA_TOKEN": false,
    "getAppMain": false,
    "GM_notification": false
  },
  "env": {
    "greasemonkey": true
  }
}


================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: Mardaneus86
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ["https://www.paypal.me/timklingeleers"]


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''

---

READ BEFORE YOU ADD AN ISSUE: Do not request autobuyer features, because they are considered as cheating. Issues regarding autobuyers will be closed immediately.

### Expected behavior:
Give a detailed explanation of the expected behavior.

### Current behavior:
Give a detailed explanation of the current behavior.

### Metadata:
- **Script version:**
- **Browser:**
- **OS:**

### To Reproduce
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

### Screenshots
If applicable, add screenshots to help explain your problem.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''

---

READ BEFORE YOU ADD A FEATURE REQUEST: Do not request autobuyer features, because they are considered as cheating. Issues regarding autobuyers will be closed immediately.

### Describe the solution you'd like
A clear and concise description of what you want to happen.

### Suggestions for implementation
Add any other context, mockups or screenshots about the feature request here.


================================================
FILE: .gitignore
================================================
dist/
node_modules/
.vscode/


================================================
FILE: .travis.yml
================================================
language: node_js
cache:
  directories:
  - ~/.npm
  - ~/node_modules
notifications:
  email: false
node_js:
  - '8'
before_script:
  - npm prune
  - git fetch --tags
  - export TM_VERSION=$(npm run semantic-release:dry | grep "The next release version is " | sed 's/[^0-9.]*\([0-9.]*\).*/\1/')
script:
  - npm run build:production
env:
  - NPM_TOKEN="00000000-0000-0000-0000-000000000000"
branches:
  except:
    - /^v\d+\.\d+\.\d+$/
after_success:
  # CREATE GIT TAG
  - git config --global user.email "builds@travis-ci.com"
  - git config --global user.name "Travis CI"
  - '[[ $TRAVIS_BRANCH == "master" ]] && git tag $TM_VERSION -a -m "Generated tag from TravisCI build $TRAVIS_BUILD_NUMBER"'
  - '[[ $TRAVIS_BRANCH == "master" ]] && git push --quiet https://$GITHUBKEY@github.com/Mardaneus86/futwebapp-tampermonkey $GIT_TAG > /dev/null 2>&1'
deploy:
  provider: releases
  api_key:
    secure: "itypG5lXUZkA647w7CUiagtzgr617UA640j17OFgzsChADpmilsdsEHV8afcuzts/CO+nzxbCO42X6jeBjYKEMvSSe1DInXCy9p0OcryUvTYsTM2zYvqTRn7syF5cN+2B3BTIQERk8nct+mao0p9iKDRvna9l6OI2MGLHm6nEsP/b4VmYMJwemleLY+dHeBLF1eSlEvg6sOHf8MZp6+OU78tUUUfb6+EW0+EpOomf4FG5D+XJn0Q7naMc6L4ehI8lvxcbuM7ECVgXBcw0ixnC/OS/JcpFYlNFHjTUqK/WDXWvoMkJPWff/+2SPYk0bnFJRI7LGCqF1DP0Yzn8Sz+TsNwkJ/plWNgZmq1GC1rEl3fVxHNRwQXb0Qu66BeK/5qJqsvZ1DW+96OwGPEYnUZBAqCVLK/4IHg1yDFTWsSs+sB+ECJNmRsXVF9dLluA3qtKlkujkTcrl5iNXptBx0czRwEvZmzZfEL8OS6naEIc396wEs4YT02vRsg8wp4psMW7YnetDcnVbqcNT9I0leFCSd7MKVFQLVfs5ybhXpb40Eo/+luq6N45GFMS+QazMnoyggAqtZ/g8HoCE0K+pK6YzbrboXeMui50yzMwwRWojM+XFSIKzcSOYP7AWWof66RKfOHqlBVYw8nhFsJ3xRQxzfp6528hZeJpWvRBFzHjbU="
  file:
    - dist/fut-enhancer.user.js
  skip_cleanup: true


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2017 Tim Klingeleers

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# FUT Web App - TamperMonkey scripts

[![Join the chat at https://gitter.im/futwebapp-tampermonkey/Lobby](https://badges.gitter.im/futwebapp-tampermonkey/Lobby.svg)](https://gitter.im/futwebapp-tampermonkey/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

## :warning: Discontinued
**Due to personal time constraints I can no longer keep this script working at all times, as you have all noted over the past few months. So I've taken the decision to stop working on this project and create some clarity for the community.**

**The code will remain available in the Github repository and on OpenUserJS. Maybe someone is willing to continue the project in a fork. If there is a fork with enough trustworthiness, I'm happy to route everyone there. Send me a message in case you want me to link to your fork.**

**This project has been a great journey for me, and I'm very thankful for all the support of the community over the years.**

FIFA 21's companion app for FIFA Ultimate Team, the FUT 21 Web App, is a website that let's you trade and manage your team on the go.

This TamperMonkey script is meant to enhance the FUT 21 Web App experience. You can install the script following the instructions below. Afterwards you will get a settings button on the bottom right of the web app, where you can enable every feature by itself. The script provides a certain degree of customization possibilities.

:warning: Using this script is at your own risk. EA might (temp-)ban you for altering parts of their Web App.

:bangbang: Do not request autobuyer features. Because they are considered to be cheating, it will not be added.

I started this project to learn about reverse engineering big Javascript codebases.

If you benefit from this project, you can buy me a beer :beers: :+1:

[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.me/timklingeleers)

## Features
- [x] Futbin integration
  - [x] Show Futbin prices on all player cards throughout the app
  - [x] Show link to player on Futbin
  - [x] Mark bargains (BIN price lower then Futbin value)
- [x] Find minimum BIN value of players
- [x] Refresh transfer list
- [x] Increase transfer list size
- [x] Extra card information (contracts)
- [x] Total coin value for cards on the transfer list

## Installation
Make sure you have user scripts enabled in your browser (these instructions refer to the latest versions of the browser):

* Firefox - install [Tampermonkey](https://tampermonkey.net/?ext=dhdg&browser=firefox). :warning: Has issues loading properly (see issue #115)
* Chrome - install [Tampermonkey](https://tampermonkey.net/?ext=dhdg&browser=chrome).
* Opera - install [Tampermonkey](https://tampermonkey.net/?ext=dhdg&browser=opera).
* Safari - install [Tampermonkey](https://tampermonkey.net/?ext=dhdg&browser=safari).
* Dolphin - install [Tampermonkey](https://tampermonkey.net/?ext=dhdg&browser=dolphin).
* UC Browser - install [Tampermonkey](https://tampermonkey.net/?ext=dhdg&browser=ucweb).

### Install scripts
Install the scripts via [OpenUserJS][install-script]. Or find the latest version and release notes at the [releases page](https://github.com/Mardaneus86/futwebapp-tampermonkey/releases).

## Feature requests
If you feel there are missing features, feel free to add a request to the [issue list][issue-list]. Make sure to provide the necessary details, or even a mockup of what the feature would look like.

## Issues
File a bug report in the [issue list][issue-list].

## Developing
Clone this repository and execute:
```
npm install
```

To start the bundling process and linting process, execute:
```
npm start
```

Make sure to enable `Allow access to file URLs` in `chrome://extensions/` for Tampermonkey, and add the following script snippet:
```
// ==UserScript==
// @name        FUT Enhancer dev
// @version     0.1
// @description
// @license     MIT
// @author      Tim Klingeleers
// @match       https://www.easports.com/fifa/ultimate-team/web-app/*
// @match       https://www.easports.com/*/fifa/ultimate-team/web-app/*
// @match       https://www.ea.com/fifa/ultimate-team/web-app/*
// @match       https://www.ea.com/*/fifa/ultimate-team/web-app/*
// @namespace   https://github.com/Mardaneus86
// @supportURL  https://github.com/Mardaneus86/futwebapp-tampermonkey/issues
// @grant       GM_notification
// @grant       GM_xmlhttpRequest
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       window.focus
// @require     file:///<path to repo>/dist/fut-enhancer.user.js
// @connect     ea.com
// @connect     futbin.com
// ==/UserScript==
```

Remember to change the path after `@require` to the folder where you cloned the repository. It should point to the generated `fut-enhancer.user.js` in the `dist` folder.

## Contribute
Add a feature request or bug to the [issue list][issue-list] before doing a PR in order to discuss it before implementing a fix. Issues that are marked with the `help wanted` have priority if you want to help.

[issue-list]: https://github.com/Mardaneus86/futwebapp-tampermonkey/issues
[install-script]: https://openuserjs.org/install/Mardaneus86/FUT_Enhancer.user.js


================================================
FILE: analytics/LICENSE
================================================
(The MIT License)

Copyright (c) 2017 Peaks & Pies GmbH <hello@peaksandpies.com>;

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

================================================
FILE: analytics/config.js
================================================
/* eslint-disable */
module.exports = {
	protocolVersion: "1",
	hostname: "https://www.google-analytics.com",
	path: "/collect",
	batchPath: "/batch",
	batching: true,
	batchSize: 10,
	acceptedParameters: [

		// General
		"v", "tid", "aip", "ds", "qt", "z",

		// User
		"cid", "uid",

		// Session
		"sc", "uip", "ua", "geoid",

		// Traffic Sources
		"dr", "cn", "cs", "cm", "ck", "cc", "ci", "gclid", "dclid",

		// System Info
		"sr", "vp", "de", "sd", "ul", "je", "fl",

		// Hit
		"t", "ni",

		// Content Information
		"dl", "dh", "dp", "dt", "cd", "linkid",

		// App Tracking
		"an", "aid", "av", "aiid",

		// Event Tracking
		"ec", "ea", "el", "ev",

		// E-commerce (transaction data: simple and enhanced)
		"ti", "ta", "tr", "ts", "tt",

		// E-commerce (item data: simple)
		"in", "ip", "iq", "ic", "iv",

		// E-commerce (currency: simple and enhanced)
		"cu",

		// Enhanced E-Commerce (see also: regex below)
		"pa", "tcc", "pal", "cos", "col", "promoa",

		// Social Interactions
		"sn", "sa", "st",

		// Timing
		"utc", "utv", "utt", "utl", "plt", "dns", "pdt", "rrt", "tcp", "srt", "dit", "clt",

		// Exceptions
		"exd", "exf",

		// Content Experiments
		"xid", "xvar"],

	acceptedParametersRegex: [
		/^cm[0-9]+$/,
		/^cd[0-9]+$/,
		/^cg(10|[0-9])$/,

		/pr[0-9]{1,3}id/,
		/pr[0-9]{1,3}nm/,
		/pr[0-9]{1,3}br/,
		/pr[0-9]{1,3}ca/,
		/pr[0-9]{1,3}va/,
		/pr[0-9]{1,3}pr/,
		/pr[0-9]{1,3}qt/,
		/pr[0-9]{1,3}cc/,
		/pr[0-9]{1,3}ps/,
		/pr[0-9]{1,3}cd[0-9]{1,3}/,
		/pr[0-9]{1,3}cm[0-9]{1,3}/,

		/il[0-9]{1,3}nm/,
		/il[0-9]{1,3}pi[0-9]{1,3}id/,
		/il[0-9]{1,3}pi[0-9]{1,3}nm/,
		/il[0-9]{1,3}pi[0-9]{1,3}br/,
		/il[0-9]{1,3}pi[0-9]{1,3}ca/,
		/il[0-9]{1,3}pi[0-9]{1,3}va/,
		/il[0-9]{1,3}pi[0-9]{1,3}ps/,
		/il[0-9]{1,3}pi[0-9]{1,3}pr/,
		/il[0-9]{1,3}pi[0-9]{1,3}cd[0-9]{1,3}/,
		/il[0-9]{1,3}pi[0-9]{1,3}cm[0-9]{1,3}/,

		/promo[0-9]{1,3}id/,
		/promo[0-9]{1,3}nm/,
		/promo[0-9]{1,3}cr/,
		/promo[0-9]{1,3}ps/
	],
	parametersMap: {
		"protocolVersion": "v",
		"trackingId": "tid",
		"webPropertyId": "tid",
		"anonymizeIp": "aip",
		"dataSource": "ds",
		"queueTime": "qt",
		"cacheBuster": "z",
		"clientId": "cid",
		"userId": "uid",
		"sessionControl": "sc",
		"ipOverride": "uip",
		"userAgentOverride": "ua",
		"documentReferrer": "dr",
		"campaignName": "cn",
		"campaignSource": "cs",
		"campaignMedium": "cm",
		"campaignKeyword": "ck",
		"campaignContent": "cc",
		"campaignId": "ci",
		"googleAdwordsId": "gclid",
		"googleDisplayAdsId": "dclid",
		"screenResolution": "sr",
		"viewportSize": "vp",
		"documentEncoding": "de",
		"screenColors": "sd",
		"userLanguage": "ul",
		"javaEnabled": "je",
		"flashVersion": "fl",
		"hitType": "t",
		"non-interactionHit": "ni",
		"documentLocationUrl": "dl",
		"documentHostName": "dh",
		"documentPath": "dp",
		"documentTitle": "dt",
		"screenName": "cd",
		"linkId": "linkid",
		"applicationName": "an",
		"applicationId": "aid",
		"applicationVersion": "av",
		"applicationInstallerId": "aiid",
		"eventCategory": "ec",
		"eventAction": "ea",
		"eventLabel": "el",
		"eventValue": "ev",
		"transactionId": "ti",
		"transactionAffiliation": "ta",
		"transactionRevenue": "tr",
		"transactionShipping": "ts",
		"transactionTax": "tt",
		"itemName": "in",
		"itemPrice": "ip",
		"itemQuantity": "iq",
		"itemCode": "ic",
		"itemCategory": "iv",
		"currencyCode": "cu",
		"socialNetwork": "sn",
		"socialAction": "sa",
		"socialActionTarget": "st",
		"userTimingCategory": "utc",
		"userTimingVariableName": "utv",
		"userTimingTime": "utt",
		"userTimingLabel": "utl",
		"pageLoadTime": "plt",
		"dnsTime": "dns",
		"pageDownloadTime": "pdt",
		"redirectResponseTime": "rrt",
		"tcpConnectTime": "tcp",
		"serverResponseTime": "srt",
		"domInteractiveTime": "dit",
		"contentLoadTime": "clt",
		"exceptionDescription": "exd",
		"isExceptionFatal": "exf",
		"isExceptionFatal?": "exf",
		"experimentId": "xid",
		"experimentVariant": "xvar"
	}
};


================================================
FILE: analytics/index.js
================================================
/* eslint-disable */
import querystring from 'querystring';

import config from './config';

module.exports = init;

function init (tid, cid, options) {
  return new Visitor(tid, cid, options);
}

var Visitor = module.exports.Visitor = function (tid, cid, options) {

  this._queue = [];

  this.options = options || {};

  if(this.options.hostname) {
    config.hostname = this.options.hostname;
  }
  if(this.options.path) {
    config.path = this.options.path;
  }

  if(this.options.enableBatching !== undefined) {
    config.batching = options.enableBatching;
  }

  if(this.options.batchSize) {
    config.batchSize = this.options.batchSize;
  }

  this._context = {};
  this._persistentParams = {};

  this.tid = this.options.tid;
  this.cid = this.options.cid;
  if(this.options.uid) {
    this.uid = this.options.uid;
  }
}

Visitor.prototype = {
  reset: function () {
    this._context = null;
    return this;
  },

  set: function (key, value) {
    this._persistentParams = this._persistentParams || {};
    this._persistentParams[key] = value;
  },

  pageview: function (path, hostname, title, params, fn) {

    if (typeof path === 'object' && path != null) {
      params = path;
      if (typeof hostname === 'function') {
        fn = hostname
      }
      path = hostname = title  = null;
    } else if (typeof hostname === 'function') {
      fn = hostname
      hostname = title = null;
    } else if (typeof title === 'function') {
      fn = title;
      title = null;
    } else if (typeof params === 'function') {
      fn = params;
      params = null;
    }

    params = this._translateParams(params);

    params = Object.assign({}, this._persistentParams || {}, params);

    params.dp = path || params.dp || this._context.dp;
    params.dh = hostname || params.dh || this._context.dh;
    params.dt = title || params.dt || this._context.dt;

    this._tidyParameters(params);

    if (!params.dp && !params.dl) {
      return this._handleError('Please provide either a page path (dp) or a document location (dl)', fn);
    }

    return this._withContext(params)._enqueue('pageview', params, fn);
  },


  screenview: function (screenName, appName, appVersion, appId, appInstallerId, params, fn) {

      if (typeof screenName === 'object' && screenName != null) {
          params = screenName;
          if (typeof appName === 'function') {
              fn = appName
          }
          screenName = appName = appVersion = appId = appInstallerId = null;
      } else if (typeof appName === 'function') {
          fn = appName
          appName = appVersion = appId = appInstallerId = null;
      } else if (typeof appVersion === 'function') {
          fn = appVersion;
          appVersion = appId = appInstallerId = null;
      } else if (typeof appId === 'function') {
          fn = appId;
          appId = appInstallerId = null;
      } else if (typeof appInstallerId === 'function') {
          fn = appInstallerId;
          appInstallerId = null;
      } else if (typeof params === 'function') {
          fn = params;
          params = null;
      }

      params = this._translateParams(params);

      params = Object.assign({}, this._persistentParams || {}, params);

      params.cd = screenName || params.cd || this._context.cd;
      params.an = appName || params.an || this._context.an;
      params.av = appVersion || params.av || this._context.av;
      params.aid = appId || params.aid || this._context.aid;
      params.aiid = appInstallerId || params.aiid || this._context.aiid;

      this._tidyParameters(params);

      if (!params.cd || !params.an) {
          return this._handleError('Please provide at least a screen name (cd) and an app name (an)', fn);
      }

      return this._withContext(params)._enqueue('screenview', params, fn);
  },


  event: function (category, action, label, value, params, fn) {

    if (typeof category === 'object' && category != null) {
      params = category;
      if (typeof action === 'function') {
        fn = action
      }
      category = action = label = value = null;
    } else if (typeof label === 'function') {
      fn = label;
      label = value = null;
    } else if (typeof value === 'function') {
      fn = value;
      value = null;
    } else if (typeof params === 'function') {
      fn = params;
      params = null;
    }

    params = this._translateParams(params);

    params = Object.assign({}, this._persistentParams || {}, params);

    params.ec = category || params.ec || this._context.ec;
    params.ea = action || params.ea || this._context.ea;
    params.el = label || params.el || this._context.el;
    params.ev = value || params.ev || this._context.ev;
    params.p = params.p || params.dp || this._context.p || this._context.dp;

    delete params.dp;
    this._tidyParameters(params);

    if (!params.ec || !params.ea) {
      return this._handleError('Please provide at least an event category (ec) and an event action (ea)', fn);
    }

    return this._withContext(params)._enqueue('event', params, fn);
  },


  transaction: function (transaction, revenue, shipping, tax, affiliation, params, fn) {
    if (typeof transaction === 'object') {
      params = transaction;
      if (typeof revenue === 'function') {
        fn = revenue
      }
      transaction = revenue = shipping = tax = affiliation = null;
    } else if (typeof revenue === 'function') {
      fn = revenue;
      revenue = shipping = tax = affiliation = null;
    } else if (typeof shipping === 'function') {
      fn = shipping;
      shipping = tax = affiliation = null;
    } else if (typeof tax === 'function') {
      fn = tax;
      tax = affiliation = null;
    } else if (typeof affiliation === 'function') {
      fn = affiliation;
      affiliation = null;
    } else if (typeof params === 'function') {
      fn = params;
      params = null;
    }

    params = this._translateParams(params);

    params = Object.assign({}, this._persistentParams || {}, params);

    params.ti = transaction || params.ti || this._context.ti;
    params.tr = revenue || params.tr || this._context.tr;
    params.ts = shipping || params.ts || this._context.ts;
    params.tt = tax || params.tt || this._context.tt;
    params.ta = affiliation || params.ta || this._context.ta;
    params.p = params.p || this._context.p || this._context.dp;

    this._tidyParameters(params);

    if (!params.ti) {
      return this._handleError('Please provide at least a transaction ID (ti)', fn);
    }

    return this._withContext(params)._enqueue('transaction', params, fn);
  },


  item: function (price, quantity, sku, name, variation, params, fn) {
    if (typeof price === 'object') {
      params = price;
      if (typeof quantity === 'function') {
        fn = quantity
      }
      price = quantity = sku = name = variation = null;
    } else if (typeof quantity === 'function') {
      fn = quantity;
      quantity = sku = name = variation = null;
    } else if (typeof sku === 'function') {
      fn = sku;
      sku = name = variation = null;
    } else if (typeof name === 'function') {
      fn = name;
      name = variation = null;
    } else if (typeof variation === 'function') {
      fn = variation;
      variation = null;
    } else if (typeof params === 'function') {
      fn = params;
      params = null;
    }

    params = this._translateParams(params);

    params = Object.assign({}, this._persistentParams || {}, params);

    params.ip = price || params.ip || this._context.ip;
    params.iq = quantity || params.iq || this._context.iq;
    params.ic = sku || params.ic || this._context.ic;
    params.in = name || params.in || this._context.in;
    params.iv = variation || params.iv || this._context.iv;
    params.p = params.p || this._context.p || this._context.dp;
    params.ti = params.ti || this._context.ti;

    this._tidyParameters(params);

    if (!params.ti) {
      return this._handleError('Please provide at least an item transaction ID (ti)', fn);
    }

    return this._withContext(params)._enqueue('item', params, fn);

  },

  exception: function (description, fatal, params, fn) {

    if (typeof description === 'object') {
      params = description;
      if (typeof fatal === 'function') {
        fn = fatal;
      }
      description = fatal = null;
    } else if (typeof fatal === 'function') {
      fn = fatal;
      fatal = 0;
    } else if (typeof params === 'function') {
      fn = params;
      params = null;
    }

    params = this._translateParams(params);

    params = Object.assign({}, this._persistentParams || {}, params);

    params.exd = description || params.exd || this._context.exd;
    params.exf = +!!(fatal || params.exf || this._context.exf);

    if (params.exf === 0) {
      delete params.exf;
    }

    this._tidyParameters(params);

    return this._withContext(params)._enqueue('exception', params, fn);
  },

  timing: function (category, variable, time, label, params, fn) {

    if (typeof category === 'object') {
      params = category;
      if (typeof variable === 'function') {
        fn = variable;
      }
      category = variable = time = label = null;
    } else if (typeof variable === 'function') {
      fn = variable;
      variable = time = label = null;
    } else if (typeof time === 'function') {
      fn = time;
      time = label = null;
    } else if (typeof label === 'function') {
      fn = label;
      label = null;
    } else if (typeof params === 'function') {
      fn = params;
      params = null;
    }

    params = this._translateParams(params);

    params = Object.assign({}, this._persistentParams || {}, params);

    params.utc = category || params.utc || this._context.utc;
    params.utv = variable || params.utv || this._context.utv;
    params.utt = time || params.utt || this._context.utt;
    params.utl = label || params.utl || this._context.utl;

    this._tidyParameters(params);

    return this._withContext(params)._enqueue('timing', params, fn);
  },


  send: function (fn) {
    var self = this;
    var count = 1;
    var fn = fn || function () {};

    var getBody = function(params) {
      return params.map(function(x) { return querystring.stringify(x); }).join('\n');
    }

    var onFinish = function (err) {
      fn.call(self, err || null, count - 1);
    }

    var iterator = function () {
      if (!self._queue.length) {
        return onFinish(null);
      }
      var params = [];

      if(config.batching) {
        params = self._queue.splice(0, Math.min(self._queue.length, config.batchSize));
      } else {
        params.push(self._queue.shift());
      }

      var useBatchPath = params.length > 1;

      var path = config.hostname + (useBatchPath ? config.batchPath :config.path);

      var options = Object.assign({}, self.options.requestOptions, {
        body: getBody(params),
        headers: self.options.headers || {}
      });

      GM_xmlhttpRequest({
        method: 'POST',
        url: path,
        headers: options.headers,
        data: options.body,
        onload: function () {
          nextIteration()
        },
        onerror: function(res) {
          nextIteration(res.status);
        }
      });
    }

    function nextIteration(err) {
      if (err) return onFinish(err);
      iterator();
    }

    iterator();

  },

  _enqueue: function (type, params, fn) {

    if (typeof params === 'function') {
      fn = params;
      params = {};
    }

    params = this._translateParams(params) || {};

    Object.assign(params, {
      v: config.protocolVersion,
      tid: this.tid,
      cid: this.cid,
      t: type
    });
    if(this.uid) {
      params.uid = this.uid;
    }

    this._queue.push(params);

    if (fn) {
      this.send(fn);
    }

    return this;
  },


  _handleError: function (message, fn) {
      fn && fn.call(this, new Error(message))
      return this;
  },

  _translateParams: function (params) {
        var translated = {};
        for (var key in params) {
            if (config.parametersMap.hasOwnProperty(key)) {
                translated[config.parametersMap[key]] = params[key];
            } else {
                translated[key] = params[key];
            }
        }
        return translated;
    },

  _tidyParameters: function (params) {
    for (var param in params) {
      if (params[param] === null || params[param] === undefined) {
        delete params[param];
      }
    }
    return params;
  },

  _withContext: function (context) {
    var visitor = new Visitor(this.tid, this.cid, this.options, context, this._persistentParams);
    visitor._queue = this._queue;
    return visitor;
  }


}

Visitor.prototype.pv = Visitor.prototype.pageview
Visitor.prototype.e = Visitor.prototype.event
Visitor.prototype.t = Visitor.prototype.transaction
Visitor.prototype.i = Visitor.prototype.item


================================================
FILE: app/core/analytics.js
================================================
import ua from '../../analytics';

import { Database } from './db';

class Analytics {
  constructor() {
    if (this.ua === undefined) {
      let id = Database.get('uuid', '');
      if (id === '') {
        id = this._uuidv4();
        Database.set('uuid', id);
      }

      this.ua = ua(null, null, {
        tid: UA_TOKEN,
        cid: id,
        uid: id,
      });
    }
  }

  /* eslint-disable */
  _uuidv4() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
      var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
      return v.toString(16);
    });
  }
  /* eslint-enable */

  trackPage(pageId) {
    return new Promise((resolve, reject) => {
      this.ua.pageview(pageId, (err) => {
        if (err) {
          reject(err);
        } else {
          resolve();
        }
      });
    });
  }

  trackEvent(category, action, label = null, value = null) {
    return new Promise((resolve, reject) => {
      this.ua.event(category, action, label, value, (err) => {
        if (err) {
          reject(err);
        } else {
          resolve();
        }
      });
    });
  }
}

export default new Analytics();


================================================
FILE: app/core/base-script.js
================================================
/* global window */
/* eslint class-methods-use-this: "off" */
import { Settings } from './settings';
import { Database } from './db';

export class BaseScript {
  constructor(id) {
    this._id = id;

    Settings.getInstance().on('entry-enabled', (entry) => {
      if (entry.id === id) {
        this.screenRequestObserver = window.onPageNavigation.observe(
          this,
          function (obs, event) {
            setTimeout(() => {
              this.onScreenRequest(event);
            }, 1000);
          },
        );

        this.activate({
          screenId: window.currentPage,
        });
      }
    });

    Settings.getInstance().on('entry-disabled', (entry) => {
      if (entry.id === id) {
        this.screenRequestObserver.unobserve(this);

        this.deactivate({
          screenId: window.currentPage,
        });
      }
    });
  }

  activate() {
    // override in subclasses
  }

  deactivate() {
    // override in subclasses
  }

  onScreenRequest() {
    // override in subclasses
  }

  getSettings() {
    return Database.getJson(`settings:${this._id}`, {});
  }
}


================================================
FILE: app/core/browser.js
================================================
/* globals
window document Blob
*/

export default {
  downloadFile(filename, data) {
    const blob = new Blob([data], { type: 'text/csv' });
    if (window.navigator.msSaveOrOpenBlob) {
      window.navigator.msSaveBlob(blob, filename);
    } else {
      const elem = window.document.createElement('a');
      elem.href = window.URL.createObjectURL(blob);
      elem.download = filename;
      document.body.appendChild(elem);
      elem.click();
      document.body.removeChild(elem);
    }
  },
};


================================================
FILE: app/core/db.js
================================================
/* eslint valid-typeof: "error" */

export class Database {
  constructor() {
    this.set('database-version', '1');
  }

  static set(key, value) {
    GM_setValue(key, value);
  }

  static setJson(key, value) {
    this.set(key, JSON.stringify(value));
  }

  static get(key, defaultValue) {
    let value = defaultValue;
    if (typeof value === 'object') {
      value = JSON.stringify(value);
    }
    return GM_getValue(key, value);
  }

  static getJson(key, defaultValue) {
    return JSON.parse(this.get(key, defaultValue));
  }
}


================================================
FILE: app/core/index.js
================================================
import { Settings } from './settings';
import { SettingsEntry } from './settings-entry';
import { BaseScript } from './base-script';
import { Database } from './db';
import { Queue } from './queue';
import browser from './browser';
import analytics from './analytics';

export {
  BaseScript,
  Database,
  Queue,
  Settings,
  SettingsEntry,
  browser,
  analytics,
};


================================================
FILE: app/core/queue.js
================================================
import { utils } from '../../fut';

export class Queue {
  constructor() {
    this._queue = [];
  }

  static getInstance() {
    if (this._instance == null) {
      this._instance = new Queue();
    }

    return this._instance;
  }

  add(identifier, cb) {
    this._queue.push({
      identifier,
      cb,
    });
  }

  async start() {
    this._running = true;
    /* eslint-disable no-await-in-loop */
    while (this._running) {
      if (this._queue.length > 0) {
        const scriptToRun = this._queue.shift();
        if (scriptToRun) {
          await scriptToRun.cb();
        }
      } else {
        await utils.sleep(1000);
      }
    }
    /* eslint-enable no-await-in-loop */
  }

  stop() {
    this._running = false;
  }
}


================================================
FILE: app/core/settings-entry.js
================================================
import { Database } from './db';

export class SettingsEntry {
  constructor(id, name) {
    const settings = Database.getJson(`settings:${id}`, {});

    this.id = id;
    this.name = name;
    this.isActive = settings.isActive ? settings.isActive : false;
    this.settings = [];
  }

  toggle() {
    this.isActive = !this.isActive;

    const settings = Database.getJson(`settings:${this.id}`, {});
    settings.isActive = this.isActive;
    Database.setJson(`settings:${this.id}`, settings);
  }

  addSetting(label, key, defaultValue, type, cb) {
    const settings = Database.getJson(`settings:${this.id}`, {});

    settings[key] = key in settings ? settings[key] : defaultValue;
    Database.setJson(`settings:${this.id}`, settings);

    this.settings.push({
      label,
      key,
      type,
      value: key in settings ? settings[key] : defaultValue,
      callback: cb,
      subsettings: [],
    });
  }

  addSettingUnder(underKey, label, key, defaultValue, type, cb) {
    const settings = Database.getJson(`settings:${this.id}`, {});
    settings[key] = key in settings ? settings[key] : defaultValue;
    Database.setJson(`settings:${this.id}`, settings);

    const setting = this.settings.find(s => s.key === underKey);
    setting.subsettings.push({
      label,
      key,
      type,
      value: key in settings ? settings[key] : defaultValue,
      callback: cb,
    });
  }

  changeValue(key, value) {
    const settings = Database.getJson(`settings:${this.id}`, {});

    settings[key] = value;

    Database.setJson(`settings:${this.id}`, settings);
  }
}


================================================
FILE: app/core/settings.js
================================================
import EventEmitter from 'event-emitter-es6';

import analytics from './analytics';

export class Settings extends EventEmitter {
  constructor() {
    super();
    this._entries = [];
  }

  static getInstance() {
    if (this._instance == null) {
      this._instance = new Settings();
    }

    return this._instance;
  }

  /**
   *
   * @param {SettingsEntry} entry The entry for the settings
   */
  registerEntry(entry) {
    this._entries.push(entry);

    if (entry.isActive) {
      this._emitEvent(entry);
    }
  }

  getEntries() {
    return this._entries;
  }

  toggleEntry(id) {
    const entries = this._entries.filter(e => e.id === id);
    if (!entries || entries.length === 0) {
      return;
    }

    entries[0].toggle();

    analytics.trackEvent('Settings', `Toggle setting ${id}`, entries[0].isActive);
    this._emitEvent(entries[0]);
  }

  _emitEvent(entry) {
    if (entry.isActive) {
      this.emit('entry-enabled', entry);
    } else {
      this.emit('entry-disabled', entry);
    }
  }
}


================================================
FILE: app/futbin/futbin-player-links.js
================================================
/* globals
window $ document
*/
import { analytics, BaseScript, Database } from '../core';
import { FutbinSettings } from './settings-entry';

export class FutbinPlayerLinks extends BaseScript {
  constructor() {
    super(FutbinSettings.id);

    const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
    this._observer = new MutationObserver(this._mutationHandler.bind(this));

    this._playerPrices = [];
  }

  activate(state) {
    super.activate(state);

    const obsConfig = {
      childList: true,
      characterData: true,
      attributes: false,
      subtree: true,
    };

    setTimeout(() => {
      this._observer.observe($(document)[0], obsConfig);
    }, 0);
  }

  deactivate(state) {
    super.deactivate(state);

    $('#futbinPlayerLink').remove();

    this._observer.disconnect();
  }

  _mutationHandler(mutationRecords) {
    mutationRecords.forEach(function (mutation) {
      if ($(mutation.target).hasClass('DetailView') && $(mutation.target)
        .find('.DetailPanel') && mutation.addedNodes.length > 0) {
        if (this.getSettings()['show-link-to-player'].toString() !== 'true') {
          return;
        }

        let selectedItem = this._getSelectedItem();
        if (selectedItem == null || selectedItem.resourceId === 0) {
          return;
        }

        const futbinPlayerLink = $(mutation.target).find('#futbinPlayerLink');
        futbinPlayerLink.remove();

        $(mutation.target).find('.DetailPanel > .ut-button-group').prepend(`<button id="futbinPlayerLink" data-resource-id="${selectedItem.resourceId}" class="list"><span class="btn-text">View on Futbin</span><span class="btn-subtext"></span></button>`);

        $('#futbinPlayerLink').bind('click', async () => {
          let btn = $('#futbinPlayerLink');
          btn.find('.btn-text').html('Searching on Futbin ...');
          const futbinLink = await FutbinPlayerLinks._getFutbinPlayerUrl(selectedItem);

          selectedItem = this._getSelectedItem();
          btn = $('#futbinPlayerLink');
          if (btn.data('resource-id') === selectedItem.resourceId) {
            if (futbinLink) {
              btn.find('.btn-text').html('View on Futbin');
              analytics.trackEvent('Futbin', 'Show player on Futbin', btn.data('resource-id'));
              window.open(futbinLink);
            } else {
              btn.find('.btn-text').html('No exact Futbin player found');
            }
          }
        });
      }
    }, this);
  }

  static _getFutbinPlayerUrl(item) {
    return new Promise((resolve) => {
      if (!item._staticData) {
        return resolve(null);
      }

      let futbinPlayerIds = Database.getJson('futbin-player-ids', []);
      const futbinPlayer = futbinPlayerIds.find(i => i.id === item.resourceId);
      if (futbinPlayer != null) {
        return resolve(`https://www.futbin.com/21/player/${futbinPlayer.futbinId}`);
      }

      const name = `${item._staticData.firstName} ${item._staticData.lastName}`.replace(' ', '+');
      const url = `https://www.futbin.com/search?year=21&term=${name}`;
      return GM_xmlhttpRequest({
        method: 'GET',
        url,
        onload: (res) => {
          if (res.status !== 200) {
            return resolve(null);
          }
          const players = JSON.parse(res.response);
          let exactPlayers = players.filter(p =>
            parseInt(p.rating, 10) === parseInt(item.rating, 10));
          if (exactPlayers.length > 1) {
            exactPlayers = exactPlayers.filter(p =>
              p.rare_type === item.rareflag.toString() &&
              p.club_image.endsWith(`/${item.teamId}.png`));
          }
          if (exactPlayers.length === 1) {
            futbinPlayerIds = Database.getJson('futbin-player-ids', []);
            if (futbinPlayerIds.find(i => i.id === item.resourceId) == null) {
              futbinPlayerIds.push({
                id: item.resourceId,
                futbinId: exactPlayers[0].id,
              });
            }
            Database.setJson('futbin-player-ids', futbinPlayerIds);
            return resolve(`https://www.futbin.com/21/player/${exactPlayers[0].id}`);
          } else if (exactPlayers.length > 1) {
            // Take first one, several players are returned more than once
            return resolve(`https://www.futbin.com/21/player/${exactPlayers[0].id}`);
          }

          return resolve(null); // TODO: what should we do if we find more than one?
        },
      });
    });
  }

  /* eslint-disable class-methods-use-this */
  _getSelectedItem() {
    const listController = getAppMain().getRootViewController()
      .getPresentedViewController().getCurrentViewController()
      .getCurrentController()._listController;
    if (listController) {
      return listController.getIterator().current();
    }

    const currentController = getAppMain().getRootViewController()
      .getPresentedViewController().getCurrentViewController()
      .getCurrentController()._rightController._currentController;
    if (currentController && currentController._viewmodel) {
      const current = currentController._viewmodel.current();

      return current._item ? current._item : current;
    }

    return null;
  }
  /* eslint-enable class-methods-use-this */
}

new FutbinPlayerLinks(); // eslint-disable-line no-new


================================================
FILE: app/futbin/futbin-prices.js
================================================
/* globals
$
window
*/

import { utils } from '../../fut';
import { BaseScript, Database } from '../core';
import { FutbinSettings } from './settings-entry';

export class FutbinPrices extends BaseScript {
  constructor() {
    super(FutbinSettings.id);
    this._squadObserver = null;
  }

  activate(state) {
    super.activate(state);

    this._show(state.screenId);
  }

  onScreenRequest(screenId) {
    super.onScreenRequest(screenId);

    const controllerName = getAppMain().getRootViewController()
      .getPresentedViewController().getCurrentViewController()
      .getCurrentController().className;

    if (screenId === 'SBCSquadSplitViewController' ||
      screenId === 'SquadSplitViewController' ||
      screenId === 'UTSquadSplitViewController' ||
      screenId === 'UTSBCSquadSplitViewController') {
      if (this.getSettings()['show-sbc-squad'].toString() !== 'true') {
        return;
      }

      this._squadObserver = getAppMain().getRootViewController()
        .getPresentedViewController().getCurrentViewController()
        .getCurrentController()._leftController._squad.onDataUpdated
        .observe(this, () => {
          $('.squadSlotPedestal.futbin').remove(); // forces update
          this._show('SBCSquadSplitViewController', true);
        });
      if ($('.ut-squad-summary-info--right.ut-squad-summary-info').find('.futbin').length === 0) {
        $('.ut-squad-summary-info--right.ut-squad-summary-info').append(`
        <div class="futbin total">
          <span class="ut-squad-summary-label">Total BIN value</span>
          <div style="text-align: right">
            <span class="ut-squad-summary-value coins value">---</span>
          </div>
        </div>
        <div class="futbin total">
          <span class="ut-squad-summary-label">
            <button class="refresh-squad-button">&nbsp;</button>
          </span>
        </div>
      `);

        $('.refresh-squad-button').click(() => {
          Database.set('lastFutbinFetchFail', 0);
          this.onScreenRequest(screenId);
        });
      }
    } else if (this._squadObserver !== null &&
      controllerName !== 'SBCSquadSplitViewController' &&
      controllerName !== 'SquadSplitViewController' &&
      controllerName !== 'UTSquadSplitViewController' &&
      controllerName !== 'UTSBCSquadSplitViewController') {
      this._squadObserver.unobserve(this);
    }

    this._show(screenId);
  }

  deactivate(state) {
    super.deactivate(state);

    $('.futbin').remove();

    if (this._squadObserver !== null) {
      this._squadObserver.unobserve(this);
    }

    if (this._intervalRunning) {
      clearInterval(this._intervalRunning);
    }
  }

  _show(screen, force = false) {
    const showFutbinPricePages = [
      // Players
      'UTTransferListSplitViewController', // transfer list
      'UTWatchListSplitViewController', // transfer targets
      'UTUnassignedItemsSplitViewController', // pack buy
      'ClubSearchResultsSplitViewController', // club
      'UTMarketSearchResultsSplitViewController', // market search
      'UTPlayerPicksViewController',
      // Squad
      'SBCSquadSplitViewController',
      'SquadSplitViewController',
      'UTSquadSplitViewController',
      'UTSBCSquadSplitViewController',
      'UTTOTWSquadSplitViewController',
    ];

    if (showFutbinPricePages.indexOf(screen) !== -1) {
      if (this._intervalRunning) {
        clearInterval(this._intervalRunning);
      }
      if (screen === 'SBCSquadSplitViewController' ||
      screen === 'SquadSplitViewController' ||
      screen === 'UTSquadSplitViewController' ||
      screen === 'UTSquadsHubViewController' ||
      screen === 'UTSBCSquadSplitViewController' ||
      screen === 'UTTOTWSquadSplitViewController') {
        this.loadFutbinPrices(showFutbinPricePages, screen, force);
      } else {
        this._intervalRunning = setInterval(() => {
          this.loadFutbinPrices(showFutbinPricePages, screen, force);
        }, 1000);
      }
    } else {
      // no need to search prices on other pages
      // reset page
      if (this._intervalRunning) {
        clearInterval(this._intervalRunning);
      }
      this._intervalRunning = null;
    }
  }

  loadFutbinPrices(showFutbinPricePages, screen, force) {
    const lastFutbinFetchFail = Database.get('lastFutbinFetchFail', 0);
    if (lastFutbinFetchFail + (5 * 60000) > Date.now()) {
      console.log(`Futbin fetching has been paused for 5 minutes because of failed requests earlier (retrying after ${new Date(lastFutbinFetchFail + (5 * 60000)).toLocaleTimeString()}). Check on Github for known issues.`); // eslint-disable-line no-console
      if (this._intervalRunning) {
        clearInterval(this._intervalRunning);
      }
      return;
    }
    if (showFutbinPricePages.indexOf(window.currentPage) === -1 && !force) {
      if (this._intervalRunning) {
        clearInterval(this._intervalRunning);
      }
      return;
    }
    const controller = getAppMain().getRootViewController()
      .getPresentedViewController().getCurrentViewController()
      .getCurrentController();

    let uiItems = null;
    if (screen === 'SBCSquadSplitViewController' ||
      screen === 'SquadSplitViewController' ||
      screen === 'UTSquadSplitViewController' ||
      screen === 'UTSquadsHubViewController' ||
      screen === 'UTSBCSquadSplitViewController' ||
      screen === 'UTTOTWSquadSplitViewController') {
      uiItems = $(controller._view.__root).find('.squadSlot');

      if (this.getSettings()['show-sbc-squad'].toString() !== 'true') {
        return;
      }
    } else {
      uiItems = $(getAppMain().getRootViewController()
        .getPresentedViewController().getCurrentViewController()
        ._view.__root).find('.listFUTItem');
    }

    if ($(uiItems[0]).find('.futbin').length > 0) {
      return;
    }

    let listController = null;
    if (screen === 'SBCSquadSplitViewController' ||
      screen === 'SquadSplitViewController' ||
      screen === 'UTSquadSplitViewController' ||
      screen === 'UTSBCSquadSplitViewController' ||
      screen === 'UTTOTWSquadSplitViewController') {
      // not needed
    } else if (screen === 'UTPlayerPicksViewController') {
      if (!controller.getPresentedViewController()) {
        return;
      }
      if ($(controller.getPresentedViewController()._view.__root).find('.futbin').length > 0) {
        // Futbin prices already shown
        return;
      }
      listController = controller.getPresentedViewController();
    } else if (screen === 'UTUnassignedItemsSplitViewController' || screen === 'UTWatchListSplitViewController') {
      if (!controller ||
        !controller._leftController ||
        !controller._leftController._view) {
        return;
      }
      listController = controller._leftController;
    } else {
      if (!controller ||
        !controller._listController ||
        !controller._listController._view) {
        return; // only run if data is available
      }
      listController = controller._listController;
    }

    let listrows = null;
    if (screen === 'SBCSquadSplitViewController' ||
      screen === 'SquadSplitViewController' ||
      screen === 'UTSquadSplitViewController' ||
      screen === 'UTSBCSquadSplitViewController' ||
      screen === 'UTTOTWSquadSplitViewController') {
      listrows = controller._squad._players.slice(0, 11).map((p, index) => (
        {
          data: p._item,
          target: controller._view._lView._slotViews[index].__root,
        }));
    } else if (listController._picks && screen === 'UTPlayerPicksViewController') {
      listrows = listController._picks.map((pick, index) => (
        {
          data: pick,
          target: listController._view._playerPickViews[index].__root,
        }));
    } else if (listController._view._list &&
      listController._view._list.listRows &&
      listController._view._list.listRows.length > 0) {
      listrows = listController._view._list.listRows; // for transfer market and club search
    } else if (listController._view._sections &&
      listController._view._sections.length > 0) { // for transfer list & trade pile
      listController._view._sections.forEach((row) => {
        if (row.listRows.length > 0) {
          if (listrows == null) {
            listrows = row.listRows;
          } else {
            listrows = listrows.concat(row.listRows);
          }
        }
      });
    }

    if (listrows === null) {
      return;
    }

    const showBargains = (this.getSettings()['show-bargains'].toString() === 'true');

    const resourceIdMapping = [];

    listrows
      .filter(row => row.data.type === 'player' && row.data.resourceId !== 0)
      .forEach((row, index) => {
        $(row.__auction).show();
        resourceIdMapping.push({
          target: uiItems[index] || row.target,
          playerId: row.data.resourceId,
          item: row.data,
        });
      });

    let fetchedPlayers = 0;
    const fetchAtOnce = 30;
    const futbinlist = [];
    while (resourceIdMapping.length > 0 && fetchedPlayers < resourceIdMapping.length && Database.get('lastFutbinFetchFail', 0) + (5 * 60000) < Date.now()) {
      const futbinUrl = `https://www.futbin.com/21/playerPrices?player=&rids=${
        resourceIdMapping.slice(fetchedPlayers, fetchedPlayers + fetchAtOnce)
          .map(i => i.playerId)
          .filter((current, next) => current !== next && current !== 0)
          .join(',')
      }`;
      fetchedPlayers += fetchAtOnce;
      /* eslint-disable no-loop-func */
      GM_xmlhttpRequest({
        method: 'GET',
        url: futbinUrl,
        onload: (res) => {
          if (res.status !== 200) {
            Database.set('lastFutbinFetchFail', Date.now());
            GM_notification(`Could not load Futbin prices (code ${res.status}), pausing fetches for 5 minutes. Disable Futbin integration if the problem persists.`, 'Futbin fetch failed');
            return;
          }

          const futbinData = JSON.parse(res.response);
          resourceIdMapping.forEach((item) => {
            FutbinPrices._showFutbinPrice(screen, item, futbinData, showBargains);
            futbinlist.push(futbinData[item.playerId]);
          });
          const platform = utils.getPlatform();
          if (screen === 'SBCSquadSplitViewController' ||
            screen === 'SquadSplitViewController' ||
            screen === 'UTSquadSplitViewController' ||
            screen === 'UTSBCSquadSplitViewController') {
            const futbinTotal = futbinlist.reduce(
              (sum, item) =>
                sum + parseInt(
                  item.prices[platform].LCPrice.toString().replace(/[,.]/g, ''),
                  10,
                ) || 0
              , 0,
            );
            $('.ut-squad-summary-value.coins.value').html(`${futbinTotal.toLocaleString()}`);
          }
        },
      });
    }
  }
  static async _showFutbinPrice(screen, item, futbinData, showBargain) {
    if (!futbinData) {
      return;
    }
    const target = $(item.target);
    const { playerId } = item;

    if (target.find('.player').length === 0) {
      // not a player
      return;
    }

    const platform = utils.getPlatform();

    if (!futbinData[playerId]) {
      return; // futbin data might not be available for this player
    }

    let targetForButton = null;

    if (target.find('.futbin').length > 0) {
      return; // futbin price already added to the row
    }

    const futbinText = 'Futbin BIN';

    switch (screen) {
      case 'SBCSquadSplitViewController':
      case 'SquadSplitViewController':
      case 'UTSquadSplitViewController':
      case 'UTSBCSquadSplitViewController':
      case 'UTTOTWSquadSplitViewController':
        target.prepend(`
        <div class="ut-squad-slot-pedestal-view no-state futbin">
          <span class="coins value">${futbinData[playerId].prices[platform].LCPrice || '---'}</span>
        </div>`);
        break;
      case 'UTPlayerPicksViewController':
        target.prepend(`
        <div class="auctionValue futbin">
          <span class="label">${futbinText}</span>
          <span class="coins value">${futbinData[playerId].prices[platform].LCPrice || '---'}</span>
        </div>`);
        break;
      case 'UTTransferListSplitViewController':
      case 'UTWatchListSplitViewController':
      case 'UTUnassignedItemsSplitViewController':
      case 'ClubSearchResultsSplitViewController':
      case 'UTMarketSearchResultsSplitViewController':
        $('.secondary.player-stats-data-component').css('float', 'left');
        target.find('.auction').prepend(`
        <div class="auctionValue futbin">
          <span class="label">${futbinText}</span>
          <span class="coins value">${futbinData[playerId].prices[platform].LCPrice || '---'}</span>
        </div>`);
        break;
      case 'SearchResults':
        targetForButton = target.find('.auctionValue').parent();
        targetForButton.prepend(`
        <div class="auctionValue futbin">
          <span class="label">${futbinText}</span>
          <span class="coins value">${futbinData[playerId].prices[platform].LCPrice || '---'}</span>
        </div>`);
        break;
      default:
      // no need to do anything
    }

    if (showBargain) {
      if (item.item._auction &&
        item.item._auction.buyNowPrice < futbinData[playerId].prices[platform].LCPrice.toString().replace(/[,.]/g, '')) {
        target.addClass('futbin-bargain');
      }
    }
  }
}


================================================
FILE: app/futbin/index.js
================================================
import './style/futbin-prices.scss';

import { FutbinPrices } from './futbin-prices';
import { FutbinPlayerLinks } from './futbin-player-links';
import { FutbinSettings } from './settings-entry';

export {
  FutbinSettings,
};

new FutbinPrices(); // eslint-disable-line no-new
new FutbinPlayerLinks(); // eslint-disable-line no-new


================================================
FILE: app/futbin/settings-entry.js
================================================
import { SettingsEntry } from '../core';

export class FutbinSettings extends SettingsEntry {
  static id = 'futbin';
  constructor() {
    super('futbin', 'FutBIN integration');

    this.addSetting('Show link to player page', 'show-link-to-player', false, 'checkbox');
    this.addSetting('Show prices on SBC and Squad', 'show-sbc-squad', false, 'checkbox');
    this.addSetting('Mark bargains', 'show-bargains', false, 'checkbox');
  }
}


================================================
FILE: app/futbin/style/futbin-prices.scss
================================================
#TradePile .player-stats-data-component, #Unassigned .player-stats-data-component {
  width: 12em;
}
#TradePile .listFUTItem .entityContainer, #Unassigned .listFUTItem .entityContainer {
  width: 45%;
}
#Unassigned .listFUTItem .auction .auctionValue, #Unassigned .listFUTItem .auction .auction-state {
  display: none;
}
#Unassigned .listFUTItem .auction .auctionValue.futbin {
  display: block;
  float: right;
}
.MyClubResults .listFUTItem .auction {
  display: block;
  position: absolute;
  right: 0;
}
.MyClubResults .listFUTItem .auction .auctionValue, .MyClubResults .listFUTItem .auction .auction-state {
  width: 24%;
  float: right;
  padding-right: 1%;
  display: none;
}
.MyClubResults .listFUTItem .auction .auctionValue.futbin {
  display: block;
}

.listFUTItem .auction>.auction-state, .listFUTItem .auction>.auctionStartPrice, .listFUTItem .auction>.auctionValue {
  flex: 1 1 20%;
  overflow: hidden;
}

.listFUTItem .auction {
  top: 30%;
  max-width: none;
  width: 50%;

  .futbin .coins.value .time {
    display: inline;
    font-size: 1em;
  }
}

@media (max-width: 1130px) {
  .listFUTItem .auction {
    width: auto;
  }
  
  html[dir=ltr] .listFUTItem .auction {
    left: auto;
  }
}
.ut-navigation-container-view.ui-layout-right .listFUTItem .auction {
  top: 30%;
}

.futbinupdate {
  font-size: 14px;
  clear: both;
  display: block;
}
.coins.value.futbin {
  -webkit-filter: hue-rotate(165deg);
  filter: hue-rotate(165deg);
}
.listFUTItem.has-auction-data.futbin-bargain .rowContent {
  background-color: #7ffe9445;
}
.listFUTItem.has-auction-data.selected.futbin-bargain .rowContent, .listFUTItem.has-auction-data.selected.futbin-bargain .rowContent.active {
  background-color: #7ffe94;
  color: #434853;
}
.ut-club-search-results-view {
  .listFUTItem .auction {
    width: 10%;
  }
  
  .auction-state, .auctionValue {
    display: none;

    &.futbin {
      display: block;
    }
  }
}
.player-picks-modal .time {
  display: block;
}
.ut-squad-slot-pedestal-view.futbin {
  min-width: 58px;
  flex: none;
  width: auto;
  bottom: -2.6em;
  white-space: nowrap;

  .coins.value {
    text-align: center;
    margin: 0 8px;
  }
}

.ut-squad-overview .ut-squad-summary {
  width: 70%;
}

.refresh-squad-button {
  margin: 17px 5px;
  color:#e2dde2;
  &:before {
    font-family: UltimateTeam-Icons,sans-serif;
    content: '\E051';
  }
}



================================================
FILE: app/index.js
================================================
/* globals onVisibilityChanged services UTGameFlowNavigationController
UTViewController EAObservable window document $ */
import 'babel-polyfill';
import './index.scss';
import initSettingsScreen from './settings';

import { analytics, Settings, Queue } from './core';

import { Logger } from '../fut';
/*
  RemoveSoldAuctionsSettings,
  RelistAuctionsSettings,
*/
import {
  RefreshListSettings,
  CardInfoSettings,
  ListSizeSettings,
  MinBinSettings,
  TransferTotalsSettings,
} from './transferlist';

import {
  FutbinSettings,
} from './futbin';

import {
  InstantBinConfirmSettings,
} from './instant-bin-confirm';
/*
import {
  ClubInfoSettings,
} from './club';
*/

window.onPageNavigation = new EAObservable();

window.currentPage = '';

UTGameFlowNavigationController.prototype.didPush = (t) => {
  if (t) {
    analytics.trackPage(t.className);
    window.onPageNavigation.notify(t.className);
    window.currentPage = t.className;
  }
};

UTViewController.prototype.didPresent = (t) => {
  if (t) {
    analytics.trackPage(t.className);
    window.onPageNavigation.notify(t.className);
    window.currentPage = t.className;
  }
};

setTimeout(() => {
  services.Authentication.oAuthentication.observe(
    this,
    () => {
      // reset the logs at startup
      new Logger().reset();

      // force full web app layout in any case
      $('body').removeClass('phone').addClass('landscape');

      Queue.getInstance().start();

      // get rid of pinEvents when switching tabs
      document.removeEventListener('visibilitychange', onVisibilityChanged);

      const settings = Settings.getInstance();
      settings.registerEntry(new RefreshListSettings());
      settings.registerEntry(new MinBinSettings());
      settings.registerEntry(new CardInfoSettings());
      settings.registerEntry(new ListSizeSettings());
      settings.registerEntry(new TransferTotalsSettings());

      settings.registerEntry(new FutbinSettings());
      settings.registerEntry(new InstantBinConfirmSettings());

      initSettingsScreen(settings);
    },
  );
}, 1000);


================================================
FILE: app/index.scss
================================================
.ut-content-container {
    padding: 0;

    .ut-content {
        border: 0;

        &.ut-content--split-view-extend {
            max-height: 100%;
        }
    }
}

.listFUTItem .entityContainer .name.untradeable {
    display: block;

    &::before {
        position: relative;
        padding-right: 10px;
    }
}

.ut-transfer-list-view .listFUTItem .entityContainer,
.ut-club-search-results-view.ui-layout-left .listFUTItem .entityContainer,
.ut-unassigned-view.ui-layout-left .listFUTItem .entityContainer {
    width: 45%;
}

@media (min-width: 1281px) {
    .ut-content-container .ut-content {
        max-width: 100%;
        max-height: 100%;
    }

    .ut-split-view .ut-content {
        max-width: 100%;
        max-height: 100%;
    }
}


================================================
FILE: app/instant-bin-confirm/index.js
================================================
import { InstantBinConfirmSettings } from './settings-entry';
import { InstantBinConfirm } from './instant-bin-confirm';

export {
  InstantBinConfirmSettings,
};

new InstantBinConfirm(); // eslint-disable-line no-new


================================================
FILE: app/instant-bin-confirm/instant-bin-confirm.js
================================================
/* global
gPopupClickShield
enums
EADialogViewController
services
utils
*/

import { BaseScript } from '../core';
import { InstantBinConfirmSettings } from './settings-entry';

export class InstantBinConfirm extends BaseScript {
  unmodifiedConfirmation = utils.PopupManager.ShowConfirmation;

  constructor() {
    super(InstantBinConfirmSettings.id);
  }

  activate(state) {
    super.activate(state);
  }

  onScreenRequest(screenId) {
    super.onScreenRequest(screenId);
    const settings = this.getSettings();

    utils.PopupManager.ShowConfirmation = (dialog, amount, proceed, s) => {
      let cancel = s;
      if (!utils.JS.isFunction(s)) {
        cancel = function () { };
      }

      if (settings.isActive && dialog.title ===
        utils.PopupManager.Confirmations.CONFIRM_BUY_NOW.title) {
        proceed();
        return;
      }

      const n = new EADialogViewController({
        dialogOptions: [dialog.buttonLabels[0],
          dialog.buttonLabels[1]],
        message: services.Localization.localize(dialog.message, amount),
        title: services.Localization.localize(dialog.title),
      });

      n.init();
      gPopupClickShield.setActivePopup(n);
      n.onExit.observe(this, (e, t) => {
        if (t !== enums.UIDialogOptions.CANCEL && t !== enums.UIDialogOptions.NO) {
          if (proceed) {
            proceed();
          } else if (cancel) {
            cancel();
          }
        } else {
          cancel();
        }
      });
    };
  }

  deactivate(state) {
    super.deactivate(state);
    utils.PopupManager.ShowConfirmation = this.unmodifiedConfirmation;
  }
}


================================================
FILE: app/instant-bin-confirm/settings-entry.js
================================================
import { SettingsEntry } from '../core';

export class InstantBinConfirmSettings extends SettingsEntry {
  static id = 'instant-bin-confirm';
  constructor() {
    super('instant-bin-confirm', 'Instantly confirm Buy It Now dialog');
  }
}


================================================
FILE: app/settings/html/index/settings.html
================================================
<button class="futsettings-toggle standard mini call-to-action">
  <span class="btn-text"></span>
</button>

<div class="futsettings">
  <h1 class="settings-title">FUT Tampermonkey settings

    <div style="float:right">
      <a target="_blank" href="https://www.paypal.me/timklingeleers"
        rel="nofollow">
        <img src="data:image/gif;base64,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" />
      </a>
    </div>
  </h1>

  <!-- settings are injected here automatically -->
  <div id="settingspanel"></div>

  <footer>
    <h3>Need help?</h3>
    <p>Talk to us in the Gitter channel or report errors in the Github repository.
      <ul>
        <li>Gitter: <a target="_blank" href="https://gitter.im/futwebapp-tampermonkey/Lobby">Talk in the Lobby</a></li>
        <li>Github: <a target="_blank" href="https://github.com/Mardaneus86/futwebapp-tampermonkey/issues">Add an issue</a></li>
      </ul>
    </p>
    <hr />
    <h3>Enjoying this plugin?</h3>
    <p>Consider a donation so this plugin can keep being improved.</p>
    <p>
      <a target="_blank" href="https://www.paypal.me/timklingeleers"
        rel="nofollow">
        <img src="data:image/gif;base64,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" />
      </a>
    </p>
  </footer>
</div>


================================================
FILE: app/settings/index.js
================================================
/* globals $ */
/* eslint-disable no-restricted-syntax */

import './index.scss';
import { analytics } from '../core';
import settingsPage from './html/index/settings.html';

const handleFieldChange = (entry, setting, e) => {
  if (setting.subsettings && setting.subsettings.length > 0) {
    entry.changeValue(setting.key, e.target.checked);
  } else if (setting.type === 'checkbox') {
    entry.changeValue(setting.key, e.target.checked);
  } else {
    entry.changeValue(setting.key, e.target.value);
  }

  if (setting.callback) {
    setting.callback(e.target.value);
  }
  if (setting.subsettings && setting.subsettings.length > 0) {
    $(`[data-parent-feature-setting-id='${entry.id}:${setting.key}']`).toggle();
  }
};

const renderSettingsEntry = (setting, entry) => {
  const inputId = `${entry.id}:${setting.key}`;
  return `<div class="setting">
    ${setting.type !== 'checkbox' ? `<label for="${inputId}">${setting.label}</label>` : ''}
    <input
      type="${setting.type}"
      id="${inputId}"
      data-feature-setting-id="${entry.id}:${setting.key}"
      value="${setting.value}"
      ${setting.type === 'checkbox' && setting.value.toString() === 'true' ? 'checked' : ''}
    />
    ${setting.type === 'checkbox' ? `<label for="${inputId}">${setting.label}</label>` : ''}
  </div>`;
};

export default (settings) => {
  const html = settingsPage;

  $('body').prepend(html);

  const settingsPanel = $('.futsettings #settingspanel');

  for (const entry of settings.getEntries()) {
    const checked = entry.isActive ? 'checked="checked"' : '';
    settingsPanel.append(`<h3 class="main-setting">
      <input type="checkbox" id="${entry.id}" data-feature-id="${entry.id}" ${checked} />
      <label for="${entry.id}">${entry.name}</label>
    </h3>`);
    let settingsFields = '';
    if (entry.settings && entry.settings.length > 0) {
      for (const setting of entry.settings) {
        if (setting.subsettings.length > 0) {
          settingsFields += renderSettingsEntry(setting, entry);

          const settingActive = setting.value ? 'block' : 'none';
          settingsFields += `<div data-parent-feature-setting-id="${entry.id}:${setting.key}" style="display: ${settingActive}">`;
          for (const subsetting of setting.subsettings) {
            settingsFields += renderSettingsEntry(subsetting, entry);
          }
          settingsFields += '</div>';
        } else {
          settingsFields += renderSettingsEntry(setting, entry);
        }
      }
      const featureActive = entry.isActive ? 'block' : 'none';
      settingsPanel.append(`<div class="feature-settings"><div data-feature-settings="${entry.id}" style="display: ${featureActive};">${settingsFields}</div></div>`);
      for (const setting of entry.settings) {
        $(`[data-feature-setting-id='${entry.id}:${setting.key}']`).on('change', (e) => {
          handleFieldChange(entry, setting, e);
        });
        for (const subsetting of setting.subsettings) {
          $(`[data-feature-setting-id='${entry.id}:${subsetting.key}']`).on('change', (e) => {
            handleFieldChange(entry, subsetting, e);
          });
        }
      }
    } else {
      settingsPanel.append('<div class="feature-settings-empty"></div>');
    }

    $(`[data-feature-id='${entry.id}']`).on('click', () => {
      settings.toggleEntry(entry.id);
      $(`[data-feature-settings='${entry.id}']`).toggle();
    });
  }

  $('.futsettings-toggle').click(() => {
    analytics.trackEvent('Settings', 'Toggle settings', $('.futsettings').is(':visible'));
    $('.futsettings').toggle();
  });
};


================================================
FILE: app/settings/index.scss
================================================
.futsettings-toggle {
  position: absolute !important;
  bottom: 20px;
  right: 20px;
  z-index: 999;

  ::before {
    font-family: UltimateTeam-Icons,sans-serif;
    content: "\E056";
    font-size: 2rem;
    color: gray;
  }
}

.futsettings {
  position: absolute;
  top: 112px;
  bottom: 0;
  left: 105px;
  right: 0;
  background-color: #fff;
  overflow-y: auto;
  display: none;
  z-index: 998;
  padding: 15px;

  label {
    color: black;
  }

  &, *, *:before, *:after {
    box-sizing: border-box;
  }

  footer {
    text-align: center;
    padding: 15px;
    color: black;

    hr {
      border: none;
      border-bottom: 1px solid #ddd;
    }

    p, li {
      font-size: smaller;
      margin: 10px;
    }
  }

  .settings-title {
    color: #183f94;
    font-size: 2.5em;
    font-weight: 400;
    font-family: UltimateTeamCondensed,sans-serif;
    line-height: 1em;
    margin-bottom: 0.5rem;
    text-transform: uppercase;
    width: 100%;
  }

  .main-setting {
    label {
      display: inline-block;
      padding-bottom: 15px;
      padding-top: 15px;
    }
  }

  .feature-settings-empty {
    display: none;
  }

  .feature-settings {
    background-color: #f5f5f5;
    margin-bottom: 25px;
    padding: 10px;
    position: relative;

    .setting {
      padding: 10px;

      input[type=number],
      input[type=text] {
        background-color: #fff;
        border: 1px #33314e solid;
        clear: both;
        color: #33314e;
        display: block;
        font-size: 14px;
        height: 3.5em;
        padding: 10px;
        text-align: left;
        width: 100%;
      }
    }
  }

  input[type=checkbox] {
    display: none;
    + label {
      cursor: pointer;
      position: relative;
      padding-left: 50px;

      &:before {
        background-color: #ccc;
        border: 1px solid #999;
        border-radius: 8px;
        content: '';
        height: 16px;
        left: 0;
        position: absolute;
        transition: background-color 300ms ease, border-color 300ms ease;;
        width: 40px;
      }
      &:after {
        background-color: #999;
        border: 1px solid #999;
        border-radius: 50%;
        content: '';
        height: 22px;
        left: 0;
        margin-top: -3px;
        position: absolute;
        transform: translateX(0);
        transition: background-color 300ms ease, border-color 300ms ease, transform 300ms ease;
        width: 22px;
      }
    }
    &:checked + label {
      &:before {
        background-color: #fc87ac;
        border-color: #f93b78;
      }
      &:after {
        background-color: #f93b78;
        border-color: #f93b78;
        transform: translateX(20px);
      }
    }
  }
}


================================================
FILE: app/transferlist/card-info.js
================================================
/* globals
window $ document */

import { BaseScript, SettingsEntry } from '../core';

import './style/card-info.scss';

export class CardInfoSettings extends SettingsEntry {
  static id = 'card-info';
  constructor() {
    super('card-info', 'Extra card information', null);

    this.addSetting('Show contracts', 'show-contracts', true, 'checkbox');
  }
}

class CardInfo extends BaseScript {
  constructor() {
    super(CardInfoSettings.id);

    const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
    this._observer = new MutationObserver(this._mutationHandler.bind(this));
  }

  activate(state) {
    super.activate(state);

    const obsConfig = {
      childList: true,
      characterData: true,
      attributes: false,
      subtree: true,
    };

    setTimeout(() => {
      this._observer.observe($(document)[0], obsConfig);
    }, 0);
  }

  deactivate(state) {
    super.deactivate(state);
    this._observer.disconnect();
  }

  _mutationHandler(mutationRecords) {
    const settings = this.getSettings();
    mutationRecords.forEach((mutation) => {
      if ($(mutation.target).find('.listFUTItem').length > 0) {
        const controller = getAppMain().getRootViewController()
          .getPresentedViewController().getCurrentViewController()
          .getCurrentController();
        if (!controller || !controller._listController) {
          return;
        }

        let items = [];
        if (controller._listController._view._list) {
          items = controller._listController._view._list._listRows;
        } else {
          items = controller._listController._viewmodel._collection.map(item => (
            { data: item }
          ));
        }
        const rows = $('.listFUTItem');

        rows.each((index, row) => {
          if ($(row).find('.infoTab-extra').length > 0) {
            return; // already added
          }

          let info = '';
          if (settings['show-contracts'].toString() === 'true') {
            info += `<div class="contracts" style="position: absolute;right: 5px;bottom: -3px;">
              C:${items[index].data.contract}
              </div>`;
          }

          $(row).find('.small.player').prepend(`<div class="infoTab-extra">${info}</div>`);
        });
      }
    });
  }
}

new CardInfo(); // eslint-disable-line no-new


================================================
FILE: app/transferlist/index.js
================================================
import { RefreshListSettings } from './refresh-list';
import { MinBinSettings } from './min-bin';
import { CardInfoSettings } from './card-info';
import { ListSizeSettings } from './list-size';
import { TransferTotalsSettings } from './transfer-totals';

export {
  CardInfoSettings,
  RefreshListSettings,
  MinBinSettings,
  ListSizeSettings,
  TransferTotalsSettings,
};


================================================
FILE: app/transferlist/list-size.js
================================================
/* globals
gConfigurationModel models
*/

import { BaseScript, SettingsEntry } from '../core';

export class ListSizeSettings extends SettingsEntry {
  static id = 'list-size';
  constructor() {
    super('list-size', 'Increase transfer list size', null);
    this.addSetting('Items per page on transfer market (max 30)', 'items-per-page-transfermarket', 30, 'number');
    this.addSetting('Items per page on club (max 90)', 'items-per-page-club', 90, 'number');
  }
}

class ListSize extends BaseScript {
  constructor() {
    super(ListSizeSettings.id);
  }

  activate(state) {
    super.activate(state);

    this._start();
  }

  onScreenRequest(screenId) {
    super.onScreenRequest(screenId);

    if (this._running) {
      this._start();
    }
  }

  deactivate(state) {
    super.deactivate(state);

    this._stop();
  }

  _start() {
    this._running = true;

    const itemsOnMarket = parseInt(this.getSettings()['items-per-page-transfermarket'], 10);
    const itemsOnClub = parseInt(this.getSettings()['items-per-page-club'], 10);
    const configObj = gConfigurationModel
      .getConfigObject(models.ConfigurationModel.KEY_ITEMS_PER_PAGE);
    configObj[models.ConfigurationModel.ITEMS_PER_PAGE.TRANSFER_MARKET] = itemsOnMarket;
    configObj[models.ConfigurationModel.ITEMS_PER_PAGE.CLUB] = itemsOnClub;
  }

  _stop() {
    this._running = false;

    const configObj = gConfigurationModel
      .getConfigObject(models.ConfigurationModel.KEY_ITEMS_PER_PAGE);
    configObj[models.ConfigurationModel.ITEMS_PER_PAGE.TRANSFER_MARKET] = 15;
    configObj[models.ConfigurationModel.ITEMS_PER_PAGE.CLUB] = 45;
  }
}

new ListSize(); // eslint-disable-line no-new


================================================
FILE: app/transferlist/min-bin.js
================================================
/* globals
window $ document
*/
import { analytics, BaseScript, SettingsEntry } from '../core';
import { TransferMarket, priceTiers } from '../../fut';

export class MinBinSettings extends SettingsEntry {
  static id = 'min-bin';
  constructor() {
    super('min-bin', 'Search minimum BIN');

    this.addSetting('Amount of lowest BINs to determine minimum on', 'mean-count', 3, 'number');
    this.addSetting('Adjust quicklist panel price automatically based on minimum BIN', 'adjust-list-price', true, 'checkbox');
    this.addSettingUnder('adjust-list-price', 'Start price percentage (0 to 100%)', 'start-price-percentage', 90, 'number');
    this.addSettingUnder('adjust-list-price', 'Buy now price percentage (0 to 100%)', 'buy-now-price-percentage', 110, 'number');
  }
}

class MinBin extends BaseScript {
  constructor() {
    super(MinBinSettings.id);

    const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
    this._observer = new MutationObserver(this._mutationHandler.bind(this));

    this._playerPrices = [];
  }

  activate(state) {
    super.activate(state);

    const obsConfig = {
      childList: true,
      characterData: true,
      attributes: false,
      subtree: true,
    };

    setTimeout(() => {
      this._observer.observe($(document)[0], obsConfig);
    }, 0);
  }

  deactivate(state) {
    super.deactivate(state);

    this._observer.disconnect();
  }

  _mutationHandler(mutationRecords) {
    mutationRecords.forEach(function (mutation) {
      if ($(mutation.target).hasClass('DetailView') && $(mutation.target)
        .find('.DetailPanel') && mutation.addedNodes.length > 0) {
        const searchMinBin = $(mutation.target).find('#searchMinBin');
        searchMinBin.remove();

        let selectedItem = this._getSelectedItem();

        if (selectedItem == null || selectedItem.resourceId === 0) {
          return;
        }
        const knownPlayerPrice = this._playerPrices
          .find(p => p.resourceId === selectedItem.resourceId);
        let price = '';
        if (knownPlayerPrice != null) {
          price = `(${knownPlayerPrice.minimumBin})`;

          this._updateListPrice(knownPlayerPrice.minimumBin);
        }
        $(mutation.target).find('.DetailPanel > .ut-button-group').prepend(`<button id="searchMinBin" data-resource-id="${selectedItem.resourceId}" class="list"><span class="btn-text">Search minimum BIN ${price}</span><span class="btn-subtext"></span></button>`);

        $('#searchMinBin').bind('click', async () => {
          const btn = $('#searchMinBin');
          btn.find('.btn-text').html('Searching minimum BIN...');
          analytics.trackEvent('Min BIN', 'Search Min BIN', btn.data('resource-id'));
          const settings = this.getSettings();
          const minimumBin = await new TransferMarket().searchMinBuy(selectedItem, parseInt(settings['mean-count'], 10));
          const playerPrice = this._playerPrices.find(p => p.resourceId === btn.data('resource-id'));
          if (playerPrice != null) {
            this._playerPrices.splice(this._playerPrices.indexOf(playerPrice), 1);
          }
          this._playerPrices.push({
            resourceId: btn.data('resource-id'),
            minimumBin,
          });

          selectedItem = this._getSelectedItem();

          let notificationText = `Minimum BIN found for ${selectedItem._staticData.name} is ${minimumBin}`;
          if (btn.data('resource-id') === selectedItem.resourceId) {
            if (minimumBin === 0) {
              btn.find('.btn-text').html('Search minimum BIN (extinct)');
              notificationText = `Minimum BIN not found for ${selectedItem._staticData.name}, card may be extinct`;
            } else {
              btn.find('.btn-text').html(`Search minimum BIN (${minimumBin})`);

              this._updateListPrice(minimumBin);
            }
          }

          GM_notification({
            text: notificationText,
            title: 'FUT 21 Web App',
            timeout: 5000,
            onclick: () => window.focus(),
          });
        });
      }
    }, this);
  }

  _updateListPrice(minimumBin) {
    const settings = this.getSettings();
    const quicklistPanel = getAppMain().getRootViewController()
      .getPresentedViewController()
      .getCurrentViewController()
      .getCurrentController()
      ._rightController._currentController._quickListPanel;

    if (settings['adjust-list-price'] && quicklistPanel) {
      const quicklistpanelView = quicklistPanel._view;

      const listPrice = priceTiers.determineListPrice(
        minimumBin * (settings['start-price-percentage'] / 100),
        minimumBin * (settings['buy-now-price-percentage'] / 100),
      );

      if (quicklistPanel._item) {
        // sets the values when the quicklistpanel hasn't been initialized
        const auction = quicklistPanel._item._auction;
        if (auction.tradeState === 'closed') {
          // item is sold
          return;
        }
        if (auction.tradeState !== 'active') {
          auction.startingBid = listPrice.start;
          auction.buyNowPrice = listPrice.buyNow;
          quicklistPanel._item.setAuctionData(auction);
        }
      }

      const bidSpinner = quicklistpanelView._bidNumericStepper;
      const buySpinner = quicklistpanelView._buyNowNumericStepper;
      bidSpinner.setValue(listPrice.start);
      buySpinner.setValue(listPrice.buyNow);
    }
  }

  /* eslint-disable class-methods-use-this */
  _getSelectedItem() {
    const listController = getAppMain().getRootViewController()
      .getPresentedViewController()
      .getCurrentViewController()
      .getCurrentController()._listController;
    if (listController) {
      return listController.getIterator().current();
    }

    const detailController = getAppMain().getRootViewController()
      .getPresentedViewController()
      .getCurrentViewController()
      .getCurrentController()._rightController;
    if (detailController && detailController._currentController._viewmodel) {
      const current = detailController
        ._currentController._viewmodel.current();

      return current._item ? current._item : current;
    }

    return null;
  }
  /* eslint-enable class-methods-use-this */
}

new MinBin(); // eslint-disable-line no-new


================================================
FILE: app/transferlist/refresh-list.js
================================================
/* globals $ */

import { BaseScript, SettingsEntry } from '../core';
import './style/refresh-list.scss';

export class RefreshListSettings extends SettingsEntry {
  static id = 'refresh-transferlist';
  constructor() {
    super('refresh-transferlist', 'Refresh transferlist', null);
  }
}

class RefreshTransferList extends BaseScript {
  constructor() {
    super(RefreshListSettings.id);
  }

  activate(state) {
    super.activate(state);
    this._show(state.screenId);
  }

  onScreenRequest(screenId) {
    super.onScreenRequest(screenId);
    this._show(screenId);
  }

  deactivate(state) {
    super.deactivate(state);
    $('#header').find('.subTitle').find('.refresh').remove();
  }

  /* eslint-disable class-methods-use-this */
  _show(event) {
    switch (event) {
      case 'UTMarketSearchResultsSplitViewController': // market search
        setTimeout(() => {
          if ($('.pagingContainer').find('.refresh').length === 0) {
            $('.pagingContainer').append('<button class="flat pagination refresh" style="float: right;">Refresh list</button>');
            $('.refresh').click(() => {
              const listController = getAppMain().getRootViewController()
                .getPresentedViewController()
                .getCurrentViewController()
                .getCurrentController()
                ._listController;

              const currentPage = listController._paginationViewModel._pageIndex;

              listController._requestItems(currentPage);
            });
          }
        }, 1000);
        break;
      default:
        // no need to show anything on other screens
    }
  }
  /* eslint-enable class-methods-use-this */
}

new RefreshTransferList(); // eslint-disable-line no-new


================================================
FILE: app/transferlist/style/card-info.scss
================================================
.item.player.small.TOTW .infoTab-extra, 
.item.player.small.OTW .infoTab-extra, 
.item.player.small.TOTS .infoTab-extra, 
.item.player.small.TOTY .infoTab-extra, 
.item.player.small.legend .infoTab-extra {
  color: white;
}

.item.player.small .infoTab-extra {
  width: 100%;
  height: 100%;
  position: absolute;
}


================================================
FILE: app/transferlist/style/refresh-list.scss
================================================
button.flat.pagination.refresh{
    &:before {
        font-family: UltimateTeam-Icons,sans-serif;
        content: '\E051';
    }
}

================================================
FILE: app/transferlist/style/transfer-totals.scss
================================================
.transfer-totals {
    background-color: #183f94;
    color: #fff;
    .auction {
        float: right;
        margin: 1em 3em 1em 0;
        text-align: right;
        width: 45%;
        .auctionStartPrice {
            display: none;
            @media (min-width: 1281px) {
                display: block;
            }
        }
        .auctionValue {
            float: left;
            padding-right: 1%;
            width: 24%;
        }
        .label {
            color: #b5b7bb;
            display: block;
            font-size: .75rem;
            text-transform: uppercase;
        }
        .value {
            font-size: 1.125em;
            font-weight: 400;
            font-family: UltimateTeamCondensed,sans-serif;
            display: block;
        }
        @media (max-width: 1130px) {
            align-items: flex-start;
            box-sizing: border-box;
            display: flex;
            float: none;
            margin: 0;
            padding: 0.5em 1.2rem 0.5em 113px;
            text-align: left;
            width: 100%;
        }
    }
    &:after {
        content: '';
        display: table;
        width: 100%;
    }
}


================================================
FILE: app/transferlist/transfer-totals.js
================================================
/* globals
window $ document */

import { BaseScript, SettingsEntry } from '../core';

import './style/transfer-totals.scss';

export class TransferTotalsSettings extends SettingsEntry {
  static id = 'transfer-totals';
  constructor() {
    super('transfer-totals', 'Transfer list totals', null);

    this.addSetting('Show transfer list totals', 'show-transfer-totals', true, 'checkbox');
  }
}

class TransferTotals extends BaseScript {
  constructor() {
    super(TransferTotalsSettings.id);

    const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
    this._observer = new MutationObserver(this._mutationHandler.bind(this));
  }

  activate(state) {
    super.activate(state);

    const obsConfig = {
      childList: true,
      characterData: true,
      attributes: false,
      subtree: true,
    };

    setTimeout(() => {
      this._observer.observe($(document)[0], obsConfig);
    }, 0);
  }

  deactivate(state) {
    super.deactivate(state);
    this._observer.disconnect();
  }

  _mutationHandler(mutationRecords) {
    const settings = this.getSettings();
    mutationRecords.forEach((mutation) => {
      if (
        $(mutation.target).find('.listFUTItem').length > 0 ||
        $(mutation.target).find('.futbin').length > 0
      ) {
        const controller = getAppMain()
          .getRootViewController()
          .getPresentedViewController()
          .getCurrentViewController()
          .getCurrentController();
        if (!controller || !controller._listController) {
          return;
        }

        if (window.currentPage !== 'UTTransferListSplitViewController') {
          return;
        }

        if (!settings.isActive || settings['show-transfer-totals'].toString() !== 'true') {
          return;
        }

        const lists = $('.ut-transfer-list-view .itemList');
        const items = controller._listController._viewmodel._collection;
        const listRows = $('.ut-transfer-list-view .listFUTItem');

        lists.each((index, list) => {
          const totals = {
            futbin: 0,
            bid: 0,
            bin: 0,
          };
          const listEl = $(list);

          if (!listEl.find('.listFUTItem').length) {
            return;
          }

          const firstIndex = $(list).find('.listFUTItem:first').index('.ut-transfer-list-view .listFUTItem');
          const lastIndex = $(list).find('.listFUTItem:last').index('.ut-transfer-list-view .listFUTItem');

          totals.futbin = items.slice(firstIndex, lastIndex + 1).reduce((sum, item, i) => {
            const futbin = parseInt(
              listRows.eq(i + firstIndex)
                .find('.auctionValue.futbin .coins.value')
                .text()
                .replace(/[,.]/g, ''),
              10,
            ) || 0;
            return sum + futbin;
          }, 0);
          totals.bid = items.slice(firstIndex, lastIndex + 1)
            .reduce((sum, item) => {
              const { currentBid, startingBid } = item._auction;
              const actualBid = currentBid > 0 ? currentBid : startingBid;
              return sum + actualBid;
            }, 0);
          totals.bin = items.slice(firstIndex, lastIndex + 1)
            .reduce((sum, item) => sum + item._auction.buyNowPrice, 0);

          const totalsItem = listEl.prev('.transfer-totals');

          if (!totalsItem.length) {
            $(`<div class="transfer-totals">
            <div class="auction">
              <div class="auctionValue futbin">
                <span class="label">Futbin BIN</span>
                <span class="coins value total-futbin">0</span>
              </div>
              <div class="auctionValue">
                <span class="label">Bid Total</span>
                <span class="coins value total-bid">0</span>
              </div>
              <div class="auctionValue">
                <span class="label">BIN Total</span>
                <span class="coins value total-bin">0</span>
              </div>
            </div>
          </div>`).insertBefore(listEl);
          }

          if (totals.futbin > 0) {
            totalsItem.find('.total-futbin').text(totals.futbin);
            totalsItem.find('.futbin').show();
          } else {
            totalsItem.find('.futbin').hide();
          }
          totalsItem.find('.total-bin').text(totals.bin);
          totalsItem.find('.total-bid').text(totals.bid);
        });
      }
    });
  }
}

new TransferTotals(); // eslint-disable-line no-new


================================================
FILE: fut/club.js
================================================
/* globals
transferobjects enums communication factories
*/

export class Club {
  async getPlayers(start, count) {
    return new Promise((resolve, reject) => {
      const t = new transferobjects.SearchCriteria();
      t.type = enums.SearchType.PLAYER;

      const o = new communication.ClubSearchDelegate(t, start, count);
      o._useClickShield = false;

      o.addListener(communication.BaseDelegate.SUCCESS, this, (sender, response) => {
        sender.clearListenersByScope(this);

        const players = Array.isArray(response.itemData) ?
          factories.Item.generateItemsFromItemData(response.itemData) : [];
        const isLastPage = players.length <= count - 1;
        resolve({
          isLastPage,
          getNextPage: isLastPage ? null : () => this.getPlayers(start + count, count),
          players,
        });
      });

      o.addListener(communication.BaseDelegate.FAIL, this, (sender, response) => {
        sender.clearListenersByScope(this);
        reject(response);
      });

      o.send();
    });
  }
}


================================================
FILE: fut/errors/index.js
================================================
export class ListPlayerError extends Error {}


================================================
FILE: fut/index.js
================================================
import { Logger } from './logger';
import { PinEvent } from './pinEvent';
import { Store } from './store';
import { TransferMarket } from './transferMarket';
import { Club } from './club';
import utils from './utils';
import priceTiers from './priceTiers';

export {
  Club,
  Logger,
  PinEvent,
  Store,
  TransferMarket,
  utils,
  priceTiers,
};


================================================
FILE: fut/logger.js
================================================
export class Logger {
  constructor() {
    this._storeName = 'logger';
  }

  log(message, category = 'FUT') {
    /* eslint-disable no-console */
    console.log(`${category}: ${message}`);
    /* eslint-enable no-console */
    const log = JSON.parse(GM_getValue(this._storeName, '[]'));
    log.push(`${category}: ${message}`);
    GM_setValue(this._storeName, JSON.stringify(log));
  }

  reset() {
    GM_setValue(this._storeName, '[]');
  }
}


================================================
FILE: fut/pinEvent.js
================================================
/* globals PIN_PAGEVIEW_EVT_TYPE services PINEventType */

export class PinEvent {
  static sendPageView(pageId, delay = 2000) {
    return new Promise(resolve =>
      setTimeout(() => {
        services.PIN.sendData(PINEventType.PAGE_VIEW, {
          type: PIN_PAGEVIEW_EVT_TYPE,
          pgid: pageId,
        });
        resolve();
      }, delay));
  }
}


================================================
FILE: fut/priceTiers.js
================================================
/* global utils UTCurrencyInputControl */
export default {
  roundValueToNearestPriceTiers(value) {
    const tier = utils.JS.find(UTCurrencyInputControl.PRICE_TIERS, i => value > i.min);

    const diff = value % tier.inc;

    if (diff === 0) {
      return value;
    } else if (diff < tier.inc / 2) {
      return value - diff;
    }
    return value + (tier.inc - diff);
  },

  roundDownToNearestPriceTiers(value) {
    const tier = utils.JS.find(UTCurrencyInputControl.PRICE_TIERS, i => value > i.min);

    const diff = value % tier.inc;

    if (diff === 0) {
      return value - tier.inc;
    }
    return value - diff;
  },

  determineListPrice(start, buyNow) {
    const tier = utils.JS.find(UTCurrencyInputControl.PRICE_TIERS, i => buyNow > i.min);

    const startPrice = this.roundValueToNearestPriceTiers(start);
    let buyNowPrice = this.roundValueToNearestPriceTiers(buyNow);

    if (startPrice === buyNowPrice) {
      buyNowPrice += tier.inc;
    }

    return {
      start: startPrice,
      buyNow: buyNowPrice,
    };
  },
};


================================================
FILE: fut/store.js
================================================
/* global communication repositories enums services */

export class Store {
  getUnassignedItems() {
    return new Promise((resolve) => {
      repositories.Item.reset(enums.FUTItemPile.PURCHASED);
      repositories.Item.getUnassignedItems().observe(this, function (o, list) {
        o.unobserve(this);
        resolve(list.items);
      });
    });
  }

  getTradePile() {
    return new Promise((resolve, reject) => {
      repositories.Item.getTransferItems().observe(this, (obs, data) => {
        obs.unobserve(this);

        if (data.error) {
          reject(new Error(data.erorr));
        } else {
          resolve(data.items);
        }
      });
    });
  }

  async getTradePileUnsold() {
    const tradepile = await this.getTradePile();

    return tradepile.filter(d => d.state === enums.ItemState.FREE && d._auction.buyNowPrice > 0);
  }

  redeemItem(item) {
    return new Promise((resolve, reject) => {
      const redeem = new communication.ConsumeUnlockableDelegate(item.id);
      redeem.addListener(communication.BaseDelegate.SUCCESS, this, (sender, response) => {
        sender.clearListenersByScope(this);
        resolve(response);
      });
      redeem.addListener(communication.BaseDelegate.FAIL, this, (sender, response) => {
        sender.clearListenersByScope(this);
        reject(response);
      });
      redeem.send();
    });
  }

  quickSell(items) {
    return new Promise((resolve) => {
      services.Item.discard(items).observe(this, (obs, res) => {
        obs.unobserve(this);
        resolve(res);
      });
    });
  }

  sendToClub(items) {
    return new Promise((resolve, reject) => {
      const moveItem = new communication.MoveItemDelegate(items, enums.FUTItemPile.CLUB);
      moveItem.addListener(communication.BaseDelegate.SUCCESS, this, (sender, response) => {
        sender.clearListenersByScope(this);
        resolve(response);
      });
      moveItem.addListener(communication.BaseDelegate.FAIL, this, (sender, response) => {
        sender.clearListenersByScope(this);
        reject(response);
      });
      moveItem.send();
    });
  }

  removeSoldAuctions() {
    return new Promise((resolve, reject) => {
      services.Item.clearSoldItems().observe(this, (observer, data) => {
        observer.unobserve(this);

        if (data.error) {
          reject(new Error(data.erorr));
        } else {
          resolve(data.items);
        }
      });
    });
  }
}


================================================
FILE: fut/transferMarket.js
================================================
/* globals
enums factories communication gUserModel models repositories services
*/
import { mean } from 'math-statistics';

import utils from './utils';
import priceTiers from './priceTiers';
import { Logger } from './logger';
import { PinEvent } from './pinEvent';
import { ListItemError } from './errors';

export class TransferMarket {
  _logger = new Logger();

  /* eslint-disable class-methods-use-this */
  async navigateToTransferHub() {
    await PinEvent.sendPageView('Hub - Transfers');
  }

  async navigateToTransferList() {
    await this.navigateToTransferHub();
    await PinEvent.sendPageView('Transfer List - List View');
  }
  /* eslint-enable class-methods-use-this */

  async searchMinBuy(item, itemsForMean = 3, lowUp = false) {
    services.Item.clearTransferMarketCache();

    this._logger.log(`Searching min buy for ${item.type} ${item._staticData.name} from low upward first ${lowUp}`, 'Core - Transfermarket');
    let minBuy = 0;

    if (lowUp) {
      minBuy = await this._findLowUp(item, itemsForMean);
      this._logger.log(`Low up search yielded ${minBuy} as a result`, 'Core - Transfermarket');
    }

    if (minBuy === 0) {
      this._logger.log('Searching low down...', 'Core - Transfermarket');
      minBuy = await this._findLowDown(item, itemsForMean);
    }

    if (minBuy === 0) {
      this._logger.log('No players found... it might be extinct', 'Core - Transfermarket');
    } else {
      this._logger.log(`Min buy for ${item.type} ${item._staticData.name} is ${minBuy}`, 'Core - Transfermarket');
    }
    return minBuy;
  }

  /**
   * List item on transfermarket
   *
   * @param {FUTItem} item
   * @param {number} start start price
   * @param {number} buyNow buy now price
   * @param {number} duration time to list in seconds (1, 3, 6, 12, 24 or 72 hours)
   */
  async listItem(item, start, buyNow, duration = 3600) {
    return new Promise(async (resolve, reject) => {
      if (gUserModel.getTradeAccess() !== models.UserModel.TRADE_ACCESS.WHITELIST) {
        reject(new Error('You are not authorized for trading'));
        return;
      }

      const prices = priceTiers.determineListPrice(start, buyNow);

      await this.sendToTradePile(item);
      await utils.sleep(1000);

      const listItem = new communication.ListItemDelegate({
        itemId: item.id,
        startingBid: prices.start,
        buyNowPrice: prices.buyNow,
        duration,
      });
      listItem.addListener(communication.BaseDelegate.SUCCESS, this, (sender) => {
        sender.clearListenersByScope(this);
        resolve({
          startingBid: prices.start,
          buyNowPrice: prices.buyNow,
        });
      });
      listItem.addListener(communication.BaseDelegate.FAIL, this, (sender, response) => {
        sender.clearListenersByScope(this);
        reject(new ListItemError(response));
      });
      listItem.send();
    });
  }

  sendToTradePile(item) {
    return new Promise((resolve, reject) => {
      const moveItem = new communication.MoveItemDelegate([item], enums.FUTItemPile.TRANSFER);
      moveItem.addListener(communication.BaseDelegate.SUCCESS, this, (sender) => {
        sender.clearListenersByScope(this);
        resolve();
      });
      moveItem.addListener(communication.BaseDelegate.FAIL, this, (sender, response) => {
        sender.clearListenersByScope(this);
        reject(new Error(response));
      });
      moveItem.send();
    });
  }

  relistAllItems() {
    return new Promise((resolve, reject) => {
      if (gUserModel.getTradeAccess() !== models.UserModel.TRADE_ACCESS.WHITELIST) {
        reject(new Error('You are not authorized for trading'));
        return;
      }

      const relistExpired = new communication.AuctionRelistDelegate();

      relistExpired.addListener(communication.BaseDelegate.SUCCESS, this, (sender) => {
        sender.clearListenersByScope(this);
        repositories.Item.setDirty(enums.FUTItemPile.TRANSFER);
        resolve();
      });

      relistExpired.addListener(communication.BaseDelegate.FAIL, this, (sender, error) => {
        sender.clearListenersByScope(this);
        reject(new Error(error));
      });
      relistExpired.execute();
    });
  }

  async _findLowUp(item, itemsForMean) {
    const searchCriteria = this._defineSearchCriteria(item, 200);
    await PinEvent.sendPageView('Transfer Market Search');
    await utils.sleep(3000);
    await PinEvent.sendPageView('Transfer Market Results - List View', 0);
    await PinEvent.sendPageView('Item - Detail View', 0);
    const items = await this._find(searchCriteria);
    if (items.length > itemsForMean) {
      // we find more than X listed at this price, so it must be low value
      return 200;
    }

    return 0; // trigger searching low down
  }

  async _findLowDown(item, itemsForMean) {
    let minBuy = 99999999;
    const searchCriteria = this._defineSearchCriteria(item);

    let valuesFound = [];
    for (let minBuyFound = false; minBuyFound === false;) {
      /* eslint-disable no-await-in-loop */
      await PinEvent.sendPageView('Transfer Market Search');
      await utils.sleep(800);
      await PinEvent.sendPageView('Transfer Market Results - List View', 0);
      await PinEvent.sendPageView('Item - Detail View', 0);
      const items = await this._find(searchCriteria);
      /* eslint-enable no-await-in-loop */
      if (items.length > 0) {
        valuesFound = valuesFound.concat(items.map(i => i._auction.buyNowPrice));

        const minBuyOnPage = Math.min(...items.map(i => i._auction.buyNowPrice));
        if (minBuyOnPage < minBuy) {
          minBuy = minBuyOnPage;
          if (items.length < searchCriteria.count) {
            minBuyFound = true;
            break;
          }
          searchCriteria.maxBuy = priceTiers.roundDownToNearestPriceTiers(minBuy);
          if (searchCriteria.maxBuy < 200) {
            searchCriteria.maxBuy = 200;
          }
        } else if (items.length === searchCriteria.count) {
          if (searchCriteria.maxBuy === 0) {
            searchCriteria.maxBuy = minBuy;
          } else {
            searchCriteria.maxBuy = priceTiers.roundDownToNearestPriceTiers(searchCriteria.maxBuy);
          }
          if (searchCriteria.maxBuy < 200) {
            searchCriteria.maxBuy = 200;
            minBuy = 200;
            minBuyFound = true;
          }
        } else {
          minBuy = Math.min(...items.map(i => i._auction.buyNowPrice));
          minBuyFound = true;
        }
      } else {
        minBuyFound = true;
      }
    }

    valuesFound = valuesFound.sort((a, b) => a - b).slice(0, itemsForMean);

    if (valuesFound.length > 0) {
      return priceTiers.roundValueToNearestPriceTiers(mean(valuesFound));
    }

    return 0; // player extinct
  }

  /* eslint-disable class-methods-use-this */
  _defineSearchCriteria(item, maxBuy = -1) {
    // TODO: check if this can handle other items as well
    // eslint-disable-next-line no-undef
    const searchCriteria = new UTSearchCriteriaDTO();

    searchCriteria.count = 30;
    searchCriteria.maskedDefId = item.getMaskedResourceId();
    searchCriteria.type = item.type;

    if (item.rareflag === 47) { // 47 = Champions
      // if it is a Champions card, this is seen as a gold card
      // Can only search for "Gold" in this case
      searchCriteria.level = factories.DataProvider.getItemLevelDP(true)
        .filter(d => d.id === 2)[0].value;
    } else if (item.rareflag >= 3) { // 3 = TOTW
      // if it is TOTW or other special, set it to TOTW. See enums.ItemRareType.
      // Can only search for "Specials", not more specific on Rare Type
      searchCriteria.level = factories.DataProvider.getItemLevelDP(true)
        .filter(d => d.id === 3)[0].value;
    }

    searchCriteria.category = enums.SearchCategory.ANY;
    searchCriteria.position = enums.SearchType.ANY;
    if (maxBuy !== -1) {
      searchCriteria.maxBuy = maxBuy;
    }

    return searchCriteria;
  }
  /* eslint-enable class-methods-use-this */

  _find(searchCriteria) {
    return new Promise((resolve, reject) => {
      services.Item.searchTransferMarket(searchCriteria, 1).observe(
        this,
        function (obs, res) {
          if (!res.success) {
            obs.unobserve(this);
            reject(res.status);
          } else {
            resolve(res.data.items);
          }
        },
      );
    });
  }
}


================================================
FILE: fut/utils.js
================================================
/* globals
services
*/

export default {
  /**
   * Sleep for a while
   *
   * @param {number} min minimum sleep time in ms
   * @param {number} variance maximum variation to add to the minimum in ms
   */
  sleep(min, variance = 1000) {
    const delay = min + Math.floor(Math.random() * variance);
    // new Logger().log(`Delay for ${delay} (requested: ${min}+${variance})`, 'Core');
    return new Promise(resolve => setTimeout(resolve, delay));
  },

  getPlatform() {
    if (services.User.getUser().getSelectedPersona().isPlaystation) {
      return 'ps';
    }
    if (services.User.getUser().getSelectedPersona().isPC) {
      return 'pc';
    }
    if (services.User.getUser().getSelectedPersona().isXbox) {
      return 'xbox';
    }

    throw new Error('unknown platform');
  },
};


================================================
FILE: package.json
================================================
{
  "name": "futwebapp-single",
  "version": "0.0.0-development",
  "description": "",
  "main": "index.js",
  "scripts": {
    "clean": "rm -rf dist",
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "npm run standards:watch & webpack --watch",
    "build": "npm run standards && webpack",
    "build:production": "npm run standards && npm run semantic-release:dry && webpack --config webpack.config.prd.js",
    "standards": "eslint .",
    "standards:watch": "esw --watch",
    "semantic-release:dry": "semantic-release --dry-run",
    "semantic-release": "semantic-release"
  },
  "eslintIgnore": [
    "dist",
    "webpack.config.js",
    "webpack.config.prd.js",
    "tampermonkey-headers.js"
  ],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-cli": "^6.26.0",
    "babel-core": "^6.26.3",
    "babel-eslint": "^8.0.1",
    "babel-loader": "^7.1.2",
    "babel-plugin-transform-class-properties": "^6.24.1",
    "babel-preset-env": "^1.7.0",
    "css-loader": "^0.28.7",
    "eslint": "^4.10.0",
    "eslint-config-airbnb": "^16.1.0",
    "eslint-plugin-import": "^2.8.0",
    "eslint-plugin-jsx-a11y": "^6.0.2",
    "eslint-plugin-react": "^7.4.0",
    "eslint-watch": "^3.1.3",
    "html-loader": "^0.5.1",
    "last-release-git": "0.0.3",
    "node-libs-browser": "webpack/node-libs-browser",
    "node-sass": "^4.14.1",
    "sass-loader": "^6.0.6",
    "semantic-release": "^9.0.0",
    "style-loader": "^0.19.0",
    "webpack": "^3.8.1",
    "webpack-cli": "^3.3.12",
    "webpack-obfuscator": "^2.4.3"
  },
  "dependencies": {
    "babel-polyfill": "^6.26.0",
    "c3": "^0.4.18",
    "d3": "^4.11.0",
    "event-emitter-es6": "^1.1.5",
    "math-statistics": "^1.2.0",
    "moment": "^2.22.2",
    "moment-duration-format": "^1.3.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/Mardaneus86/futwebapp-tampermonkey.git"
  },
  "release": {
    "getLastRelease": "last-release-git",
    "branch": "master"
  }
}


================================================
FILE: tampermonkey-headers.js
================================================
// ==UserScript==
// @name        FUT Enhancer
// @version     VERSION
// @description Enhances the FIFA Ultimate Team 21 Web app. Includes Futbin integration and other useful tools
// @license     MIT
// @author      Tim Klingeleers
// @match       https://www.ea.com/fifa/ultimate-team/web-app/*
// @match       https://www.easports.com/*/fifa/ultimate-team/web-app/*
// @match       https://www.ea.com/*/fifa/ultimate-team/web-app/*
// @namespace   https://github.com/Mardaneus86
// @supportURL  https://github.com/Mardaneus86/futwebapp-tampermonkey/issues
// @grant       GM_notification
// @grant       GM_xmlhttpRequest
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       window.focus
// @connect     ea.com
// @connect     futbin.com
// @connect     google-analytics.com
// @updateURL   https://github.com/Mardaneus86/futwebapp-tampermonkey-web/raw/master/downloads/FUT_Enhancer.meta.js
// @downloadURL https://github.com/Mardaneus86/futwebapp-tampermonkey-web/raw/master/downloads/FUT_Enhancer.user.js
// ==/UserScript==


================================================
FILE: webpack.config.js
================================================
const webpack = require('webpack');

module.exports = {
  entry: './app/index.js',
  output: {
    filename: './dist/fut-enhancer.user.js',
  },
  node: {
    fs: 'empty',
    tls: 'empty',
    net: 'empty'
  },
  devtool: 'eval-source-map',
  plugins: [
    new webpack.DefinePlugin({
        'UA_TOKEN': JSON.stringify('UA-126264296-1')
    })
  ],
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['env'],
            plugins: [
              'transform-class-properties',
            ],
          },
        },
      },
      {
        test: /\.scss$|\.css$/,
        use: [{
          loader: 'style-loader', // creates style nodes from JS strings
        }, {
          loader: 'css-loader', // translates CSS into CommonJS
        }, {
          loader: 'sass-loader', // compiles Sass to CSS
        }],
      },
      {
        test: /\.html$/,
        use: [{
          loader: 'html-loader',
          options: {
            minimize: true,
            removeComments: false,
            collapseWhitespace: false,
          },
        }],
      },
    ],
  },
};


================================================
FILE: webpack.config.prd.js
================================================
var fs = require("fs");

const webpack = require('webpack');
const config = require('./webpack.config');

module.exports = env => {
  let header = fs.readFileSync('./tampermonkey-headers.js', 'utf8');
  header = header.replace('VERSION', process.env.TM_VERSION); // set by the build process on Travis
  
  console.log('Changed Tampermonkey header version to ' + process.env.TM_VERSION);
  
  config.devtool = 'none';
  config.plugins = [
    new webpack.DefinePlugin({
        'UA_TOKEN': JSON.stringify('UA-126264296-2')
    }),
    new webpack.BannerPlugin({ 
      banner: header,
      raw: true, 
      entryOnly: true 
    }),
  ];

  return config;
};
Download .txt
gitextract_atpnmaik/

├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github/
│   ├── FUNDING.yml
│   └── ISSUE_TEMPLATE/
│       ├── bug_report.md
│       └── feature_request.md
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── analytics/
│   ├── LICENSE
│   ├── config.js
│   └── index.js
├── app/
│   ├── core/
│   │   ├── analytics.js
│   │   ├── base-script.js
│   │   ├── browser.js
│   │   ├── db.js
│   │   ├── index.js
│   │   ├── queue.js
│   │   ├── settings-entry.js
│   │   └── settings.js
│   ├── futbin/
│   │   ├── futbin-player-links.js
│   │   ├── futbin-prices.js
│   │   ├── index.js
│   │   ├── settings-entry.js
│   │   └── style/
│   │       └── futbin-prices.scss
│   ├── index.js
│   ├── index.scss
│   ├── instant-bin-confirm/
│   │   ├── index.js
│   │   ├── instant-bin-confirm.js
│   │   └── settings-entry.js
│   ├── settings/
│   │   ├── html/
│   │   │   └── index/
│   │   │       └── settings.html
│   │   ├── index.js
│   │   └── index.scss
│   └── transferlist/
│       ├── card-info.js
│       ├── index.js
│       ├── list-size.js
│       ├── min-bin.js
│       ├── refresh-list.js
│       ├── style/
│       │   ├── card-info.scss
│       │   ├── refresh-list.scss
│       │   └── transfer-totals.scss
│       └── transfer-totals.js
├── fut/
│   ├── club.js
│   ├── errors/
│   │   └── index.js
│   ├── index.js
│   ├── logger.js
│   ├── pinEvent.js
│   ├── priceTiers.js
│   ├── store.js
│   ├── transferMarket.js
│   └── utils.js
├── package.json
├── tampermonkey-headers.js
├── webpack.config.js
└── webpack.config.prd.js
Download .txt
SYMBOL INDEX (136 symbols across 26 files)

FILE: analytics/index.js
  function init (line 8) | function init (tid, cid, options) {
  function nextIteration (line 384) | function nextIteration(err) {

FILE: app/core/analytics.js
  class Analytics (line 5) | class Analytics {
    method constructor (line 6) | constructor() {
    method _uuidv4 (line 23) | _uuidv4() {
    method trackPage (line 31) | trackPage(pageId) {
    method trackEvent (line 43) | trackEvent(category, action, label = null, value = null) {

FILE: app/core/base-script.js
  class BaseScript (line 6) | class BaseScript {
    method constructor (line 7) | constructor(id) {
    method activate (line 38) | activate() {
    method deactivate (line 42) | deactivate() {
    method onScreenRequest (line 46) | onScreenRequest() {
    method getSettings (line 50) | getSettings() {

FILE: app/core/browser.js
  method downloadFile (line 6) | downloadFile(filename, data) {

FILE: app/core/db.js
  class Database (line 3) | class Database {
    method constructor (line 4) | constructor() {
    method set (line 8) | static set(key, value) {
    method setJson (line 12) | static setJson(key, value) {
    method get (line 16) | static get(key, defaultValue) {
    method getJson (line 24) | static getJson(key, defaultValue) {

FILE: app/core/queue.js
  class Queue (line 3) | class Queue {
    method constructor (line 4) | constructor() {
    method getInstance (line 8) | static getInstance() {
    method add (line 16) | add(identifier, cb) {
    method start (line 23) | async start() {
    method stop (line 39) | stop() {

FILE: app/core/settings-entry.js
  class SettingsEntry (line 3) | class SettingsEntry {
    method constructor (line 4) | constructor(id, name) {
    method toggle (line 13) | toggle() {
    method addSetting (line 21) | addSetting(label, key, defaultValue, type, cb) {
    method addSettingUnder (line 37) | addSettingUnder(underKey, label, key, defaultValue, type, cb) {
    method changeValue (line 52) | changeValue(key, value) {

FILE: app/core/settings.js
  class Settings (line 5) | class Settings extends EventEmitter {
    method constructor (line 6) | constructor() {
    method getInstance (line 11) | static getInstance() {
    method registerEntry (line 23) | registerEntry(entry) {
    method getEntries (line 31) | getEntries() {
    method toggleEntry (line 35) | toggleEntry(id) {
    method _emitEvent (line 47) | _emitEvent(entry) {

FILE: app/futbin/futbin-player-links.js
  class FutbinPlayerLinks (line 7) | class FutbinPlayerLinks extends BaseScript {
    method constructor (line 8) | constructor() {
    method activate (line 17) | activate(state) {
    method deactivate (line 32) | deactivate(state) {
    method _mutationHandler (line 40) | _mutationHandler(mutationRecords) {
    method _getFutbinPlayerUrl (line 79) | static _getFutbinPlayerUrl(item) {
    method _getSelectedItem (line 130) | _getSelectedItem() {

FILE: app/futbin/futbin-prices.js
  class FutbinPrices (line 10) | class FutbinPrices extends BaseScript {
    method constructor (line 11) | constructor() {
    method activate (line 16) | activate(state) {
    method onScreenRequest (line 22) | onScreenRequest(screenId) {
    method deactivate (line 75) | deactivate(state) {
    method _show (line 89) | _show(screen, force = false) {
    method loadFutbinPrices (line 132) | loadFutbinPrices(showFutbinPricePages, screen, force) {
    method _showFutbinPrice (line 304) | static async _showFutbinPrice(screen, item, futbinData, showBargain) {

FILE: app/futbin/settings-entry.js
  class FutbinSettings (line 3) | class FutbinSettings extends SettingsEntry {
    method constructor (line 5) | constructor() {

FILE: app/instant-bin-confirm/instant-bin-confirm.js
  class InstantBinConfirm (line 12) | class InstantBinConfirm extends BaseScript {
    method constructor (line 15) | constructor() {
    method activate (line 19) | activate(state) {
    method onScreenRequest (line 23) | onScreenRequest(screenId) {
    method deactivate (line 62) | deactivate(state) {

FILE: app/instant-bin-confirm/settings-entry.js
  class InstantBinConfirmSettings (line 3) | class InstantBinConfirmSettings extends SettingsEntry {
    method constructor (line 5) | constructor() {

FILE: app/transferlist/card-info.js
  class CardInfoSettings (line 8) | class CardInfoSettings extends SettingsEntry {
    method constructor (line 10) | constructor() {
  class CardInfo (line 17) | class CardInfo extends BaseScript {
    method constructor (line 18) | constructor() {
    method activate (line 25) | activate(state) {
    method deactivate (line 40) | deactivate(state) {
    method _mutationHandler (line 45) | _mutationHandler(mutationRecords) {

FILE: app/transferlist/list-size.js
  class ListSizeSettings (line 7) | class ListSizeSettings extends SettingsEntry {
    method constructor (line 9) | constructor() {
  class ListSize (line 16) | class ListSize extends BaseScript {
    method constructor (line 17) | constructor() {
    method activate (line 21) | activate(state) {
    method onScreenRequest (line 27) | onScreenRequest(screenId) {
    method deactivate (line 35) | deactivate(state) {
    method _start (line 41) | _start() {
    method _stop (line 52) | _stop() {

FILE: app/transferlist/min-bin.js
  class MinBinSettings (line 7) | class MinBinSettings extends SettingsEntry {
    method constructor (line 9) | constructor() {
  class MinBin (line 19) | class MinBin extends BaseScript {
    method constructor (line 20) | constructor() {
    method activate (line 29) | activate(state) {
    method deactivate (line 44) | deactivate(state) {
    method _mutationHandler (line 50) | _mutationHandler(mutationRecords) {
    method _updateListPrice (line 112) | _updateListPrice(minimumBin) {
    method _getSelectedItem (line 150) | _getSelectedItem() {

FILE: app/transferlist/refresh-list.js
  class RefreshListSettings (line 6) | class RefreshListSettings extends SettingsEntry {
    method constructor (line 8) | constructor() {
  class RefreshTransferList (line 13) | class RefreshTransferList extends BaseScript {
    method constructor (line 14) | constructor() {
    method activate (line 18) | activate(state) {
    method onScreenRequest (line 23) | onScreenRequest(screenId) {
    method deactivate (line 28) | deactivate(state) {
    method _show (line 34) | _show(event) {

FILE: app/transferlist/transfer-totals.js
  class TransferTotalsSettings (line 8) | class TransferTotalsSettings extends SettingsEntry {
    method constructor (line 10) | constructor() {
  class TransferTotals (line 17) | class TransferTotals extends BaseScript {
    method constructor (line 18) | constructor() {
    method activate (line 25) | activate(state) {
    method deactivate (line 40) | deactivate(state) {
    method _mutationHandler (line 45) | _mutationHandler(mutationRecords) {

FILE: fut/club.js
  class Club (line 5) | class Club {
    method getPlayers (line 6) | async getPlayers(start, count) {

FILE: fut/errors/index.js
  class ListPlayerError (line 1) | class ListPlayerError extends Error {}

FILE: fut/logger.js
  class Logger (line 1) | class Logger {
    method constructor (line 2) | constructor() {
    method log (line 6) | log(message, category = 'FUT') {
    method reset (line 15) | reset() {

FILE: fut/pinEvent.js
  class PinEvent (line 3) | class PinEvent {
    method sendPageView (line 4) | static sendPageView(pageId, delay = 2000) {

FILE: fut/priceTiers.js
  method roundValueToNearestPriceTiers (line 3) | roundValueToNearestPriceTiers(value) {
  method roundDownToNearestPriceTiers (line 16) | roundDownToNearestPriceTiers(value) {
  method determineListPrice (line 27) | determineListPrice(start, buyNow) {

FILE: fut/store.js
  class Store (line 3) | class Store {
    method getUnassignedItems (line 4) | getUnassignedItems() {
    method getTradePile (line 14) | getTradePile() {
    method getTradePileUnsold (line 28) | async getTradePileUnsold() {
    method redeemItem (line 34) | redeemItem(item) {
    method quickSell (line 49) | quickSell(items) {
    method sendToClub (line 58) | sendToClub(items) {
    method removeSoldAuctions (line 73) | removeSoldAuctions() {

FILE: fut/transferMarket.js
  class TransferMarket (line 12) | class TransferMarket {
    method navigateToTransferHub (line 16) | async navigateToTransferHub() {
    method navigateToTransferList (line 20) | async navigateToTransferList() {
    method searchMinBuy (line 26) | async searchMinBuy(item, itemsForMean = 3, lowUp = false) {
    method listItem (line 58) | async listItem(item, start, buyNow, duration = 3600) {
    method sendToTradePile (line 91) | sendToTradePile(item) {
    method relistAllItems (line 106) | relistAllItems() {
    method _findLowUp (line 129) | async _findLowUp(item, itemsForMean) {
    method _findLowDown (line 144) | async _findLowDown(item, itemsForMean) {
    method _defineSearchCriteria (line 201) | _defineSearchCriteria(item, maxBuy = -1) {
    method _find (line 232) | _find(searchCriteria) {

FILE: fut/utils.js
  method sleep (line 12) | sleep(min, variance = 1000) {
  method getPlatform (line 18) | getPlatform() {
Condensed preview — 56 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (124K chars).
[
  {
    "path": ".editorconfig",
    "chars": 136,
    "preview": "# top-most EditorConfig file\nroot = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\n\n[*.js]\nindent_style = space\n"
  },
  {
    "path": ".eslintignore",
    "chars": 70,
    "preview": "dist/\ntampermonkey-headers.js\nwebpack.config.js\nwebpack.config.prd.js\n"
  },
  {
    "path": ".eslintrc",
    "chars": 325,
    "preview": "{\n  \"extends\": \"airbnb\",\n  \"parser\": \"babel-eslint\",\n  \"rules\": {\n    \"func-names\": [\"error\", \"never\"],\n    \"import/pref"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 614,
    "preview": "# These are supported funding model platforms\n\ngithub: Mardaneus86\npatreon: # Replace with a single Patreon username\nope"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 684,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\nREAD BEFORE YOU"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 499,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\nREAD"
  },
  {
    "path": ".gitignore",
    "chars": 29,
    "preview": "dist/\nnode_modules/\n.vscode/\n"
  },
  {
    "path": ".travis.yml",
    "chars": 1648,
    "preview": "language: node_js\ncache:\n  directories:\n  - ~/.npm\n  - ~/node_modules\nnotifications:\n  email: false\nnode_js:\n  - '8'\nbef"
  },
  {
    "path": "LICENSE",
    "chars": 1072,
    "preview": "MIT License\n\nCopyright (c) 2017 Tim Klingeleers\n\nPermission is hereby granted, free of charge, to any person obtaining a"
  },
  {
    "path": "README.md",
    "chars": 5189,
    "preview": "# FUT Web App - TamperMonkey scripts\n\n[![Join the chat at https://gitter.im/futwebapp-tampermonkey/Lobby](https://badges"
  },
  {
    "path": "analytics/LICENSE",
    "chars": 1105,
    "preview": "(The MIT License)\n\nCopyright (c) 2017 Peaks & Pies GmbH <hello@peaksandpies.com>;\n\nPermission is hereby granted, free of"
  },
  {
    "path": "analytics/config.js",
    "chars": 3927,
    "preview": "/* eslint-disable */\nmodule.exports = {\n\tprotocolVersion: \"1\",\n\thostname: \"https://www.google-analytics.com\",\n\tpath: \"/c"
  },
  {
    "path": "analytics/index.js",
    "chars": 12868,
    "preview": "/* eslint-disable */\nimport querystring from 'querystring';\n\nimport config from './config';\n\nmodule.exports = init;\n\nfun"
  },
  {
    "path": "app/core/analytics.js",
    "chars": 1177,
    "preview": "import ua from '../../analytics';\n\nimport { Database } from './db';\n\nclass Analytics {\n  constructor() {\n    if (this.ua"
  },
  {
    "path": "app/core/base-script.js",
    "chars": 1107,
    "preview": "/* global window */\n/* eslint class-methods-use-this: \"off\" */\nimport { Settings } from './settings';\nimport { Database "
  },
  {
    "path": "app/core/browser.js",
    "chars": 503,
    "preview": "/* globals\nwindow document Blob\n*/\n\nexport default {\n  downloadFile(filename, data) {\n    const blob = new Blob([data], "
  },
  {
    "path": "app/core/db.js",
    "chars": 542,
    "preview": "/* eslint valid-typeof: \"error\" */\n\nexport class Database {\n  constructor() {\n    this.set('database-version', '1');\n  }"
  },
  {
    "path": "app/core/index.js",
    "chars": 370,
    "preview": "import { Settings } from './settings';\nimport { SettingsEntry } from './settings-entry';\nimport { BaseScript } from './b"
  },
  {
    "path": "app/core/queue.js",
    "chars": 746,
    "preview": "import { utils } from '../../fut';\n\nexport class Queue {\n  constructor() {\n    this._queue = [];\n  }\n\n  static getInstan"
  },
  {
    "path": "app/core/settings-entry.js",
    "chars": 1588,
    "preview": "import { Database } from './db';\n\nexport class SettingsEntry {\n  constructor(id, name) {\n    const settings = Database.g"
  },
  {
    "path": "app/core/settings.js",
    "chars": 1025,
    "preview": "import EventEmitter from 'event-emitter-es6';\n\nimport analytics from './analytics';\n\nexport class Settings extends Event"
  },
  {
    "path": "app/futbin/futbin-player-links.js",
    "chars": 5344,
    "preview": "/* globals\nwindow $ document\n*/\nimport { analytics, BaseScript, Database } from '../core';\nimport { FutbinSettings } fro"
  },
  {
    "path": "app/futbin/futbin-prices.js",
    "chars": 13489,
    "preview": "/* globals\n$\nwindow\n*/\n\nimport { utils } from '../../fut';\nimport { BaseScript, Database } from '../core';\nimport { Futb"
  },
  {
    "path": "app/futbin/index.js",
    "chars": 333,
    "preview": "import './style/futbin-prices.scss';\n\nimport { FutbinPrices } from './futbin-prices';\nimport { FutbinPlayerLinks } from "
  },
  {
    "path": "app/futbin/settings-entry.js",
    "chars": 441,
    "preview": "import { SettingsEntry } from '../core';\n\nexport class FutbinSettings extends SettingsEntry {\n  static id = 'futbin';\n  "
  },
  {
    "path": "app/futbin/style/futbin-prices.scss",
    "chars": 2376,
    "preview": "#TradePile .player-stats-data-component, #Unassigned .player-stats-data-component {\n  width: 12em;\n}\n#TradePile .listFUT"
  },
  {
    "path": "app/index.js",
    "chars": 2074,
    "preview": "/* globals onVisibilityChanged services UTGameFlowNavigationController\nUTViewController EAObservable window document $ *"
  },
  {
    "path": "app/index.scss",
    "chars": 757,
    "preview": ".ut-content-container {\n    padding: 0;\n\n    .ut-content {\n        border: 0;\n\n        &.ut-content--split-view-extend {"
  },
  {
    "path": "app/instant-bin-confirm/index.js",
    "chars": 219,
    "preview": "import { InstantBinConfirmSettings } from './settings-entry';\nimport { InstantBinConfirm } from './instant-bin-confirm';"
  },
  {
    "path": "app/instant-bin-confirm/instant-bin-confirm.js",
    "chars": 1622,
    "preview": "/* global\ngPopupClickShield\nenums\nEADialogViewController\nservices\nutils\n*/\n\nimport { BaseScript } from '../core';\nimport"
  },
  {
    "path": "app/instant-bin-confirm/settings-entry.js",
    "chars": 239,
    "preview": "import { SettingsEntry } from '../core';\n\nexport class InstantBinConfirmSettings extends SettingsEntry {\n  static id = '"
  },
  {
    "path": "app/settings/html/index/settings.html",
    "chars": 9161,
    "preview": "<button class=\"futsettings-toggle standard mini call-to-action\">\n  <span class=\"btn-text\"></span>\n</button>\n\n<div class="
  },
  {
    "path": "app/settings/index.js",
    "chars": 3595,
    "preview": "/* globals $ */\n/* eslint-disable no-restricted-syntax */\n\nimport './index.scss';\nimport { analytics } from '../core';\ni"
  },
  {
    "path": "app/settings/index.scss",
    "chars": 2699,
    "preview": ".futsettings-toggle {\n  position: absolute !important;\n  bottom: 20px;\n  right: 20px;\n  z-index: 999;\n\n  ::before {\n    "
  },
  {
    "path": "app/transferlist/card-info.js",
    "chars": 2340,
    "preview": "/* globals\nwindow $ document */\n\nimport { BaseScript, SettingsEntry } from '../core';\n\nimport './style/card-info.scss';\n"
  },
  {
    "path": "app/transferlist/index.js",
    "chars": 374,
    "preview": "import { RefreshListSettings } from './refresh-list';\nimport { MinBinSettings } from './min-bin';\nimport { CardInfoSetti"
  },
  {
    "path": "app/transferlist/list-size.js",
    "chars": 1679,
    "preview": "/* globals\ngConfigurationModel models\n*/\n\nimport { BaseScript, SettingsEntry } from '../core';\n\nexport class ListSizeSet"
  },
  {
    "path": "app/transferlist/min-bin.js",
    "chars": 6299,
    "preview": "/* globals\nwindow $ document\n*/\nimport { analytics, BaseScript, SettingsEntry } from '../core';\nimport { TransferMarket,"
  },
  {
    "path": "app/transferlist/refresh-list.js",
    "chars": 1741,
    "preview": "/* globals $ */\n\nimport { BaseScript, SettingsEntry } from '../core';\nimport './style/refresh-list.scss';\n\nexport class "
  },
  {
    "path": "app/transferlist/style/card-info.scss",
    "chars": 316,
    "preview": ".item.player.small.TOTW .infoTab-extra, \n.item.player.small.OTW .infoTab-extra, \n.item.player.small.TOTS .infoTab-extra,"
  },
  {
    "path": "app/transferlist/style/refresh-list.scss",
    "chars": 132,
    "preview": "button.flat.pagination.refresh{\n    &:before {\n        font-family: UltimateTeam-Icons,sans-serif;\n        content: '\\E0"
  },
  {
    "path": "app/transferlist/style/transfer-totals.scss",
    "chars": 1169,
    "preview": ".transfer-totals {\n    background-color: #183f94;\n    color: #fff;\n    .auction {\n        float: right;\n        margin: "
  },
  {
    "path": "app/transferlist/transfer-totals.js",
    "chars": 4500,
    "preview": "/* globals\nwindow $ document */\n\nimport { BaseScript, SettingsEntry } from '../core';\n\nimport './style/transfer-totals.s"
  },
  {
    "path": "fut/club.js",
    "chars": 1048,
    "preview": "/* globals\ntransferobjects enums communication factories\n*/\n\nexport class Club {\n  async getPlayers(start, count) {\n    "
  },
  {
    "path": "fut/errors/index.js",
    "chars": 46,
    "preview": "export class ListPlayerError extends Error {}\n"
  },
  {
    "path": "fut/index.js",
    "chars": 350,
    "preview": "import { Logger } from './logger';\nimport { PinEvent } from './pinEvent';\nimport { Store } from './store';\nimport { Tran"
  },
  {
    "path": "fut/logger.js",
    "chars": 450,
    "preview": "export class Logger {\n  constructor() {\n    this._storeName = 'logger';\n  }\n\n  log(message, category = 'FUT') {\n    /* e"
  },
  {
    "path": "fut/pinEvent.js",
    "chars": 362,
    "preview": "/* globals PIN_PAGEVIEW_EVT_TYPE services PINEventType */\n\nexport class PinEvent {\n  static sendPageView(pageId, delay ="
  },
  {
    "path": "fut/priceTiers.js",
    "chars": 1054,
    "preview": "/* global utils UTCurrencyInputControl */\nexport default {\n  roundValueToNearestPriceTiers(value) {\n    const tier = uti"
  },
  {
    "path": "fut/store.js",
    "chars": 2440,
    "preview": "/* global communication repositories enums services */\n\nexport class Store {\n  getUnassignedItems() {\n    return new Pro"
  },
  {
    "path": "fut/transferMarket.js",
    "chars": 8385,
    "preview": "/* globals\nenums factories communication gUserModel models repositories services\n*/\nimport { mean } from 'math-statistic"
  },
  {
    "path": "fut/utils.js",
    "chars": 796,
    "preview": "/* globals\nservices\n*/\n\nexport default {\n  /**\n   * Sleep for a while\n   *\n   * @param {number} min minimum sleep time i"
  },
  {
    "path": "package.json",
    "chars": 1997,
    "preview": "{\n  \"name\": \"futwebapp-single\",\n  \"version\": \"0.0.0-development\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\""
  },
  {
    "path": "tampermonkey-headers.js",
    "chars": 1049,
    "preview": "// ==UserScript==\n// @name        FUT Enhancer\n// @version     VERSION\n// @description Enhances the FIFA Ultimate Team 2"
  },
  {
    "path": "webpack.config.js",
    "chars": 1225,
    "preview": "const webpack = require('webpack');\n\nmodule.exports = {\n  entry: './app/index.js',\n  output: {\n    filename: './dist/fut"
  },
  {
    "path": "webpack.config.prd.js",
    "chars": 659,
    "preview": "var fs = require(\"fs\");\n\nconst webpack = require('webpack');\nconst config = require('./webpack.config');\n\nmodule.exports"
  }
]

About this extraction

This page contains the full source code of the Mardaneus86/futwebapp-tampermonkey GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 56 files (113.3 KB), approximately 34.2k tokens, and a symbol index with 136 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!