[
  {
    "path": ".editorconfig",
    "content": "# top-most EditorConfig file\nroot = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\n\n[*.js]\nindent_style = space\nindent_size = 2\n"
  },
  {
    "path": ".eslintignore",
    "content": "dist/\ntampermonkey-headers.js\nwebpack.config.js\nwebpack.config.prd.js\n"
  },
  {
    "path": ".eslintrc",
    "content": "{\n  \"extends\": \"airbnb\",\n  \"parser\": \"babel-eslint\",\n  \"rules\": {\n    \"func-names\": [\"error\", \"never\"],\n    \"import/prefer-default-export\": \"off\",\n    \"no-underscore-dangle\": \"off\"\n  },\n  \"globals\": {\n    \"UA_TOKEN\": false,\n    \"getAppMain\": false,\n    \"GM_notification\": false\n  },\n  \"env\": {\n    \"greasemonkey\": true\n  }\n}\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: Mardaneus86\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: [\"https://www.paypal.me/timklingeleers\"]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\nREAD BEFORE YOU ADD AN ISSUE: Do not request autobuyer features, because they are considered as cheating. Issues regarding autobuyers will be closed immediately.\n\n### Expected behavior:\nGive a detailed explanation of the expected behavior.\n\n### Current behavior:\nGive a detailed explanation of the current behavior.\n\n### Metadata:\n- **Script version:**\n- **Browser:**\n- **OS:**\n\n### To Reproduce\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n### Screenshots\nIf applicable, add screenshots to help explain your problem.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\nREAD BEFORE YOU ADD A FEATURE REQUEST: Do not request autobuyer features, because they are considered as cheating. Issues regarding autobuyers will be closed immediately.\n\n### Describe the solution you'd like\nA clear and concise description of what you want to happen.\n\n### Suggestions for implementation\nAdd any other context, mockups or screenshots about the feature request here.\n"
  },
  {
    "path": ".gitignore",
    "content": "dist/\nnode_modules/\n.vscode/\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\ncache:\n  directories:\n  - ~/.npm\n  - ~/node_modules\nnotifications:\n  email: false\nnode_js:\n  - '8'\nbefore_script:\n  - npm prune\n  - git fetch --tags\n  - export TM_VERSION=$(npm run semantic-release:dry | grep \"The next release version is \" | sed 's/[^0-9.]*\\([0-9.]*\\).*/\\1/')\nscript:\n  - npm run build:production\nenv:\n  - NPM_TOKEN=\"00000000-0000-0000-0000-000000000000\"\nbranches:\n  except:\n    - /^v\\d+\\.\\d+\\.\\d+$/\nafter_success:\n  # CREATE GIT TAG\n  - git config --global user.email \"builds@travis-ci.com\"\n  - git config --global user.name \"Travis CI\"\n  - '[[ $TRAVIS_BRANCH == \"master\" ]] && git tag $TM_VERSION -a -m \"Generated tag from TravisCI build $TRAVIS_BUILD_NUMBER\"'\n  - '[[ $TRAVIS_BRANCH == \"master\" ]] && git push --quiet https://$GITHUBKEY@github.com/Mardaneus86/futwebapp-tampermonkey $GIT_TAG > /dev/null 2>&1'\ndeploy:\n  provider: releases\n  api_key:\n    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=\"\n  file:\n    - dist/fut-enhancer.user.js\n  skip_cleanup: true\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 Tim Klingeleers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# FUT Web App - TamperMonkey scripts\n\n[![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)\n\n## :warning: Discontinued\n**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.**\n\n**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.**\n\n**This project has been a great journey for me, and I'm very thankful for all the support of the community over the years.**\n\nFIFA 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.\n\nThis 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.\n\n:warning: Using this script is at your own risk. EA might (temp-)ban you for altering parts of their Web App.\n\n:bangbang: Do not request autobuyer features. Because they are considered to be cheating, it will not be added.\n\nI started this project to learn about reverse engineering big Javascript codebases.\n\nIf you benefit from this project, you can buy me a beer :beers: :+1:\n\n[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.me/timklingeleers)\n\n## Features\n- [x] Futbin integration\n  - [x] Show Futbin prices on all player cards throughout the app\n  - [x] Show link to player on Futbin\n  - [x] Mark bargains (BIN price lower then Futbin value)\n- [x] Find minimum BIN value of players\n- [x] Refresh transfer list\n- [x] Increase transfer list size\n- [x] Extra card information (contracts)\n- [x] Total coin value for cards on the transfer list\n\n## Installation\nMake sure you have user scripts enabled in your browser (these instructions refer to the latest versions of the browser):\n\n* Firefox - install [Tampermonkey](https://tampermonkey.net/?ext=dhdg&browser=firefox). :warning: Has issues loading properly (see issue #115)\n* Chrome - install [Tampermonkey](https://tampermonkey.net/?ext=dhdg&browser=chrome).\n* Opera - install [Tampermonkey](https://tampermonkey.net/?ext=dhdg&browser=opera).\n* Safari - install [Tampermonkey](https://tampermonkey.net/?ext=dhdg&browser=safari).\n* Dolphin - install [Tampermonkey](https://tampermonkey.net/?ext=dhdg&browser=dolphin).\n* UC Browser - install [Tampermonkey](https://tampermonkey.net/?ext=dhdg&browser=ucweb).\n\n### Install scripts\nInstall 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).\n\n## Feature requests\nIf 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.\n\n## Issues\nFile a bug report in the [issue list][issue-list].\n\n## Developing\nClone this repository and execute:\n```\nnpm install\n```\n\nTo start the bundling process and linting process, execute:\n```\nnpm start\n```\n\nMake sure to enable `Allow access to file URLs` in `chrome://extensions/` for Tampermonkey, and add the following script snippet:\n```\n// ==UserScript==\n// @name        FUT Enhancer dev\n// @version     0.1\n// @description\n// @license     MIT\n// @author      Tim Klingeleers\n// @match       https://www.easports.com/fifa/ultimate-team/web-app/*\n// @match       https://www.easports.com/*/fifa/ultimate-team/web-app/*\n// @match       https://www.ea.com/fifa/ultimate-team/web-app/*\n// @match       https://www.ea.com/*/fifa/ultimate-team/web-app/*\n// @namespace   https://github.com/Mardaneus86\n// @supportURL  https://github.com/Mardaneus86/futwebapp-tampermonkey/issues\n// @grant       GM_notification\n// @grant       GM_xmlhttpRequest\n// @grant       GM_getValue\n// @grant       GM_setValue\n// @grant       window.focus\n// @require     file:///<path to repo>/dist/fut-enhancer.user.js\n// @connect     ea.com\n// @connect     futbin.com\n// ==/UserScript==\n```\n\nRemember 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.\n\n## Contribute\nAdd 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.\n\n[issue-list]: https://github.com/Mardaneus86/futwebapp-tampermonkey/issues\n[install-script]: https://openuserjs.org/install/Mardaneus86/FUT_Enhancer.user.js\n"
  },
  {
    "path": "analytics/LICENSE",
    "content": "(The MIT License)\n\nCopyright (c) 2017 Peaks & Pies GmbH <hello@peaksandpies.com>;\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "analytics/config.js",
    "content": "/* eslint-disable */\nmodule.exports = {\n\tprotocolVersion: \"1\",\n\thostname: \"https://www.google-analytics.com\",\n\tpath: \"/collect\",\n\tbatchPath: \"/batch\",\n\tbatching: true,\n\tbatchSize: 10,\n\tacceptedParameters: [\n\n\t\t// General\n\t\t\"v\", \"tid\", \"aip\", \"ds\", \"qt\", \"z\",\n\n\t\t// User\n\t\t\"cid\", \"uid\",\n\n\t\t// Session\n\t\t\"sc\", \"uip\", \"ua\", \"geoid\",\n\n\t\t// Traffic Sources\n\t\t\"dr\", \"cn\", \"cs\", \"cm\", \"ck\", \"cc\", \"ci\", \"gclid\", \"dclid\",\n\n\t\t// System Info\n\t\t\"sr\", \"vp\", \"de\", \"sd\", \"ul\", \"je\", \"fl\",\n\n\t\t// Hit\n\t\t\"t\", \"ni\",\n\n\t\t// Content Information\n\t\t\"dl\", \"dh\", \"dp\", \"dt\", \"cd\", \"linkid\",\n\n\t\t// App Tracking\n\t\t\"an\", \"aid\", \"av\", \"aiid\",\n\n\t\t// Event Tracking\n\t\t\"ec\", \"ea\", \"el\", \"ev\",\n\n\t\t// E-commerce (transaction data: simple and enhanced)\n\t\t\"ti\", \"ta\", \"tr\", \"ts\", \"tt\",\n\n\t\t// E-commerce (item data: simple)\n\t\t\"in\", \"ip\", \"iq\", \"ic\", \"iv\",\n\n\t\t// E-commerce (currency: simple and enhanced)\n\t\t\"cu\",\n\n\t\t// Enhanced E-Commerce (see also: regex below)\n\t\t\"pa\", \"tcc\", \"pal\", \"cos\", \"col\", \"promoa\",\n\n\t\t// Social Interactions\n\t\t\"sn\", \"sa\", \"st\",\n\n\t\t// Timing\n\t\t\"utc\", \"utv\", \"utt\", \"utl\", \"plt\", \"dns\", \"pdt\", \"rrt\", \"tcp\", \"srt\", \"dit\", \"clt\",\n\n\t\t// Exceptions\n\t\t\"exd\", \"exf\",\n\n\t\t// Content Experiments\n\t\t\"xid\", \"xvar\"],\n\n\tacceptedParametersRegex: [\n\t\t/^cm[0-9]+$/,\n\t\t/^cd[0-9]+$/,\n\t\t/^cg(10|[0-9])$/,\n\n\t\t/pr[0-9]{1,3}id/,\n\t\t/pr[0-9]{1,3}nm/,\n\t\t/pr[0-9]{1,3}br/,\n\t\t/pr[0-9]{1,3}ca/,\n\t\t/pr[0-9]{1,3}va/,\n\t\t/pr[0-9]{1,3}pr/,\n\t\t/pr[0-9]{1,3}qt/,\n\t\t/pr[0-9]{1,3}cc/,\n\t\t/pr[0-9]{1,3}ps/,\n\t\t/pr[0-9]{1,3}cd[0-9]{1,3}/,\n\t\t/pr[0-9]{1,3}cm[0-9]{1,3}/,\n\n\t\t/il[0-9]{1,3}nm/,\n\t\t/il[0-9]{1,3}pi[0-9]{1,3}id/,\n\t\t/il[0-9]{1,3}pi[0-9]{1,3}nm/,\n\t\t/il[0-9]{1,3}pi[0-9]{1,3}br/,\n\t\t/il[0-9]{1,3}pi[0-9]{1,3}ca/,\n\t\t/il[0-9]{1,3}pi[0-9]{1,3}va/,\n\t\t/il[0-9]{1,3}pi[0-9]{1,3}ps/,\n\t\t/il[0-9]{1,3}pi[0-9]{1,3}pr/,\n\t\t/il[0-9]{1,3}pi[0-9]{1,3}cd[0-9]{1,3}/,\n\t\t/il[0-9]{1,3}pi[0-9]{1,3}cm[0-9]{1,3}/,\n\n\t\t/promo[0-9]{1,3}id/,\n\t\t/promo[0-9]{1,3}nm/,\n\t\t/promo[0-9]{1,3}cr/,\n\t\t/promo[0-9]{1,3}ps/\n\t],\n\tparametersMap: {\n\t\t\"protocolVersion\": \"v\",\n\t\t\"trackingId\": \"tid\",\n\t\t\"webPropertyId\": \"tid\",\n\t\t\"anonymizeIp\": \"aip\",\n\t\t\"dataSource\": \"ds\",\n\t\t\"queueTime\": \"qt\",\n\t\t\"cacheBuster\": \"z\",\n\t\t\"clientId\": \"cid\",\n\t\t\"userId\": \"uid\",\n\t\t\"sessionControl\": \"sc\",\n\t\t\"ipOverride\": \"uip\",\n\t\t\"userAgentOverride\": \"ua\",\n\t\t\"documentReferrer\": \"dr\",\n\t\t\"campaignName\": \"cn\",\n\t\t\"campaignSource\": \"cs\",\n\t\t\"campaignMedium\": \"cm\",\n\t\t\"campaignKeyword\": \"ck\",\n\t\t\"campaignContent\": \"cc\",\n\t\t\"campaignId\": \"ci\",\n\t\t\"googleAdwordsId\": \"gclid\",\n\t\t\"googleDisplayAdsId\": \"dclid\",\n\t\t\"screenResolution\": \"sr\",\n\t\t\"viewportSize\": \"vp\",\n\t\t\"documentEncoding\": \"de\",\n\t\t\"screenColors\": \"sd\",\n\t\t\"userLanguage\": \"ul\",\n\t\t\"javaEnabled\": \"je\",\n\t\t\"flashVersion\": \"fl\",\n\t\t\"hitType\": \"t\",\n\t\t\"non-interactionHit\": \"ni\",\n\t\t\"documentLocationUrl\": \"dl\",\n\t\t\"documentHostName\": \"dh\",\n\t\t\"documentPath\": \"dp\",\n\t\t\"documentTitle\": \"dt\",\n\t\t\"screenName\": \"cd\",\n\t\t\"linkId\": \"linkid\",\n\t\t\"applicationName\": \"an\",\n\t\t\"applicationId\": \"aid\",\n\t\t\"applicationVersion\": \"av\",\n\t\t\"applicationInstallerId\": \"aiid\",\n\t\t\"eventCategory\": \"ec\",\n\t\t\"eventAction\": \"ea\",\n\t\t\"eventLabel\": \"el\",\n\t\t\"eventValue\": \"ev\",\n\t\t\"transactionId\": \"ti\",\n\t\t\"transactionAffiliation\": \"ta\",\n\t\t\"transactionRevenue\": \"tr\",\n\t\t\"transactionShipping\": \"ts\",\n\t\t\"transactionTax\": \"tt\",\n\t\t\"itemName\": \"in\",\n\t\t\"itemPrice\": \"ip\",\n\t\t\"itemQuantity\": \"iq\",\n\t\t\"itemCode\": \"ic\",\n\t\t\"itemCategory\": \"iv\",\n\t\t\"currencyCode\": \"cu\",\n\t\t\"socialNetwork\": \"sn\",\n\t\t\"socialAction\": \"sa\",\n\t\t\"socialActionTarget\": \"st\",\n\t\t\"userTimingCategory\": \"utc\",\n\t\t\"userTimingVariableName\": \"utv\",\n\t\t\"userTimingTime\": \"utt\",\n\t\t\"userTimingLabel\": \"utl\",\n\t\t\"pageLoadTime\": \"plt\",\n\t\t\"dnsTime\": \"dns\",\n\t\t\"pageDownloadTime\": \"pdt\",\n\t\t\"redirectResponseTime\": \"rrt\",\n\t\t\"tcpConnectTime\": \"tcp\",\n\t\t\"serverResponseTime\": \"srt\",\n\t\t\"domInteractiveTime\": \"dit\",\n\t\t\"contentLoadTime\": \"clt\",\n\t\t\"exceptionDescription\": \"exd\",\n\t\t\"isExceptionFatal\": \"exf\",\n\t\t\"isExceptionFatal?\": \"exf\",\n\t\t\"experimentId\": \"xid\",\n\t\t\"experimentVariant\": \"xvar\"\n\t}\n};\n"
  },
  {
    "path": "analytics/index.js",
    "content": "/* eslint-disable */\nimport querystring from 'querystring';\n\nimport config from './config';\n\nmodule.exports = init;\n\nfunction init (tid, cid, options) {\n  return new Visitor(tid, cid, options);\n}\n\nvar Visitor = module.exports.Visitor = function (tid, cid, options) {\n\n  this._queue = [];\n\n  this.options = options || {};\n\n  if(this.options.hostname) {\n    config.hostname = this.options.hostname;\n  }\n  if(this.options.path) {\n    config.path = this.options.path;\n  }\n\n  if(this.options.enableBatching !== undefined) {\n    config.batching = options.enableBatching;\n  }\n\n  if(this.options.batchSize) {\n    config.batchSize = this.options.batchSize;\n  }\n\n  this._context = {};\n  this._persistentParams = {};\n\n  this.tid = this.options.tid;\n  this.cid = this.options.cid;\n  if(this.options.uid) {\n    this.uid = this.options.uid;\n  }\n}\n\nVisitor.prototype = {\n  reset: function () {\n    this._context = null;\n    return this;\n  },\n\n  set: function (key, value) {\n    this._persistentParams = this._persistentParams || {};\n    this._persistentParams[key] = value;\n  },\n\n  pageview: function (path, hostname, title, params, fn) {\n\n    if (typeof path === 'object' && path != null) {\n      params = path;\n      if (typeof hostname === 'function') {\n        fn = hostname\n      }\n      path = hostname = title  = null;\n    } else if (typeof hostname === 'function') {\n      fn = hostname\n      hostname = title = null;\n    } else if (typeof title === 'function') {\n      fn = title;\n      title = null;\n    } else if (typeof params === 'function') {\n      fn = params;\n      params = null;\n    }\n\n    params = this._translateParams(params);\n\n    params = Object.assign({}, this._persistentParams || {}, params);\n\n    params.dp = path || params.dp || this._context.dp;\n    params.dh = hostname || params.dh || this._context.dh;\n    params.dt = title || params.dt || this._context.dt;\n\n    this._tidyParameters(params);\n\n    if (!params.dp && !params.dl) {\n      return this._handleError('Please provide either a page path (dp) or a document location (dl)', fn);\n    }\n\n    return this._withContext(params)._enqueue('pageview', params, fn);\n  },\n\n\n  screenview: function (screenName, appName, appVersion, appId, appInstallerId, params, fn) {\n\n      if (typeof screenName === 'object' && screenName != null) {\n          params = screenName;\n          if (typeof appName === 'function') {\n              fn = appName\n          }\n          screenName = appName = appVersion = appId = appInstallerId = null;\n      } else if (typeof appName === 'function') {\n          fn = appName\n          appName = appVersion = appId = appInstallerId = null;\n      } else if (typeof appVersion === 'function') {\n          fn = appVersion;\n          appVersion = appId = appInstallerId = null;\n      } else if (typeof appId === 'function') {\n          fn = appId;\n          appId = appInstallerId = null;\n      } else if (typeof appInstallerId === 'function') {\n          fn = appInstallerId;\n          appInstallerId = null;\n      } else if (typeof params === 'function') {\n          fn = params;\n          params = null;\n      }\n\n      params = this._translateParams(params);\n\n      params = Object.assign({}, this._persistentParams || {}, params);\n\n      params.cd = screenName || params.cd || this._context.cd;\n      params.an = appName || params.an || this._context.an;\n      params.av = appVersion || params.av || this._context.av;\n      params.aid = appId || params.aid || this._context.aid;\n      params.aiid = appInstallerId || params.aiid || this._context.aiid;\n\n      this._tidyParameters(params);\n\n      if (!params.cd || !params.an) {\n          return this._handleError('Please provide at least a screen name (cd) and an app name (an)', fn);\n      }\n\n      return this._withContext(params)._enqueue('screenview', params, fn);\n  },\n\n\n  event: function (category, action, label, value, params, fn) {\n\n    if (typeof category === 'object' && category != null) {\n      params = category;\n      if (typeof action === 'function') {\n        fn = action\n      }\n      category = action = label = value = null;\n    } else if (typeof label === 'function') {\n      fn = label;\n      label = value = null;\n    } else if (typeof value === 'function') {\n      fn = value;\n      value = null;\n    } else if (typeof params === 'function') {\n      fn = params;\n      params = null;\n    }\n\n    params = this._translateParams(params);\n\n    params = Object.assign({}, this._persistentParams || {}, params);\n\n    params.ec = category || params.ec || this._context.ec;\n    params.ea = action || params.ea || this._context.ea;\n    params.el = label || params.el || this._context.el;\n    params.ev = value || params.ev || this._context.ev;\n    params.p = params.p || params.dp || this._context.p || this._context.dp;\n\n    delete params.dp;\n    this._tidyParameters(params);\n\n    if (!params.ec || !params.ea) {\n      return this._handleError('Please provide at least an event category (ec) and an event action (ea)', fn);\n    }\n\n    return this._withContext(params)._enqueue('event', params, fn);\n  },\n\n\n  transaction: function (transaction, revenue, shipping, tax, affiliation, params, fn) {\n    if (typeof transaction === 'object') {\n      params = transaction;\n      if (typeof revenue === 'function') {\n        fn = revenue\n      }\n      transaction = revenue = shipping = tax = affiliation = null;\n    } else if (typeof revenue === 'function') {\n      fn = revenue;\n      revenue = shipping = tax = affiliation = null;\n    } else if (typeof shipping === 'function') {\n      fn = shipping;\n      shipping = tax = affiliation = null;\n    } else if (typeof tax === 'function') {\n      fn = tax;\n      tax = affiliation = null;\n    } else if (typeof affiliation === 'function') {\n      fn = affiliation;\n      affiliation = null;\n    } else if (typeof params === 'function') {\n      fn = params;\n      params = null;\n    }\n\n    params = this._translateParams(params);\n\n    params = Object.assign({}, this._persistentParams || {}, params);\n\n    params.ti = transaction || params.ti || this._context.ti;\n    params.tr = revenue || params.tr || this._context.tr;\n    params.ts = shipping || params.ts || this._context.ts;\n    params.tt = tax || params.tt || this._context.tt;\n    params.ta = affiliation || params.ta || this._context.ta;\n    params.p = params.p || this._context.p || this._context.dp;\n\n    this._tidyParameters(params);\n\n    if (!params.ti) {\n      return this._handleError('Please provide at least a transaction ID (ti)', fn);\n    }\n\n    return this._withContext(params)._enqueue('transaction', params, fn);\n  },\n\n\n  item: function (price, quantity, sku, name, variation, params, fn) {\n    if (typeof price === 'object') {\n      params = price;\n      if (typeof quantity === 'function') {\n        fn = quantity\n      }\n      price = quantity = sku = name = variation = null;\n    } else if (typeof quantity === 'function') {\n      fn = quantity;\n      quantity = sku = name = variation = null;\n    } else if (typeof sku === 'function') {\n      fn = sku;\n      sku = name = variation = null;\n    } else if (typeof name === 'function') {\n      fn = name;\n      name = variation = null;\n    } else if (typeof variation === 'function') {\n      fn = variation;\n      variation = null;\n    } else if (typeof params === 'function') {\n      fn = params;\n      params = null;\n    }\n\n    params = this._translateParams(params);\n\n    params = Object.assign({}, this._persistentParams || {}, params);\n\n    params.ip = price || params.ip || this._context.ip;\n    params.iq = quantity || params.iq || this._context.iq;\n    params.ic = sku || params.ic || this._context.ic;\n    params.in = name || params.in || this._context.in;\n    params.iv = variation || params.iv || this._context.iv;\n    params.p = params.p || this._context.p || this._context.dp;\n    params.ti = params.ti || this._context.ti;\n\n    this._tidyParameters(params);\n\n    if (!params.ti) {\n      return this._handleError('Please provide at least an item transaction ID (ti)', fn);\n    }\n\n    return this._withContext(params)._enqueue('item', params, fn);\n\n  },\n\n  exception: function (description, fatal, params, fn) {\n\n    if (typeof description === 'object') {\n      params = description;\n      if (typeof fatal === 'function') {\n        fn = fatal;\n      }\n      description = fatal = null;\n    } else if (typeof fatal === 'function') {\n      fn = fatal;\n      fatal = 0;\n    } else if (typeof params === 'function') {\n      fn = params;\n      params = null;\n    }\n\n    params = this._translateParams(params);\n\n    params = Object.assign({}, this._persistentParams || {}, params);\n\n    params.exd = description || params.exd || this._context.exd;\n    params.exf = +!!(fatal || params.exf || this._context.exf);\n\n    if (params.exf === 0) {\n      delete params.exf;\n    }\n\n    this._tidyParameters(params);\n\n    return this._withContext(params)._enqueue('exception', params, fn);\n  },\n\n  timing: function (category, variable, time, label, params, fn) {\n\n    if (typeof category === 'object') {\n      params = category;\n      if (typeof variable === 'function') {\n        fn = variable;\n      }\n      category = variable = time = label = null;\n    } else if (typeof variable === 'function') {\n      fn = variable;\n      variable = time = label = null;\n    } else if (typeof time === 'function') {\n      fn = time;\n      time = label = null;\n    } else if (typeof label === 'function') {\n      fn = label;\n      label = null;\n    } else if (typeof params === 'function') {\n      fn = params;\n      params = null;\n    }\n\n    params = this._translateParams(params);\n\n    params = Object.assign({}, this._persistentParams || {}, params);\n\n    params.utc = category || params.utc || this._context.utc;\n    params.utv = variable || params.utv || this._context.utv;\n    params.utt = time || params.utt || this._context.utt;\n    params.utl = label || params.utl || this._context.utl;\n\n    this._tidyParameters(params);\n\n    return this._withContext(params)._enqueue('timing', params, fn);\n  },\n\n\n  send: function (fn) {\n    var self = this;\n    var count = 1;\n    var fn = fn || function () {};\n\n    var getBody = function(params) {\n      return params.map(function(x) { return querystring.stringify(x); }).join('\\n');\n    }\n\n    var onFinish = function (err) {\n      fn.call(self, err || null, count - 1);\n    }\n\n    var iterator = function () {\n      if (!self._queue.length) {\n        return onFinish(null);\n      }\n      var params = [];\n\n      if(config.batching) {\n        params = self._queue.splice(0, Math.min(self._queue.length, config.batchSize));\n      } else {\n        params.push(self._queue.shift());\n      }\n\n      var useBatchPath = params.length > 1;\n\n      var path = config.hostname + (useBatchPath ? config.batchPath :config.path);\n\n      var options = Object.assign({}, self.options.requestOptions, {\n        body: getBody(params),\n        headers: self.options.headers || {}\n      });\n\n      GM_xmlhttpRequest({\n        method: 'POST',\n        url: path,\n        headers: options.headers,\n        data: options.body,\n        onload: function () {\n          nextIteration()\n        },\n        onerror: function(res) {\n          nextIteration(res.status);\n        }\n      });\n    }\n\n    function nextIteration(err) {\n      if (err) return onFinish(err);\n      iterator();\n    }\n\n    iterator();\n\n  },\n\n  _enqueue: function (type, params, fn) {\n\n    if (typeof params === 'function') {\n      fn = params;\n      params = {};\n    }\n\n    params = this._translateParams(params) || {};\n\n    Object.assign(params, {\n      v: config.protocolVersion,\n      tid: this.tid,\n      cid: this.cid,\n      t: type\n    });\n    if(this.uid) {\n      params.uid = this.uid;\n    }\n\n    this._queue.push(params);\n\n    if (fn) {\n      this.send(fn);\n    }\n\n    return this;\n  },\n\n\n  _handleError: function (message, fn) {\n      fn && fn.call(this, new Error(message))\n      return this;\n  },\n\n  _translateParams: function (params) {\n        var translated = {};\n        for (var key in params) {\n            if (config.parametersMap.hasOwnProperty(key)) {\n                translated[config.parametersMap[key]] = params[key];\n            } else {\n                translated[key] = params[key];\n            }\n        }\n        return translated;\n    },\n\n  _tidyParameters: function (params) {\n    for (var param in params) {\n      if (params[param] === null || params[param] === undefined) {\n        delete params[param];\n      }\n    }\n    return params;\n  },\n\n  _withContext: function (context) {\n    var visitor = new Visitor(this.tid, this.cid, this.options, context, this._persistentParams);\n    visitor._queue = this._queue;\n    return visitor;\n  }\n\n\n}\n\nVisitor.prototype.pv = Visitor.prototype.pageview\nVisitor.prototype.e = Visitor.prototype.event\nVisitor.prototype.t = Visitor.prototype.transaction\nVisitor.prototype.i = Visitor.prototype.item\n"
  },
  {
    "path": "app/core/analytics.js",
    "content": "import ua from '../../analytics';\n\nimport { Database } from './db';\n\nclass Analytics {\n  constructor() {\n    if (this.ua === undefined) {\n      let id = Database.get('uuid', '');\n      if (id === '') {\n        id = this._uuidv4();\n        Database.set('uuid', id);\n      }\n\n      this.ua = ua(null, null, {\n        tid: UA_TOKEN,\n        cid: id,\n        uid: id,\n      });\n    }\n  }\n\n  /* eslint-disable */\n  _uuidv4() {\n    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n      var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);\n      return v.toString(16);\n    });\n  }\n  /* eslint-enable */\n\n  trackPage(pageId) {\n    return new Promise((resolve, reject) => {\n      this.ua.pageview(pageId, (err) => {\n        if (err) {\n          reject(err);\n        } else {\n          resolve();\n        }\n      });\n    });\n  }\n\n  trackEvent(category, action, label = null, value = null) {\n    return new Promise((resolve, reject) => {\n      this.ua.event(category, action, label, value, (err) => {\n        if (err) {\n          reject(err);\n        } else {\n          resolve();\n        }\n      });\n    });\n  }\n}\n\nexport default new Analytics();\n"
  },
  {
    "path": "app/core/base-script.js",
    "content": "/* global window */\n/* eslint class-methods-use-this: \"off\" */\nimport { Settings } from './settings';\nimport { Database } from './db';\n\nexport class BaseScript {\n  constructor(id) {\n    this._id = id;\n\n    Settings.getInstance().on('entry-enabled', (entry) => {\n      if (entry.id === id) {\n        this.screenRequestObserver = window.onPageNavigation.observe(\n          this,\n          function (obs, event) {\n            setTimeout(() => {\n              this.onScreenRequest(event);\n            }, 1000);\n          },\n        );\n\n        this.activate({\n          screenId: window.currentPage,\n        });\n      }\n    });\n\n    Settings.getInstance().on('entry-disabled', (entry) => {\n      if (entry.id === id) {\n        this.screenRequestObserver.unobserve(this);\n\n        this.deactivate({\n          screenId: window.currentPage,\n        });\n      }\n    });\n  }\n\n  activate() {\n    // override in subclasses\n  }\n\n  deactivate() {\n    // override in subclasses\n  }\n\n  onScreenRequest() {\n    // override in subclasses\n  }\n\n  getSettings() {\n    return Database.getJson(`settings:${this._id}`, {});\n  }\n}\n"
  },
  {
    "path": "app/core/browser.js",
    "content": "/* globals\nwindow document Blob\n*/\n\nexport default {\n  downloadFile(filename, data) {\n    const blob = new Blob([data], { type: 'text/csv' });\n    if (window.navigator.msSaveOrOpenBlob) {\n      window.navigator.msSaveBlob(blob, filename);\n    } else {\n      const elem = window.document.createElement('a');\n      elem.href = window.URL.createObjectURL(blob);\n      elem.download = filename;\n      document.body.appendChild(elem);\n      elem.click();\n      document.body.removeChild(elem);\n    }\n  },\n};\n"
  },
  {
    "path": "app/core/db.js",
    "content": "/* eslint valid-typeof: \"error\" */\n\nexport class Database {\n  constructor() {\n    this.set('database-version', '1');\n  }\n\n  static set(key, value) {\n    GM_setValue(key, value);\n  }\n\n  static setJson(key, value) {\n    this.set(key, JSON.stringify(value));\n  }\n\n  static get(key, defaultValue) {\n    let value = defaultValue;\n    if (typeof value === 'object') {\n      value = JSON.stringify(value);\n    }\n    return GM_getValue(key, value);\n  }\n\n  static getJson(key, defaultValue) {\n    return JSON.parse(this.get(key, defaultValue));\n  }\n}\n"
  },
  {
    "path": "app/core/index.js",
    "content": "import { Settings } from './settings';\nimport { SettingsEntry } from './settings-entry';\nimport { BaseScript } from './base-script';\nimport { Database } from './db';\nimport { Queue } from './queue';\nimport browser from './browser';\nimport analytics from './analytics';\n\nexport {\n  BaseScript,\n  Database,\n  Queue,\n  Settings,\n  SettingsEntry,\n  browser,\n  analytics,\n};\n"
  },
  {
    "path": "app/core/queue.js",
    "content": "import { utils } from '../../fut';\n\nexport class Queue {\n  constructor() {\n    this._queue = [];\n  }\n\n  static getInstance() {\n    if (this._instance == null) {\n      this._instance = new Queue();\n    }\n\n    return this._instance;\n  }\n\n  add(identifier, cb) {\n    this._queue.push({\n      identifier,\n      cb,\n    });\n  }\n\n  async start() {\n    this._running = true;\n    /* eslint-disable no-await-in-loop */\n    while (this._running) {\n      if (this._queue.length > 0) {\n        const scriptToRun = this._queue.shift();\n        if (scriptToRun) {\n          await scriptToRun.cb();\n        }\n      } else {\n        await utils.sleep(1000);\n      }\n    }\n    /* eslint-enable no-await-in-loop */\n  }\n\n  stop() {\n    this._running = false;\n  }\n}\n"
  },
  {
    "path": "app/core/settings-entry.js",
    "content": "import { Database } from './db';\n\nexport class SettingsEntry {\n  constructor(id, name) {\n    const settings = Database.getJson(`settings:${id}`, {});\n\n    this.id = id;\n    this.name = name;\n    this.isActive = settings.isActive ? settings.isActive : false;\n    this.settings = [];\n  }\n\n  toggle() {\n    this.isActive = !this.isActive;\n\n    const settings = Database.getJson(`settings:${this.id}`, {});\n    settings.isActive = this.isActive;\n    Database.setJson(`settings:${this.id}`, settings);\n  }\n\n  addSetting(label, key, defaultValue, type, cb) {\n    const settings = Database.getJson(`settings:${this.id}`, {});\n\n    settings[key] = key in settings ? settings[key] : defaultValue;\n    Database.setJson(`settings:${this.id}`, settings);\n\n    this.settings.push({\n      label,\n      key,\n      type,\n      value: key in settings ? settings[key] : defaultValue,\n      callback: cb,\n      subsettings: [],\n    });\n  }\n\n  addSettingUnder(underKey, label, key, defaultValue, type, cb) {\n    const settings = Database.getJson(`settings:${this.id}`, {});\n    settings[key] = key in settings ? settings[key] : defaultValue;\n    Database.setJson(`settings:${this.id}`, settings);\n\n    const setting = this.settings.find(s => s.key === underKey);\n    setting.subsettings.push({\n      label,\n      key,\n      type,\n      value: key in settings ? settings[key] : defaultValue,\n      callback: cb,\n    });\n  }\n\n  changeValue(key, value) {\n    const settings = Database.getJson(`settings:${this.id}`, {});\n\n    settings[key] = value;\n\n    Database.setJson(`settings:${this.id}`, settings);\n  }\n}\n"
  },
  {
    "path": "app/core/settings.js",
    "content": "import EventEmitter from 'event-emitter-es6';\n\nimport analytics from './analytics';\n\nexport class Settings extends EventEmitter {\n  constructor() {\n    super();\n    this._entries = [];\n  }\n\n  static getInstance() {\n    if (this._instance == null) {\n      this._instance = new Settings();\n    }\n\n    return this._instance;\n  }\n\n  /**\n   *\n   * @param {SettingsEntry} entry The entry for the settings\n   */\n  registerEntry(entry) {\n    this._entries.push(entry);\n\n    if (entry.isActive) {\n      this._emitEvent(entry);\n    }\n  }\n\n  getEntries() {\n    return this._entries;\n  }\n\n  toggleEntry(id) {\n    const entries = this._entries.filter(e => e.id === id);\n    if (!entries || entries.length === 0) {\n      return;\n    }\n\n    entries[0].toggle();\n\n    analytics.trackEvent('Settings', `Toggle setting ${id}`, entries[0].isActive);\n    this._emitEvent(entries[0]);\n  }\n\n  _emitEvent(entry) {\n    if (entry.isActive) {\n      this.emit('entry-enabled', entry);\n    } else {\n      this.emit('entry-disabled', entry);\n    }\n  }\n}\n"
  },
  {
    "path": "app/futbin/futbin-player-links.js",
    "content": "/* globals\nwindow $ document\n*/\nimport { analytics, BaseScript, Database } from '../core';\nimport { FutbinSettings } from './settings-entry';\n\nexport class FutbinPlayerLinks extends BaseScript {\n  constructor() {\n    super(FutbinSettings.id);\n\n    const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;\n    this._observer = new MutationObserver(this._mutationHandler.bind(this));\n\n    this._playerPrices = [];\n  }\n\n  activate(state) {\n    super.activate(state);\n\n    const obsConfig = {\n      childList: true,\n      characterData: true,\n      attributes: false,\n      subtree: true,\n    };\n\n    setTimeout(() => {\n      this._observer.observe($(document)[0], obsConfig);\n    }, 0);\n  }\n\n  deactivate(state) {\n    super.deactivate(state);\n\n    $('#futbinPlayerLink').remove();\n\n    this._observer.disconnect();\n  }\n\n  _mutationHandler(mutationRecords) {\n    mutationRecords.forEach(function (mutation) {\n      if ($(mutation.target).hasClass('DetailView') && $(mutation.target)\n        .find('.DetailPanel') && mutation.addedNodes.length > 0) {\n        if (this.getSettings()['show-link-to-player'].toString() !== 'true') {\n          return;\n        }\n\n        let selectedItem = this._getSelectedItem();\n        if (selectedItem == null || selectedItem.resourceId === 0) {\n          return;\n        }\n\n        const futbinPlayerLink = $(mutation.target).find('#futbinPlayerLink');\n        futbinPlayerLink.remove();\n\n        $(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>`);\n\n        $('#futbinPlayerLink').bind('click', async () => {\n          let btn = $('#futbinPlayerLink');\n          btn.find('.btn-text').html('Searching on Futbin ...');\n          const futbinLink = await FutbinPlayerLinks._getFutbinPlayerUrl(selectedItem);\n\n          selectedItem = this._getSelectedItem();\n          btn = $('#futbinPlayerLink');\n          if (btn.data('resource-id') === selectedItem.resourceId) {\n            if (futbinLink) {\n              btn.find('.btn-text').html('View on Futbin');\n              analytics.trackEvent('Futbin', 'Show player on Futbin', btn.data('resource-id'));\n              window.open(futbinLink);\n            } else {\n              btn.find('.btn-text').html('No exact Futbin player found');\n            }\n          }\n        });\n      }\n    }, this);\n  }\n\n  static _getFutbinPlayerUrl(item) {\n    return new Promise((resolve) => {\n      if (!item._staticData) {\n        return resolve(null);\n      }\n\n      let futbinPlayerIds = Database.getJson('futbin-player-ids', []);\n      const futbinPlayer = futbinPlayerIds.find(i => i.id === item.resourceId);\n      if (futbinPlayer != null) {\n        return resolve(`https://www.futbin.com/21/player/${futbinPlayer.futbinId}`);\n      }\n\n      const name = `${item._staticData.firstName} ${item._staticData.lastName}`.replace(' ', '+');\n      const url = `https://www.futbin.com/search?year=21&term=${name}`;\n      return GM_xmlhttpRequest({\n        method: 'GET',\n        url,\n        onload: (res) => {\n          if (res.status !== 200) {\n            return resolve(null);\n          }\n          const players = JSON.parse(res.response);\n          let exactPlayers = players.filter(p =>\n            parseInt(p.rating, 10) === parseInt(item.rating, 10));\n          if (exactPlayers.length > 1) {\n            exactPlayers = exactPlayers.filter(p =>\n              p.rare_type === item.rareflag.toString() &&\n              p.club_image.endsWith(`/${item.teamId}.png`));\n          }\n          if (exactPlayers.length === 1) {\n            futbinPlayerIds = Database.getJson('futbin-player-ids', []);\n            if (futbinPlayerIds.find(i => i.id === item.resourceId) == null) {\n              futbinPlayerIds.push({\n                id: item.resourceId,\n                futbinId: exactPlayers[0].id,\n              });\n            }\n            Database.setJson('futbin-player-ids', futbinPlayerIds);\n            return resolve(`https://www.futbin.com/21/player/${exactPlayers[0].id}`);\n          } else if (exactPlayers.length > 1) {\n            // Take first one, several players are returned more than once\n            return resolve(`https://www.futbin.com/21/player/${exactPlayers[0].id}`);\n          }\n\n          return resolve(null); // TODO: what should we do if we find more than one?\n        },\n      });\n    });\n  }\n\n  /* eslint-disable class-methods-use-this */\n  _getSelectedItem() {\n    const listController = getAppMain().getRootViewController()\n      .getPresentedViewController().getCurrentViewController()\n      .getCurrentController()._listController;\n    if (listController) {\n      return listController.getIterator().current();\n    }\n\n    const currentController = getAppMain().getRootViewController()\n      .getPresentedViewController().getCurrentViewController()\n      .getCurrentController()._rightController._currentController;\n    if (currentController && currentController._viewmodel) {\n      const current = currentController._viewmodel.current();\n\n      return current._item ? current._item : current;\n    }\n\n    return null;\n  }\n  /* eslint-enable class-methods-use-this */\n}\n\nnew FutbinPlayerLinks(); // eslint-disable-line no-new\n"
  },
  {
    "path": "app/futbin/futbin-prices.js",
    "content": "/* globals\n$\nwindow\n*/\n\nimport { utils } from '../../fut';\nimport { BaseScript, Database } from '../core';\nimport { FutbinSettings } from './settings-entry';\n\nexport class FutbinPrices extends BaseScript {\n  constructor() {\n    super(FutbinSettings.id);\n    this._squadObserver = null;\n  }\n\n  activate(state) {\n    super.activate(state);\n\n    this._show(state.screenId);\n  }\n\n  onScreenRequest(screenId) {\n    super.onScreenRequest(screenId);\n\n    const controllerName = getAppMain().getRootViewController()\n      .getPresentedViewController().getCurrentViewController()\n      .getCurrentController().className;\n\n    if (screenId === 'SBCSquadSplitViewController' ||\n      screenId === 'SquadSplitViewController' ||\n      screenId === 'UTSquadSplitViewController' ||\n      screenId === 'UTSBCSquadSplitViewController') {\n      if (this.getSettings()['show-sbc-squad'].toString() !== 'true') {\n        return;\n      }\n\n      this._squadObserver = getAppMain().getRootViewController()\n        .getPresentedViewController().getCurrentViewController()\n        .getCurrentController()._leftController._squad.onDataUpdated\n        .observe(this, () => {\n          $('.squadSlotPedestal.futbin').remove(); // forces update\n          this._show('SBCSquadSplitViewController', true);\n        });\n      if ($('.ut-squad-summary-info--right.ut-squad-summary-info').find('.futbin').length === 0) {\n        $('.ut-squad-summary-info--right.ut-squad-summary-info').append(`\n        <div class=\"futbin total\">\n          <span class=\"ut-squad-summary-label\">Total BIN value</span>\n          <div style=\"text-align: right\">\n            <span class=\"ut-squad-summary-value coins value\">---</span>\n          </div>\n        </div>\n        <div class=\"futbin total\">\n          <span class=\"ut-squad-summary-label\">\n            <button class=\"refresh-squad-button\">&nbsp;</button>\n          </span>\n        </div>\n      `);\n\n        $('.refresh-squad-button').click(() => {\n          Database.set('lastFutbinFetchFail', 0);\n          this.onScreenRequest(screenId);\n        });\n      }\n    } else if (this._squadObserver !== null &&\n      controllerName !== 'SBCSquadSplitViewController' &&\n      controllerName !== 'SquadSplitViewController' &&\n      controllerName !== 'UTSquadSplitViewController' &&\n      controllerName !== 'UTSBCSquadSplitViewController') {\n      this._squadObserver.unobserve(this);\n    }\n\n    this._show(screenId);\n  }\n\n  deactivate(state) {\n    super.deactivate(state);\n\n    $('.futbin').remove();\n\n    if (this._squadObserver !== null) {\n      this._squadObserver.unobserve(this);\n    }\n\n    if (this._intervalRunning) {\n      clearInterval(this._intervalRunning);\n    }\n  }\n\n  _show(screen, force = false) {\n    const showFutbinPricePages = [\n      // Players\n      'UTTransferListSplitViewController', // transfer list\n      'UTWatchListSplitViewController', // transfer targets\n      'UTUnassignedItemsSplitViewController', // pack buy\n      'ClubSearchResultsSplitViewController', // club\n      'UTMarketSearchResultsSplitViewController', // market search\n      'UTPlayerPicksViewController',\n      // Squad\n      'SBCSquadSplitViewController',\n      'SquadSplitViewController',\n      'UTSquadSplitViewController',\n      'UTSBCSquadSplitViewController',\n      'UTTOTWSquadSplitViewController',\n    ];\n\n    if (showFutbinPricePages.indexOf(screen) !== -1) {\n      if (this._intervalRunning) {\n        clearInterval(this._intervalRunning);\n      }\n      if (screen === 'SBCSquadSplitViewController' ||\n      screen === 'SquadSplitViewController' ||\n      screen === 'UTSquadSplitViewController' ||\n      screen === 'UTSquadsHubViewController' ||\n      screen === 'UTSBCSquadSplitViewController' ||\n      screen === 'UTTOTWSquadSplitViewController') {\n        this.loadFutbinPrices(showFutbinPricePages, screen, force);\n      } else {\n        this._intervalRunning = setInterval(() => {\n          this.loadFutbinPrices(showFutbinPricePages, screen, force);\n        }, 1000);\n      }\n    } else {\n      // no need to search prices on other pages\n      // reset page\n      if (this._intervalRunning) {\n        clearInterval(this._intervalRunning);\n      }\n      this._intervalRunning = null;\n    }\n  }\n\n  loadFutbinPrices(showFutbinPricePages, screen, force) {\n    const lastFutbinFetchFail = Database.get('lastFutbinFetchFail', 0);\n    if (lastFutbinFetchFail + (5 * 60000) > Date.now()) {\n      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\n      if (this._intervalRunning) {\n        clearInterval(this._intervalRunning);\n      }\n      return;\n    }\n    if (showFutbinPricePages.indexOf(window.currentPage) === -1 && !force) {\n      if (this._intervalRunning) {\n        clearInterval(this._intervalRunning);\n      }\n      return;\n    }\n    const controller = getAppMain().getRootViewController()\n      .getPresentedViewController().getCurrentViewController()\n      .getCurrentController();\n\n    let uiItems = null;\n    if (screen === 'SBCSquadSplitViewController' ||\n      screen === 'SquadSplitViewController' ||\n      screen === 'UTSquadSplitViewController' ||\n      screen === 'UTSquadsHubViewController' ||\n      screen === 'UTSBCSquadSplitViewController' ||\n      screen === 'UTTOTWSquadSplitViewController') {\n      uiItems = $(controller._view.__root).find('.squadSlot');\n\n      if (this.getSettings()['show-sbc-squad'].toString() !== 'true') {\n        return;\n      }\n    } else {\n      uiItems = $(getAppMain().getRootViewController()\n        .getPresentedViewController().getCurrentViewController()\n        ._view.__root).find('.listFUTItem');\n    }\n\n    if ($(uiItems[0]).find('.futbin').length > 0) {\n      return;\n    }\n\n    let listController = null;\n    if (screen === 'SBCSquadSplitViewController' ||\n      screen === 'SquadSplitViewController' ||\n      screen === 'UTSquadSplitViewController' ||\n      screen === 'UTSBCSquadSplitViewController' ||\n      screen === 'UTTOTWSquadSplitViewController') {\n      // not needed\n    } else if (screen === 'UTPlayerPicksViewController') {\n      if (!controller.getPresentedViewController()) {\n        return;\n      }\n      if ($(controller.getPresentedViewController()._view.__root).find('.futbin').length > 0) {\n        // Futbin prices already shown\n        return;\n      }\n      listController = controller.getPresentedViewController();\n    } else if (screen === 'UTUnassignedItemsSplitViewController' || screen === 'UTWatchListSplitViewController') {\n      if (!controller ||\n        !controller._leftController ||\n        !controller._leftController._view) {\n        return;\n      }\n      listController = controller._leftController;\n    } else {\n      if (!controller ||\n        !controller._listController ||\n        !controller._listController._view) {\n        return; // only run if data is available\n      }\n      listController = controller._listController;\n    }\n\n    let listrows = null;\n    if (screen === 'SBCSquadSplitViewController' ||\n      screen === 'SquadSplitViewController' ||\n      screen === 'UTSquadSplitViewController' ||\n      screen === 'UTSBCSquadSplitViewController' ||\n      screen === 'UTTOTWSquadSplitViewController') {\n      listrows = controller._squad._players.slice(0, 11).map((p, index) => (\n        {\n          data: p._item,\n          target: controller._view._lView._slotViews[index].__root,\n        }));\n    } else if (listController._picks && screen === 'UTPlayerPicksViewController') {\n      listrows = listController._picks.map((pick, index) => (\n        {\n          data: pick,\n          target: listController._view._playerPickViews[index].__root,\n        }));\n    } else if (listController._view._list &&\n      listController._view._list.listRows &&\n      listController._view._list.listRows.length > 0) {\n      listrows = listController._view._list.listRows; // for transfer market and club search\n    } else if (listController._view._sections &&\n      listController._view._sections.length > 0) { // for transfer list & trade pile\n      listController._view._sections.forEach((row) => {\n        if (row.listRows.length > 0) {\n          if (listrows == null) {\n            listrows = row.listRows;\n          } else {\n            listrows = listrows.concat(row.listRows);\n          }\n        }\n      });\n    }\n\n    if (listrows === null) {\n      return;\n    }\n\n    const showBargains = (this.getSettings()['show-bargains'].toString() === 'true');\n\n    const resourceIdMapping = [];\n\n    listrows\n      .filter(row => row.data.type === 'player' && row.data.resourceId !== 0)\n      .forEach((row, index) => {\n        $(row.__auction).show();\n        resourceIdMapping.push({\n          target: uiItems[index] || row.target,\n          playerId: row.data.resourceId,\n          item: row.data,\n        });\n      });\n\n    let fetchedPlayers = 0;\n    const fetchAtOnce = 30;\n    const futbinlist = [];\n    while (resourceIdMapping.length > 0 && fetchedPlayers < resourceIdMapping.length && Database.get('lastFutbinFetchFail', 0) + (5 * 60000) < Date.now()) {\n      const futbinUrl = `https://www.futbin.com/21/playerPrices?player=&rids=${\n        resourceIdMapping.slice(fetchedPlayers, fetchedPlayers + fetchAtOnce)\n          .map(i => i.playerId)\n          .filter((current, next) => current !== next && current !== 0)\n          .join(',')\n      }`;\n      fetchedPlayers += fetchAtOnce;\n      /* eslint-disable no-loop-func */\n      GM_xmlhttpRequest({\n        method: 'GET',\n        url: futbinUrl,\n        onload: (res) => {\n          if (res.status !== 200) {\n            Database.set('lastFutbinFetchFail', Date.now());\n            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');\n            return;\n          }\n\n          const futbinData = JSON.parse(res.response);\n          resourceIdMapping.forEach((item) => {\n            FutbinPrices._showFutbinPrice(screen, item, futbinData, showBargains);\n            futbinlist.push(futbinData[item.playerId]);\n          });\n          const platform = utils.getPlatform();\n          if (screen === 'SBCSquadSplitViewController' ||\n            screen === 'SquadSplitViewController' ||\n            screen === 'UTSquadSplitViewController' ||\n            screen === 'UTSBCSquadSplitViewController') {\n            const futbinTotal = futbinlist.reduce(\n              (sum, item) =>\n                sum + parseInt(\n                  item.prices[platform].LCPrice.toString().replace(/[,.]/g, ''),\n                  10,\n                ) || 0\n              , 0,\n            );\n            $('.ut-squad-summary-value.coins.value').html(`${futbinTotal.toLocaleString()}`);\n          }\n        },\n      });\n    }\n  }\n  static async _showFutbinPrice(screen, item, futbinData, showBargain) {\n    if (!futbinData) {\n      return;\n    }\n    const target = $(item.target);\n    const { playerId } = item;\n\n    if (target.find('.player').length === 0) {\n      // not a player\n      return;\n    }\n\n    const platform = utils.getPlatform();\n\n    if (!futbinData[playerId]) {\n      return; // futbin data might not be available for this player\n    }\n\n    let targetForButton = null;\n\n    if (target.find('.futbin').length > 0) {\n      return; // futbin price already added to the row\n    }\n\n    const futbinText = 'Futbin BIN';\n\n    switch (screen) {\n      case 'SBCSquadSplitViewController':\n      case 'SquadSplitViewController':\n      case 'UTSquadSplitViewController':\n      case 'UTSBCSquadSplitViewController':\n      case 'UTTOTWSquadSplitViewController':\n        target.prepend(`\n        <div class=\"ut-squad-slot-pedestal-view no-state futbin\">\n          <span class=\"coins value\">${futbinData[playerId].prices[platform].LCPrice || '---'}</span>\n        </div>`);\n        break;\n      case 'UTPlayerPicksViewController':\n        target.prepend(`\n        <div class=\"auctionValue futbin\">\n          <span class=\"label\">${futbinText}</span>\n          <span class=\"coins value\">${futbinData[playerId].prices[platform].LCPrice || '---'}</span>\n        </div>`);\n        break;\n      case 'UTTransferListSplitViewController':\n      case 'UTWatchListSplitViewController':\n      case 'UTUnassignedItemsSplitViewController':\n      case 'ClubSearchResultsSplitViewController':\n      case 'UTMarketSearchResultsSplitViewController':\n        $('.secondary.player-stats-data-component').css('float', 'left');\n        target.find('.auction').prepend(`\n        <div class=\"auctionValue futbin\">\n          <span class=\"label\">${futbinText}</span>\n          <span class=\"coins value\">${futbinData[playerId].prices[platform].LCPrice || '---'}</span>\n        </div>`);\n        break;\n      case 'SearchResults':\n        targetForButton = target.find('.auctionValue').parent();\n        targetForButton.prepend(`\n        <div class=\"auctionValue futbin\">\n          <span class=\"label\">${futbinText}</span>\n          <span class=\"coins value\">${futbinData[playerId].prices[platform].LCPrice || '---'}</span>\n        </div>`);\n        break;\n      default:\n      // no need to do anything\n    }\n\n    if (showBargain) {\n      if (item.item._auction &&\n        item.item._auction.buyNowPrice < futbinData[playerId].prices[platform].LCPrice.toString().replace(/[,.]/g, '')) {\n        target.addClass('futbin-bargain');\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "app/futbin/index.js",
    "content": "import './style/futbin-prices.scss';\n\nimport { FutbinPrices } from './futbin-prices';\nimport { FutbinPlayerLinks } from './futbin-player-links';\nimport { FutbinSettings } from './settings-entry';\n\nexport {\n  FutbinSettings,\n};\n\nnew FutbinPrices(); // eslint-disable-line no-new\nnew FutbinPlayerLinks(); // eslint-disable-line no-new\n"
  },
  {
    "path": "app/futbin/settings-entry.js",
    "content": "import { SettingsEntry } from '../core';\n\nexport class FutbinSettings extends SettingsEntry {\n  static id = 'futbin';\n  constructor() {\n    super('futbin', 'FutBIN integration');\n\n    this.addSetting('Show link to player page', 'show-link-to-player', false, 'checkbox');\n    this.addSetting('Show prices on SBC and Squad', 'show-sbc-squad', false, 'checkbox');\n    this.addSetting('Mark bargains', 'show-bargains', false, 'checkbox');\n  }\n}\n"
  },
  {
    "path": "app/futbin/style/futbin-prices.scss",
    "content": "#TradePile .player-stats-data-component, #Unassigned .player-stats-data-component {\n  width: 12em;\n}\n#TradePile .listFUTItem .entityContainer, #Unassigned .listFUTItem .entityContainer {\n  width: 45%;\n}\n#Unassigned .listFUTItem .auction .auctionValue, #Unassigned .listFUTItem .auction .auction-state {\n  display: none;\n}\n#Unassigned .listFUTItem .auction .auctionValue.futbin {\n  display: block;\n  float: right;\n}\n.MyClubResults .listFUTItem .auction {\n  display: block;\n  position: absolute;\n  right: 0;\n}\n.MyClubResults .listFUTItem .auction .auctionValue, .MyClubResults .listFUTItem .auction .auction-state {\n  width: 24%;\n  float: right;\n  padding-right: 1%;\n  display: none;\n}\n.MyClubResults .listFUTItem .auction .auctionValue.futbin {\n  display: block;\n}\n\n.listFUTItem .auction>.auction-state, .listFUTItem .auction>.auctionStartPrice, .listFUTItem .auction>.auctionValue {\n  flex: 1 1 20%;\n  overflow: hidden;\n}\n\n.listFUTItem .auction {\n  top: 30%;\n  max-width: none;\n  width: 50%;\n\n  .futbin .coins.value .time {\n    display: inline;\n    font-size: 1em;\n  }\n}\n\n@media (max-width: 1130px) {\n  .listFUTItem .auction {\n    width: auto;\n  }\n  \n  html[dir=ltr] .listFUTItem .auction {\n    left: auto;\n  }\n}\n.ut-navigation-container-view.ui-layout-right .listFUTItem .auction {\n  top: 30%;\n}\n\n.futbinupdate {\n  font-size: 14px;\n  clear: both;\n  display: block;\n}\n.coins.value.futbin {\n  -webkit-filter: hue-rotate(165deg);\n  filter: hue-rotate(165deg);\n}\n.listFUTItem.has-auction-data.futbin-bargain .rowContent {\n  background-color: #7ffe9445;\n}\n.listFUTItem.has-auction-data.selected.futbin-bargain .rowContent, .listFUTItem.has-auction-data.selected.futbin-bargain .rowContent.active {\n  background-color: #7ffe94;\n  color: #434853;\n}\n.ut-club-search-results-view {\n  .listFUTItem .auction {\n    width: 10%;\n  }\n  \n  .auction-state, .auctionValue {\n    display: none;\n\n    &.futbin {\n      display: block;\n    }\n  }\n}\n.player-picks-modal .time {\n  display: block;\n}\n.ut-squad-slot-pedestal-view.futbin {\n  min-width: 58px;\n  flex: none;\n  width: auto;\n  bottom: -2.6em;\n  white-space: nowrap;\n\n  .coins.value {\n    text-align: center;\n    margin: 0 8px;\n  }\n}\n\n.ut-squad-overview .ut-squad-summary {\n  width: 70%;\n}\n\n.refresh-squad-button {\n  margin: 17px 5px;\n  color:#e2dde2;\n  &:before {\n    font-family: UltimateTeam-Icons,sans-serif;\n    content: '\\E051';\n  }\n}\n\n"
  },
  {
    "path": "app/index.js",
    "content": "/* globals onVisibilityChanged services UTGameFlowNavigationController\nUTViewController EAObservable window document $ */\nimport 'babel-polyfill';\nimport './index.scss';\nimport initSettingsScreen from './settings';\n\nimport { analytics, Settings, Queue } from './core';\n\nimport { Logger } from '../fut';\n/*\n  RemoveSoldAuctionsSettings,\n  RelistAuctionsSettings,\n*/\nimport {\n  RefreshListSettings,\n  CardInfoSettings,\n  ListSizeSettings,\n  MinBinSettings,\n  TransferTotalsSettings,\n} from './transferlist';\n\nimport {\n  FutbinSettings,\n} from './futbin';\n\nimport {\n  InstantBinConfirmSettings,\n} from './instant-bin-confirm';\n/*\nimport {\n  ClubInfoSettings,\n} from './club';\n*/\n\nwindow.onPageNavigation = new EAObservable();\n\nwindow.currentPage = '';\n\nUTGameFlowNavigationController.prototype.didPush = (t) => {\n  if (t) {\n    analytics.trackPage(t.className);\n    window.onPageNavigation.notify(t.className);\n    window.currentPage = t.className;\n  }\n};\n\nUTViewController.prototype.didPresent = (t) => {\n  if (t) {\n    analytics.trackPage(t.className);\n    window.onPageNavigation.notify(t.className);\n    window.currentPage = t.className;\n  }\n};\n\nsetTimeout(() => {\n  services.Authentication.oAuthentication.observe(\n    this,\n    () => {\n      // reset the logs at startup\n      new Logger().reset();\n\n      // force full web app layout in any case\n      $('body').removeClass('phone').addClass('landscape');\n\n      Queue.getInstance().start();\n\n      // get rid of pinEvents when switching tabs\n      document.removeEventListener('visibilitychange', onVisibilityChanged);\n\n      const settings = Settings.getInstance();\n      settings.registerEntry(new RefreshListSettings());\n      settings.registerEntry(new MinBinSettings());\n      settings.registerEntry(new CardInfoSettings());\n      settings.registerEntry(new ListSizeSettings());\n      settings.registerEntry(new TransferTotalsSettings());\n\n      settings.registerEntry(new FutbinSettings());\n      settings.registerEntry(new InstantBinConfirmSettings());\n\n      initSettingsScreen(settings);\n    },\n  );\n}, 1000);\n"
  },
  {
    "path": "app/index.scss",
    "content": ".ut-content-container {\n    padding: 0;\n\n    .ut-content {\n        border: 0;\n\n        &.ut-content--split-view-extend {\n            max-height: 100%;\n        }\n    }\n}\n\n.listFUTItem .entityContainer .name.untradeable {\n    display: block;\n\n    &::before {\n        position: relative;\n        padding-right: 10px;\n    }\n}\n\n.ut-transfer-list-view .listFUTItem .entityContainer,\n.ut-club-search-results-view.ui-layout-left .listFUTItem .entityContainer,\n.ut-unassigned-view.ui-layout-left .listFUTItem .entityContainer {\n    width: 45%;\n}\n\n@media (min-width: 1281px) {\n    .ut-content-container .ut-content {\n        max-width: 100%;\n        max-height: 100%;\n    }\n\n    .ut-split-view .ut-content {\n        max-width: 100%;\n        max-height: 100%;\n    }\n}\n"
  },
  {
    "path": "app/instant-bin-confirm/index.js",
    "content": "import { InstantBinConfirmSettings } from './settings-entry';\nimport { InstantBinConfirm } from './instant-bin-confirm';\n\nexport {\n  InstantBinConfirmSettings,\n};\n\nnew InstantBinConfirm(); // eslint-disable-line no-new\n"
  },
  {
    "path": "app/instant-bin-confirm/instant-bin-confirm.js",
    "content": "/* global\ngPopupClickShield\nenums\nEADialogViewController\nservices\nutils\n*/\n\nimport { BaseScript } from '../core';\nimport { InstantBinConfirmSettings } from './settings-entry';\n\nexport class InstantBinConfirm extends BaseScript {\n  unmodifiedConfirmation = utils.PopupManager.ShowConfirmation;\n\n  constructor() {\n    super(InstantBinConfirmSettings.id);\n  }\n\n  activate(state) {\n    super.activate(state);\n  }\n\n  onScreenRequest(screenId) {\n    super.onScreenRequest(screenId);\n    const settings = this.getSettings();\n\n    utils.PopupManager.ShowConfirmation = (dialog, amount, proceed, s) => {\n      let cancel = s;\n      if (!utils.JS.isFunction(s)) {\n        cancel = function () { };\n      }\n\n      if (settings.isActive && dialog.title ===\n        utils.PopupManager.Confirmations.CONFIRM_BUY_NOW.title) {\n        proceed();\n        return;\n      }\n\n      const n = new EADialogViewController({\n        dialogOptions: [dialog.buttonLabels[0],\n          dialog.buttonLabels[1]],\n        message: services.Localization.localize(dialog.message, amount),\n        title: services.Localization.localize(dialog.title),\n      });\n\n      n.init();\n      gPopupClickShield.setActivePopup(n);\n      n.onExit.observe(this, (e, t) => {\n        if (t !== enums.UIDialogOptions.CANCEL && t !== enums.UIDialogOptions.NO) {\n          if (proceed) {\n            proceed();\n          } else if (cancel) {\n            cancel();\n          }\n        } else {\n          cancel();\n        }\n      });\n    };\n  }\n\n  deactivate(state) {\n    super.deactivate(state);\n    utils.PopupManager.ShowConfirmation = this.unmodifiedConfirmation;\n  }\n}\n"
  },
  {
    "path": "app/instant-bin-confirm/settings-entry.js",
    "content": "import { SettingsEntry } from '../core';\n\nexport class InstantBinConfirmSettings extends SettingsEntry {\n  static id = 'instant-bin-confirm';\n  constructor() {\n    super('instant-bin-confirm', 'Instantly confirm Buy It Now dialog');\n  }\n}\n"
  },
  {
    "path": "app/settings/html/index/settings.html",
    "content": "<button class=\"futsettings-toggle standard mini call-to-action\">\n  <span class=\"btn-text\"></span>\n</button>\n\n<div class=\"futsettings\">\n  <h1 class=\"settings-title\">FUT Tampermonkey settings\n\n    <div style=\"float:right\">\n      <a target=\"_blank\" href=\"https://www.paypal.me/timklingeleers\"\n        rel=\"nofollow\">\n        <img src=\"data:image/gif;base64,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\" />\n      </a>\n    </div>\n  </h1>\n\n  <!-- settings are injected here automatically -->\n  <div id=\"settingspanel\"></div>\n\n  <footer>\n    <h3>Need help?</h3>\n    <p>Talk to us in the Gitter channel or report errors in the Github repository.\n      <ul>\n        <li>Gitter: <a target=\"_blank\" href=\"https://gitter.im/futwebapp-tampermonkey/Lobby\">Talk in the Lobby</a></li>\n        <li>Github: <a target=\"_blank\" href=\"https://github.com/Mardaneus86/futwebapp-tampermonkey/issues\">Add an issue</a></li>\n      </ul>\n    </p>\n    <hr />\n    <h3>Enjoying this plugin?</h3>\n    <p>Consider a donation so this plugin can keep being improved.</p>\n    <p>\n      <a target=\"_blank\" href=\"https://www.paypal.me/timklingeleers\"\n        rel=\"nofollow\">\n        <img src=\"data:image/gif;base64,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\" />\n      </a>\n    </p>\n  </footer>\n</div>\n"
  },
  {
    "path": "app/settings/index.js",
    "content": "/* globals $ */\n/* eslint-disable no-restricted-syntax */\n\nimport './index.scss';\nimport { analytics } from '../core';\nimport settingsPage from './html/index/settings.html';\n\nconst handleFieldChange = (entry, setting, e) => {\n  if (setting.subsettings && setting.subsettings.length > 0) {\n    entry.changeValue(setting.key, e.target.checked);\n  } else if (setting.type === 'checkbox') {\n    entry.changeValue(setting.key, e.target.checked);\n  } else {\n    entry.changeValue(setting.key, e.target.value);\n  }\n\n  if (setting.callback) {\n    setting.callback(e.target.value);\n  }\n  if (setting.subsettings && setting.subsettings.length > 0) {\n    $(`[data-parent-feature-setting-id='${entry.id}:${setting.key}']`).toggle();\n  }\n};\n\nconst renderSettingsEntry = (setting, entry) => {\n  const inputId = `${entry.id}:${setting.key}`;\n  return `<div class=\"setting\">\n    ${setting.type !== 'checkbox' ? `<label for=\"${inputId}\">${setting.label}</label>` : ''}\n    <input\n      type=\"${setting.type}\"\n      id=\"${inputId}\"\n      data-feature-setting-id=\"${entry.id}:${setting.key}\"\n      value=\"${setting.value}\"\n      ${setting.type === 'checkbox' && setting.value.toString() === 'true' ? 'checked' : ''}\n    />\n    ${setting.type === 'checkbox' ? `<label for=\"${inputId}\">${setting.label}</label>` : ''}\n  </div>`;\n};\n\nexport default (settings) => {\n  const html = settingsPage;\n\n  $('body').prepend(html);\n\n  const settingsPanel = $('.futsettings #settingspanel');\n\n  for (const entry of settings.getEntries()) {\n    const checked = entry.isActive ? 'checked=\"checked\"' : '';\n    settingsPanel.append(`<h3 class=\"main-setting\">\n      <input type=\"checkbox\" id=\"${entry.id}\" data-feature-id=\"${entry.id}\" ${checked} />\n      <label for=\"${entry.id}\">${entry.name}</label>\n    </h3>`);\n    let settingsFields = '';\n    if (entry.settings && entry.settings.length > 0) {\n      for (const setting of entry.settings) {\n        if (setting.subsettings.length > 0) {\n          settingsFields += renderSettingsEntry(setting, entry);\n\n          const settingActive = setting.value ? 'block' : 'none';\n          settingsFields += `<div data-parent-feature-setting-id=\"${entry.id}:${setting.key}\" style=\"display: ${settingActive}\">`;\n          for (const subsetting of setting.subsettings) {\n            settingsFields += renderSettingsEntry(subsetting, entry);\n          }\n          settingsFields += '</div>';\n        } else {\n          settingsFields += renderSettingsEntry(setting, entry);\n        }\n      }\n      const featureActive = entry.isActive ? 'block' : 'none';\n      settingsPanel.append(`<div class=\"feature-settings\"><div data-feature-settings=\"${entry.id}\" style=\"display: ${featureActive};\">${settingsFields}</div></div>`);\n      for (const setting of entry.settings) {\n        $(`[data-feature-setting-id='${entry.id}:${setting.key}']`).on('change', (e) => {\n          handleFieldChange(entry, setting, e);\n        });\n        for (const subsetting of setting.subsettings) {\n          $(`[data-feature-setting-id='${entry.id}:${subsetting.key}']`).on('change', (e) => {\n            handleFieldChange(entry, subsetting, e);\n          });\n        }\n      }\n    } else {\n      settingsPanel.append('<div class=\"feature-settings-empty\"></div>');\n    }\n\n    $(`[data-feature-id='${entry.id}']`).on('click', () => {\n      settings.toggleEntry(entry.id);\n      $(`[data-feature-settings='${entry.id}']`).toggle();\n    });\n  }\n\n  $('.futsettings-toggle').click(() => {\n    analytics.trackEvent('Settings', 'Toggle settings', $('.futsettings').is(':visible'));\n    $('.futsettings').toggle();\n  });\n};\n"
  },
  {
    "path": "app/settings/index.scss",
    "content": ".futsettings-toggle {\n  position: absolute !important;\n  bottom: 20px;\n  right: 20px;\n  z-index: 999;\n\n  ::before {\n    font-family: UltimateTeam-Icons,sans-serif;\n    content: \"\\E056\";\n    font-size: 2rem;\n    color: gray;\n  }\n}\n\n.futsettings {\n  position: absolute;\n  top: 112px;\n  bottom: 0;\n  left: 105px;\n  right: 0;\n  background-color: #fff;\n  overflow-y: auto;\n  display: none;\n  z-index: 998;\n  padding: 15px;\n\n  label {\n    color: black;\n  }\n\n  &, *, *:before, *:after {\n    box-sizing: border-box;\n  }\n\n  footer {\n    text-align: center;\n    padding: 15px;\n    color: black;\n\n    hr {\n      border: none;\n      border-bottom: 1px solid #ddd;\n    }\n\n    p, li {\n      font-size: smaller;\n      margin: 10px;\n    }\n  }\n\n  .settings-title {\n    color: #183f94;\n    font-size: 2.5em;\n    font-weight: 400;\n    font-family: UltimateTeamCondensed,sans-serif;\n    line-height: 1em;\n    margin-bottom: 0.5rem;\n    text-transform: uppercase;\n    width: 100%;\n  }\n\n  .main-setting {\n    label {\n      display: inline-block;\n      padding-bottom: 15px;\n      padding-top: 15px;\n    }\n  }\n\n  .feature-settings-empty {\n    display: none;\n  }\n\n  .feature-settings {\n    background-color: #f5f5f5;\n    margin-bottom: 25px;\n    padding: 10px;\n    position: relative;\n\n    .setting {\n      padding: 10px;\n\n      input[type=number],\n      input[type=text] {\n        background-color: #fff;\n        border: 1px #33314e solid;\n        clear: both;\n        color: #33314e;\n        display: block;\n        font-size: 14px;\n        height: 3.5em;\n        padding: 10px;\n        text-align: left;\n        width: 100%;\n      }\n    }\n  }\n\n  input[type=checkbox] {\n    display: none;\n    + label {\n      cursor: pointer;\n      position: relative;\n      padding-left: 50px;\n\n      &:before {\n        background-color: #ccc;\n        border: 1px solid #999;\n        border-radius: 8px;\n        content: '';\n        height: 16px;\n        left: 0;\n        position: absolute;\n        transition: background-color 300ms ease, border-color 300ms ease;;\n        width: 40px;\n      }\n      &:after {\n        background-color: #999;\n        border: 1px solid #999;\n        border-radius: 50%;\n        content: '';\n        height: 22px;\n        left: 0;\n        margin-top: -3px;\n        position: absolute;\n        transform: translateX(0);\n        transition: background-color 300ms ease, border-color 300ms ease, transform 300ms ease;\n        width: 22px;\n      }\n    }\n    &:checked + label {\n      &:before {\n        background-color: #fc87ac;\n        border-color: #f93b78;\n      }\n      &:after {\n        background-color: #f93b78;\n        border-color: #f93b78;\n        transform: translateX(20px);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "app/transferlist/card-info.js",
    "content": "/* globals\nwindow $ document */\n\nimport { BaseScript, SettingsEntry } from '../core';\n\nimport './style/card-info.scss';\n\nexport class CardInfoSettings extends SettingsEntry {\n  static id = 'card-info';\n  constructor() {\n    super('card-info', 'Extra card information', null);\n\n    this.addSetting('Show contracts', 'show-contracts', true, 'checkbox');\n  }\n}\n\nclass CardInfo extends BaseScript {\n  constructor() {\n    super(CardInfoSettings.id);\n\n    const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;\n    this._observer = new MutationObserver(this._mutationHandler.bind(this));\n  }\n\n  activate(state) {\n    super.activate(state);\n\n    const obsConfig = {\n      childList: true,\n      characterData: true,\n      attributes: false,\n      subtree: true,\n    };\n\n    setTimeout(() => {\n      this._observer.observe($(document)[0], obsConfig);\n    }, 0);\n  }\n\n  deactivate(state) {\n    super.deactivate(state);\n    this._observer.disconnect();\n  }\n\n  _mutationHandler(mutationRecords) {\n    const settings = this.getSettings();\n    mutationRecords.forEach((mutation) => {\n      if ($(mutation.target).find('.listFUTItem').length > 0) {\n        const controller = getAppMain().getRootViewController()\n          .getPresentedViewController().getCurrentViewController()\n          .getCurrentController();\n        if (!controller || !controller._listController) {\n          return;\n        }\n\n        let items = [];\n        if (controller._listController._view._list) {\n          items = controller._listController._view._list._listRows;\n        } else {\n          items = controller._listController._viewmodel._collection.map(item => (\n            { data: item }\n          ));\n        }\n        const rows = $('.listFUTItem');\n\n        rows.each((index, row) => {\n          if ($(row).find('.infoTab-extra').length > 0) {\n            return; // already added\n          }\n\n          let info = '';\n          if (settings['show-contracts'].toString() === 'true') {\n            info += `<div class=\"contracts\" style=\"position: absolute;right: 5px;bottom: -3px;\">\n              C:${items[index].data.contract}\n              </div>`;\n          }\n\n          $(row).find('.small.player').prepend(`<div class=\"infoTab-extra\">${info}</div>`);\n        });\n      }\n    });\n  }\n}\n\nnew CardInfo(); // eslint-disable-line no-new\n"
  },
  {
    "path": "app/transferlist/index.js",
    "content": "import { RefreshListSettings } from './refresh-list';\nimport { MinBinSettings } from './min-bin';\nimport { CardInfoSettings } from './card-info';\nimport { ListSizeSettings } from './list-size';\nimport { TransferTotalsSettings } from './transfer-totals';\n\nexport {\n  CardInfoSettings,\n  RefreshListSettings,\n  MinBinSettings,\n  ListSizeSettings,\n  TransferTotalsSettings,\n};\n"
  },
  {
    "path": "app/transferlist/list-size.js",
    "content": "/* globals\ngConfigurationModel models\n*/\n\nimport { BaseScript, SettingsEntry } from '../core';\n\nexport class ListSizeSettings extends SettingsEntry {\n  static id = 'list-size';\n  constructor() {\n    super('list-size', 'Increase transfer list size', null);\n    this.addSetting('Items per page on transfer market (max 30)', 'items-per-page-transfermarket', 30, 'number');\n    this.addSetting('Items per page on club (max 90)', 'items-per-page-club', 90, 'number');\n  }\n}\n\nclass ListSize extends BaseScript {\n  constructor() {\n    super(ListSizeSettings.id);\n  }\n\n  activate(state) {\n    super.activate(state);\n\n    this._start();\n  }\n\n  onScreenRequest(screenId) {\n    super.onScreenRequest(screenId);\n\n    if (this._running) {\n      this._start();\n    }\n  }\n\n  deactivate(state) {\n    super.deactivate(state);\n\n    this._stop();\n  }\n\n  _start() {\n    this._running = true;\n\n    const itemsOnMarket = parseInt(this.getSettings()['items-per-page-transfermarket'], 10);\n    const itemsOnClub = parseInt(this.getSettings()['items-per-page-club'], 10);\n    const configObj = gConfigurationModel\n      .getConfigObject(models.ConfigurationModel.KEY_ITEMS_PER_PAGE);\n    configObj[models.ConfigurationModel.ITEMS_PER_PAGE.TRANSFER_MARKET] = itemsOnMarket;\n    configObj[models.ConfigurationModel.ITEMS_PER_PAGE.CLUB] = itemsOnClub;\n  }\n\n  _stop() {\n    this._running = false;\n\n    const configObj = gConfigurationModel\n      .getConfigObject(models.ConfigurationModel.KEY_ITEMS_PER_PAGE);\n    configObj[models.ConfigurationModel.ITEMS_PER_PAGE.TRANSFER_MARKET] = 15;\n    configObj[models.ConfigurationModel.ITEMS_PER_PAGE.CLUB] = 45;\n  }\n}\n\nnew ListSize(); // eslint-disable-line no-new\n"
  },
  {
    "path": "app/transferlist/min-bin.js",
    "content": "/* globals\nwindow $ document\n*/\nimport { analytics, BaseScript, SettingsEntry } from '../core';\nimport { TransferMarket, priceTiers } from '../../fut';\n\nexport class MinBinSettings extends SettingsEntry {\n  static id = 'min-bin';\n  constructor() {\n    super('min-bin', 'Search minimum BIN');\n\n    this.addSetting('Amount of lowest BINs to determine minimum on', 'mean-count', 3, 'number');\n    this.addSetting('Adjust quicklist panel price automatically based on minimum BIN', 'adjust-list-price', true, 'checkbox');\n    this.addSettingUnder('adjust-list-price', 'Start price percentage (0 to 100%)', 'start-price-percentage', 90, 'number');\n    this.addSettingUnder('adjust-list-price', 'Buy now price percentage (0 to 100%)', 'buy-now-price-percentage', 110, 'number');\n  }\n}\n\nclass MinBin extends BaseScript {\n  constructor() {\n    super(MinBinSettings.id);\n\n    const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;\n    this._observer = new MutationObserver(this._mutationHandler.bind(this));\n\n    this._playerPrices = [];\n  }\n\n  activate(state) {\n    super.activate(state);\n\n    const obsConfig = {\n      childList: true,\n      characterData: true,\n      attributes: false,\n      subtree: true,\n    };\n\n    setTimeout(() => {\n      this._observer.observe($(document)[0], obsConfig);\n    }, 0);\n  }\n\n  deactivate(state) {\n    super.deactivate(state);\n\n    this._observer.disconnect();\n  }\n\n  _mutationHandler(mutationRecords) {\n    mutationRecords.forEach(function (mutation) {\n      if ($(mutation.target).hasClass('DetailView') && $(mutation.target)\n        .find('.DetailPanel') && mutation.addedNodes.length > 0) {\n        const searchMinBin = $(mutation.target).find('#searchMinBin');\n        searchMinBin.remove();\n\n        let selectedItem = this._getSelectedItem();\n\n        if (selectedItem == null || selectedItem.resourceId === 0) {\n          return;\n        }\n        const knownPlayerPrice = this._playerPrices\n          .find(p => p.resourceId === selectedItem.resourceId);\n        let price = '';\n        if (knownPlayerPrice != null) {\n          price = `(${knownPlayerPrice.minimumBin})`;\n\n          this._updateListPrice(knownPlayerPrice.minimumBin);\n        }\n        $(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>`);\n\n        $('#searchMinBin').bind('click', async () => {\n          const btn = $('#searchMinBin');\n          btn.find('.btn-text').html('Searching minimum BIN...');\n          analytics.trackEvent('Min BIN', 'Search Min BIN', btn.data('resource-id'));\n          const settings = this.getSettings();\n          const minimumBin = await new TransferMarket().searchMinBuy(selectedItem, parseInt(settings['mean-count'], 10));\n          const playerPrice = this._playerPrices.find(p => p.resourceId === btn.data('resource-id'));\n          if (playerPrice != null) {\n            this._playerPrices.splice(this._playerPrices.indexOf(playerPrice), 1);\n          }\n          this._playerPrices.push({\n            resourceId: btn.data('resource-id'),\n            minimumBin,\n          });\n\n          selectedItem = this._getSelectedItem();\n\n          let notificationText = `Minimum BIN found for ${selectedItem._staticData.name} is ${minimumBin}`;\n          if (btn.data('resource-id') === selectedItem.resourceId) {\n            if (minimumBin === 0) {\n              btn.find('.btn-text').html('Search minimum BIN (extinct)');\n              notificationText = `Minimum BIN not found for ${selectedItem._staticData.name}, card may be extinct`;\n            } else {\n              btn.find('.btn-text').html(`Search minimum BIN (${minimumBin})`);\n\n              this._updateListPrice(minimumBin);\n            }\n          }\n\n          GM_notification({\n            text: notificationText,\n            title: 'FUT 21 Web App',\n            timeout: 5000,\n            onclick: () => window.focus(),\n          });\n        });\n      }\n    }, this);\n  }\n\n  _updateListPrice(minimumBin) {\n    const settings = this.getSettings();\n    const quicklistPanel = getAppMain().getRootViewController()\n      .getPresentedViewController()\n      .getCurrentViewController()\n      .getCurrentController()\n      ._rightController._currentController._quickListPanel;\n\n    if (settings['adjust-list-price'] && quicklistPanel) {\n      const quicklistpanelView = quicklistPanel._view;\n\n      const listPrice = priceTiers.determineListPrice(\n        minimumBin * (settings['start-price-percentage'] / 100),\n        minimumBin * (settings['buy-now-price-percentage'] / 100),\n      );\n\n      if (quicklistPanel._item) {\n        // sets the values when the quicklistpanel hasn't been initialized\n        const auction = quicklistPanel._item._auction;\n        if (auction.tradeState === 'closed') {\n          // item is sold\n          return;\n        }\n        if (auction.tradeState !== 'active') {\n          auction.startingBid = listPrice.start;\n          auction.buyNowPrice = listPrice.buyNow;\n          quicklistPanel._item.setAuctionData(auction);\n        }\n      }\n\n      const bidSpinner = quicklistpanelView._bidNumericStepper;\n      const buySpinner = quicklistpanelView._buyNowNumericStepper;\n      bidSpinner.setValue(listPrice.start);\n      buySpinner.setValue(listPrice.buyNow);\n    }\n  }\n\n  /* eslint-disable class-methods-use-this */\n  _getSelectedItem() {\n    const listController = getAppMain().getRootViewController()\n      .getPresentedViewController()\n      .getCurrentViewController()\n      .getCurrentController()._listController;\n    if (listController) {\n      return listController.getIterator().current();\n    }\n\n    const detailController = getAppMain().getRootViewController()\n      .getPresentedViewController()\n      .getCurrentViewController()\n      .getCurrentController()._rightController;\n    if (detailController && detailController._currentController._viewmodel) {\n      const current = detailController\n        ._currentController._viewmodel.current();\n\n      return current._item ? current._item : current;\n    }\n\n    return null;\n  }\n  /* eslint-enable class-methods-use-this */\n}\n\nnew MinBin(); // eslint-disable-line no-new\n"
  },
  {
    "path": "app/transferlist/refresh-list.js",
    "content": "/* globals $ */\n\nimport { BaseScript, SettingsEntry } from '../core';\nimport './style/refresh-list.scss';\n\nexport class RefreshListSettings extends SettingsEntry {\n  static id = 'refresh-transferlist';\n  constructor() {\n    super('refresh-transferlist', 'Refresh transferlist', null);\n  }\n}\n\nclass RefreshTransferList extends BaseScript {\n  constructor() {\n    super(RefreshListSettings.id);\n  }\n\n  activate(state) {\n    super.activate(state);\n    this._show(state.screenId);\n  }\n\n  onScreenRequest(screenId) {\n    super.onScreenRequest(screenId);\n    this._show(screenId);\n  }\n\n  deactivate(state) {\n    super.deactivate(state);\n    $('#header').find('.subTitle').find('.refresh').remove();\n  }\n\n  /* eslint-disable class-methods-use-this */\n  _show(event) {\n    switch (event) {\n      case 'UTMarketSearchResultsSplitViewController': // market search\n        setTimeout(() => {\n          if ($('.pagingContainer').find('.refresh').length === 0) {\n            $('.pagingContainer').append('<button class=\"flat pagination refresh\" style=\"float: right;\">Refresh list</button>');\n            $('.refresh').click(() => {\n              const listController = getAppMain().getRootViewController()\n                .getPresentedViewController()\n                .getCurrentViewController()\n                .getCurrentController()\n                ._listController;\n\n              const currentPage = listController._paginationViewModel._pageIndex;\n\n              listController._requestItems(currentPage);\n            });\n          }\n        }, 1000);\n        break;\n      default:\n        // no need to show anything on other screens\n    }\n  }\n  /* eslint-enable class-methods-use-this */\n}\n\nnew RefreshTransferList(); // eslint-disable-line no-new\n"
  },
  {
    "path": "app/transferlist/style/card-info.scss",
    "content": ".item.player.small.TOTW .infoTab-extra, \n.item.player.small.OTW .infoTab-extra, \n.item.player.small.TOTS .infoTab-extra, \n.item.player.small.TOTY .infoTab-extra, \n.item.player.small.legend .infoTab-extra {\n  color: white;\n}\n\n.item.player.small .infoTab-extra {\n  width: 100%;\n  height: 100%;\n  position: absolute;\n}\n"
  },
  {
    "path": "app/transferlist/style/refresh-list.scss",
    "content": "button.flat.pagination.refresh{\n    &:before {\n        font-family: UltimateTeam-Icons,sans-serif;\n        content: '\\E051';\n    }\n}"
  },
  {
    "path": "app/transferlist/style/transfer-totals.scss",
    "content": ".transfer-totals {\n    background-color: #183f94;\n    color: #fff;\n    .auction {\n        float: right;\n        margin: 1em 3em 1em 0;\n        text-align: right;\n        width: 45%;\n        .auctionStartPrice {\n            display: none;\n            @media (min-width: 1281px) {\n                display: block;\n            }\n        }\n        .auctionValue {\n            float: left;\n            padding-right: 1%;\n            width: 24%;\n        }\n        .label {\n            color: #b5b7bb;\n            display: block;\n            font-size: .75rem;\n            text-transform: uppercase;\n        }\n        .value {\n            font-size: 1.125em;\n            font-weight: 400;\n            font-family: UltimateTeamCondensed,sans-serif;\n            display: block;\n        }\n        @media (max-width: 1130px) {\n            align-items: flex-start;\n            box-sizing: border-box;\n            display: flex;\n            float: none;\n            margin: 0;\n            padding: 0.5em 1.2rem 0.5em 113px;\n            text-align: left;\n            width: 100%;\n        }\n    }\n    &:after {\n        content: '';\n        display: table;\n        width: 100%;\n    }\n}\n"
  },
  {
    "path": "app/transferlist/transfer-totals.js",
    "content": "/* globals\nwindow $ document */\n\nimport { BaseScript, SettingsEntry } from '../core';\n\nimport './style/transfer-totals.scss';\n\nexport class TransferTotalsSettings extends SettingsEntry {\n  static id = 'transfer-totals';\n  constructor() {\n    super('transfer-totals', 'Transfer list totals', null);\n\n    this.addSetting('Show transfer list totals', 'show-transfer-totals', true, 'checkbox');\n  }\n}\n\nclass TransferTotals extends BaseScript {\n  constructor() {\n    super(TransferTotalsSettings.id);\n\n    const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;\n    this._observer = new MutationObserver(this._mutationHandler.bind(this));\n  }\n\n  activate(state) {\n    super.activate(state);\n\n    const obsConfig = {\n      childList: true,\n      characterData: true,\n      attributes: false,\n      subtree: true,\n    };\n\n    setTimeout(() => {\n      this._observer.observe($(document)[0], obsConfig);\n    }, 0);\n  }\n\n  deactivate(state) {\n    super.deactivate(state);\n    this._observer.disconnect();\n  }\n\n  _mutationHandler(mutationRecords) {\n    const settings = this.getSettings();\n    mutationRecords.forEach((mutation) => {\n      if (\n        $(mutation.target).find('.listFUTItem').length > 0 ||\n        $(mutation.target).find('.futbin').length > 0\n      ) {\n        const controller = getAppMain()\n          .getRootViewController()\n          .getPresentedViewController()\n          .getCurrentViewController()\n          .getCurrentController();\n        if (!controller || !controller._listController) {\n          return;\n        }\n\n        if (window.currentPage !== 'UTTransferListSplitViewController') {\n          return;\n        }\n\n        if (!settings.isActive || settings['show-transfer-totals'].toString() !== 'true') {\n          return;\n        }\n\n        const lists = $('.ut-transfer-list-view .itemList');\n        const items = controller._listController._viewmodel._collection;\n        const listRows = $('.ut-transfer-list-view .listFUTItem');\n\n        lists.each((index, list) => {\n          const totals = {\n            futbin: 0,\n            bid: 0,\n            bin: 0,\n          };\n          const listEl = $(list);\n\n          if (!listEl.find('.listFUTItem').length) {\n            return;\n          }\n\n          const firstIndex = $(list).find('.listFUTItem:first').index('.ut-transfer-list-view .listFUTItem');\n          const lastIndex = $(list).find('.listFUTItem:last').index('.ut-transfer-list-view .listFUTItem');\n\n          totals.futbin = items.slice(firstIndex, lastIndex + 1).reduce((sum, item, i) => {\n            const futbin = parseInt(\n              listRows.eq(i + firstIndex)\n                .find('.auctionValue.futbin .coins.value')\n                .text()\n                .replace(/[,.]/g, ''),\n              10,\n            ) || 0;\n            return sum + futbin;\n          }, 0);\n          totals.bid = items.slice(firstIndex, lastIndex + 1)\n            .reduce((sum, item) => {\n              const { currentBid, startingBid } = item._auction;\n              const actualBid = currentBid > 0 ? currentBid : startingBid;\n              return sum + actualBid;\n            }, 0);\n          totals.bin = items.slice(firstIndex, lastIndex + 1)\n            .reduce((sum, item) => sum + item._auction.buyNowPrice, 0);\n\n          const totalsItem = listEl.prev('.transfer-totals');\n\n          if (!totalsItem.length) {\n            $(`<div class=\"transfer-totals\">\n            <div class=\"auction\">\n              <div class=\"auctionValue futbin\">\n                <span class=\"label\">Futbin BIN</span>\n                <span class=\"coins value total-futbin\">0</span>\n              </div>\n              <div class=\"auctionValue\">\n                <span class=\"label\">Bid Total</span>\n                <span class=\"coins value total-bid\">0</span>\n              </div>\n              <div class=\"auctionValue\">\n                <span class=\"label\">BIN Total</span>\n                <span class=\"coins value total-bin\">0</span>\n              </div>\n            </div>\n          </div>`).insertBefore(listEl);\n          }\n\n          if (totals.futbin > 0) {\n            totalsItem.find('.total-futbin').text(totals.futbin);\n            totalsItem.find('.futbin').show();\n          } else {\n            totalsItem.find('.futbin').hide();\n          }\n          totalsItem.find('.total-bin').text(totals.bin);\n          totalsItem.find('.total-bid').text(totals.bid);\n        });\n      }\n    });\n  }\n}\n\nnew TransferTotals(); // eslint-disable-line no-new\n"
  },
  {
    "path": "fut/club.js",
    "content": "/* globals\ntransferobjects enums communication factories\n*/\n\nexport class Club {\n  async getPlayers(start, count) {\n    return new Promise((resolve, reject) => {\n      const t = new transferobjects.SearchCriteria();\n      t.type = enums.SearchType.PLAYER;\n\n      const o = new communication.ClubSearchDelegate(t, start, count);\n      o._useClickShield = false;\n\n      o.addListener(communication.BaseDelegate.SUCCESS, this, (sender, response) => {\n        sender.clearListenersByScope(this);\n\n        const players = Array.isArray(response.itemData) ?\n          factories.Item.generateItemsFromItemData(response.itemData) : [];\n        const isLastPage = players.length <= count - 1;\n        resolve({\n          isLastPage,\n          getNextPage: isLastPage ? null : () => this.getPlayers(start + count, count),\n          players,\n        });\n      });\n\n      o.addListener(communication.BaseDelegate.FAIL, this, (sender, response) => {\n        sender.clearListenersByScope(this);\n        reject(response);\n      });\n\n      o.send();\n    });\n  }\n}\n"
  },
  {
    "path": "fut/errors/index.js",
    "content": "export class ListPlayerError extends Error {}\n"
  },
  {
    "path": "fut/index.js",
    "content": "import { Logger } from './logger';\nimport { PinEvent } from './pinEvent';\nimport { Store } from './store';\nimport { TransferMarket } from './transferMarket';\nimport { Club } from './club';\nimport utils from './utils';\nimport priceTiers from './priceTiers';\n\nexport {\n  Club,\n  Logger,\n  PinEvent,\n  Store,\n  TransferMarket,\n  utils,\n  priceTiers,\n};\n"
  },
  {
    "path": "fut/logger.js",
    "content": "export class Logger {\n  constructor() {\n    this._storeName = 'logger';\n  }\n\n  log(message, category = 'FUT') {\n    /* eslint-disable no-console */\n    console.log(`${category}: ${message}`);\n    /* eslint-enable no-console */\n    const log = JSON.parse(GM_getValue(this._storeName, '[]'));\n    log.push(`${category}: ${message}`);\n    GM_setValue(this._storeName, JSON.stringify(log));\n  }\n\n  reset() {\n    GM_setValue(this._storeName, '[]');\n  }\n}\n"
  },
  {
    "path": "fut/pinEvent.js",
    "content": "/* globals PIN_PAGEVIEW_EVT_TYPE services PINEventType */\n\nexport class PinEvent {\n  static sendPageView(pageId, delay = 2000) {\n    return new Promise(resolve =>\n      setTimeout(() => {\n        services.PIN.sendData(PINEventType.PAGE_VIEW, {\n          type: PIN_PAGEVIEW_EVT_TYPE,\n          pgid: pageId,\n        });\n        resolve();\n      }, delay));\n  }\n}\n"
  },
  {
    "path": "fut/priceTiers.js",
    "content": "/* global utils UTCurrencyInputControl */\nexport default {\n  roundValueToNearestPriceTiers(value) {\n    const tier = utils.JS.find(UTCurrencyInputControl.PRICE_TIERS, i => value > i.min);\n\n    const diff = value % tier.inc;\n\n    if (diff === 0) {\n      return value;\n    } else if (diff < tier.inc / 2) {\n      return value - diff;\n    }\n    return value + (tier.inc - diff);\n  },\n\n  roundDownToNearestPriceTiers(value) {\n    const tier = utils.JS.find(UTCurrencyInputControl.PRICE_TIERS, i => value > i.min);\n\n    const diff = value % tier.inc;\n\n    if (diff === 0) {\n      return value - tier.inc;\n    }\n    return value - diff;\n  },\n\n  determineListPrice(start, buyNow) {\n    const tier = utils.JS.find(UTCurrencyInputControl.PRICE_TIERS, i => buyNow > i.min);\n\n    const startPrice = this.roundValueToNearestPriceTiers(start);\n    let buyNowPrice = this.roundValueToNearestPriceTiers(buyNow);\n\n    if (startPrice === buyNowPrice) {\n      buyNowPrice += tier.inc;\n    }\n\n    return {\n      start: startPrice,\n      buyNow: buyNowPrice,\n    };\n  },\n};\n"
  },
  {
    "path": "fut/store.js",
    "content": "/* global communication repositories enums services */\n\nexport class Store {\n  getUnassignedItems() {\n    return new Promise((resolve) => {\n      repositories.Item.reset(enums.FUTItemPile.PURCHASED);\n      repositories.Item.getUnassignedItems().observe(this, function (o, list) {\n        o.unobserve(this);\n        resolve(list.items);\n      });\n    });\n  }\n\n  getTradePile() {\n    return new Promise((resolve, reject) => {\n      repositories.Item.getTransferItems().observe(this, (obs, data) => {\n        obs.unobserve(this);\n\n        if (data.error) {\n          reject(new Error(data.erorr));\n        } else {\n          resolve(data.items);\n        }\n      });\n    });\n  }\n\n  async getTradePileUnsold() {\n    const tradepile = await this.getTradePile();\n\n    return tradepile.filter(d => d.state === enums.ItemState.FREE && d._auction.buyNowPrice > 0);\n  }\n\n  redeemItem(item) {\n    return new Promise((resolve, reject) => {\n      const redeem = new communication.ConsumeUnlockableDelegate(item.id);\n      redeem.addListener(communication.BaseDelegate.SUCCESS, this, (sender, response) => {\n        sender.clearListenersByScope(this);\n        resolve(response);\n      });\n      redeem.addListener(communication.BaseDelegate.FAIL, this, (sender, response) => {\n        sender.clearListenersByScope(this);\n        reject(response);\n      });\n      redeem.send();\n    });\n  }\n\n  quickSell(items) {\n    return new Promise((resolve) => {\n      services.Item.discard(items).observe(this, (obs, res) => {\n        obs.unobserve(this);\n        resolve(res);\n      });\n    });\n  }\n\n  sendToClub(items) {\n    return new Promise((resolve, reject) => {\n      const moveItem = new communication.MoveItemDelegate(items, enums.FUTItemPile.CLUB);\n      moveItem.addListener(communication.BaseDelegate.SUCCESS, this, (sender, response) => {\n        sender.clearListenersByScope(this);\n        resolve(response);\n      });\n      moveItem.addListener(communication.BaseDelegate.FAIL, this, (sender, response) => {\n        sender.clearListenersByScope(this);\n        reject(response);\n      });\n      moveItem.send();\n    });\n  }\n\n  removeSoldAuctions() {\n    return new Promise((resolve, reject) => {\n      services.Item.clearSoldItems().observe(this, (observer, data) => {\n        observer.unobserve(this);\n\n        if (data.error) {\n          reject(new Error(data.erorr));\n        } else {\n          resolve(data.items);\n        }\n      });\n    });\n  }\n}\n"
  },
  {
    "path": "fut/transferMarket.js",
    "content": "/* globals\nenums factories communication gUserModel models repositories services\n*/\nimport { mean } from 'math-statistics';\n\nimport utils from './utils';\nimport priceTiers from './priceTiers';\nimport { Logger } from './logger';\nimport { PinEvent } from './pinEvent';\nimport { ListItemError } from './errors';\n\nexport class TransferMarket {\n  _logger = new Logger();\n\n  /* eslint-disable class-methods-use-this */\n  async navigateToTransferHub() {\n    await PinEvent.sendPageView('Hub - Transfers');\n  }\n\n  async navigateToTransferList() {\n    await this.navigateToTransferHub();\n    await PinEvent.sendPageView('Transfer List - List View');\n  }\n  /* eslint-enable class-methods-use-this */\n\n  async searchMinBuy(item, itemsForMean = 3, lowUp = false) {\n    services.Item.clearTransferMarketCache();\n\n    this._logger.log(`Searching min buy for ${item.type} ${item._staticData.name} from low upward first ${lowUp}`, 'Core - Transfermarket');\n    let minBuy = 0;\n\n    if (lowUp) {\n      minBuy = await this._findLowUp(item, itemsForMean);\n      this._logger.log(`Low up search yielded ${minBuy} as a result`, 'Core - Transfermarket');\n    }\n\n    if (minBuy === 0) {\n      this._logger.log('Searching low down...', 'Core - Transfermarket');\n      minBuy = await this._findLowDown(item, itemsForMean);\n    }\n\n    if (minBuy === 0) {\n      this._logger.log('No players found... it might be extinct', 'Core - Transfermarket');\n    } else {\n      this._logger.log(`Min buy for ${item.type} ${item._staticData.name} is ${minBuy}`, 'Core - Transfermarket');\n    }\n    return minBuy;\n  }\n\n  /**\n   * List item on transfermarket\n   *\n   * @param {FUTItem} item\n   * @param {number} start start price\n   * @param {number} buyNow buy now price\n   * @param {number} duration time to list in seconds (1, 3, 6, 12, 24 or 72 hours)\n   */\n  async listItem(item, start, buyNow, duration = 3600) {\n    return new Promise(async (resolve, reject) => {\n      if (gUserModel.getTradeAccess() !== models.UserModel.TRADE_ACCESS.WHITELIST) {\n        reject(new Error('You are not authorized for trading'));\n        return;\n      }\n\n      const prices = priceTiers.determineListPrice(start, buyNow);\n\n      await this.sendToTradePile(item);\n      await utils.sleep(1000);\n\n      const listItem = new communication.ListItemDelegate({\n        itemId: item.id,\n        startingBid: prices.start,\n        buyNowPrice: prices.buyNow,\n        duration,\n      });\n      listItem.addListener(communication.BaseDelegate.SUCCESS, this, (sender) => {\n        sender.clearListenersByScope(this);\n        resolve({\n          startingBid: prices.start,\n          buyNowPrice: prices.buyNow,\n        });\n      });\n      listItem.addListener(communication.BaseDelegate.FAIL, this, (sender, response) => {\n        sender.clearListenersByScope(this);\n        reject(new ListItemError(response));\n      });\n      listItem.send();\n    });\n  }\n\n  sendToTradePile(item) {\n    return new Promise((resolve, reject) => {\n      const moveItem = new communication.MoveItemDelegate([item], enums.FUTItemPile.TRANSFER);\n      moveItem.addListener(communication.BaseDelegate.SUCCESS, this, (sender) => {\n        sender.clearListenersByScope(this);\n        resolve();\n      });\n      moveItem.addListener(communication.BaseDelegate.FAIL, this, (sender, response) => {\n        sender.clearListenersByScope(this);\n        reject(new Error(response));\n      });\n      moveItem.send();\n    });\n  }\n\n  relistAllItems() {\n    return new Promise((resolve, reject) => {\n      if (gUserModel.getTradeAccess() !== models.UserModel.TRADE_ACCESS.WHITELIST) {\n        reject(new Error('You are not authorized for trading'));\n        return;\n      }\n\n      const relistExpired = new communication.AuctionRelistDelegate();\n\n      relistExpired.addListener(communication.BaseDelegate.SUCCESS, this, (sender) => {\n        sender.clearListenersByScope(this);\n        repositories.Item.setDirty(enums.FUTItemPile.TRANSFER);\n        resolve();\n      });\n\n      relistExpired.addListener(communication.BaseDelegate.FAIL, this, (sender, error) => {\n        sender.clearListenersByScope(this);\n        reject(new Error(error));\n      });\n      relistExpired.execute();\n    });\n  }\n\n  async _findLowUp(item, itemsForMean) {\n    const searchCriteria = this._defineSearchCriteria(item, 200);\n    await PinEvent.sendPageView('Transfer Market Search');\n    await utils.sleep(3000);\n    await PinEvent.sendPageView('Transfer Market Results - List View', 0);\n    await PinEvent.sendPageView('Item - Detail View', 0);\n    const items = await this._find(searchCriteria);\n    if (items.length > itemsForMean) {\n      // we find more than X listed at this price, so it must be low value\n      return 200;\n    }\n\n    return 0; // trigger searching low down\n  }\n\n  async _findLowDown(item, itemsForMean) {\n    let minBuy = 99999999;\n    const searchCriteria = this._defineSearchCriteria(item);\n\n    let valuesFound = [];\n    for (let minBuyFound = false; minBuyFound === false;) {\n      /* eslint-disable no-await-in-loop */\n      await PinEvent.sendPageView('Transfer Market Search');\n      await utils.sleep(800);\n      await PinEvent.sendPageView('Transfer Market Results - List View', 0);\n      await PinEvent.sendPageView('Item - Detail View', 0);\n      const items = await this._find(searchCriteria);\n      /* eslint-enable no-await-in-loop */\n      if (items.length > 0) {\n        valuesFound = valuesFound.concat(items.map(i => i._auction.buyNowPrice));\n\n        const minBuyOnPage = Math.min(...items.map(i => i._auction.buyNowPrice));\n        if (minBuyOnPage < minBuy) {\n          minBuy = minBuyOnPage;\n          if (items.length < searchCriteria.count) {\n            minBuyFound = true;\n            break;\n          }\n          searchCriteria.maxBuy = priceTiers.roundDownToNearestPriceTiers(minBuy);\n          if (searchCriteria.maxBuy < 200) {\n            searchCriteria.maxBuy = 200;\n          }\n        } else if (items.length === searchCriteria.count) {\n          if (searchCriteria.maxBuy === 0) {\n            searchCriteria.maxBuy = minBuy;\n          } else {\n            searchCriteria.maxBuy = priceTiers.roundDownToNearestPriceTiers(searchCriteria.maxBuy);\n          }\n          if (searchCriteria.maxBuy < 200) {\n            searchCriteria.maxBuy = 200;\n            minBuy = 200;\n            minBuyFound = true;\n          }\n        } else {\n          minBuy = Math.min(...items.map(i => i._auction.buyNowPrice));\n          minBuyFound = true;\n        }\n      } else {\n        minBuyFound = true;\n      }\n    }\n\n    valuesFound = valuesFound.sort((a, b) => a - b).slice(0, itemsForMean);\n\n    if (valuesFound.length > 0) {\n      return priceTiers.roundValueToNearestPriceTiers(mean(valuesFound));\n    }\n\n    return 0; // player extinct\n  }\n\n  /* eslint-disable class-methods-use-this */\n  _defineSearchCriteria(item, maxBuy = -1) {\n    // TODO: check if this can handle other items as well\n    // eslint-disable-next-line no-undef\n    const searchCriteria = new UTSearchCriteriaDTO();\n\n    searchCriteria.count = 30;\n    searchCriteria.maskedDefId = item.getMaskedResourceId();\n    searchCriteria.type = item.type;\n\n    if (item.rareflag === 47) { // 47 = Champions\n      // if it is a Champions card, this is seen as a gold card\n      // Can only search for \"Gold\" in this case\n      searchCriteria.level = factories.DataProvider.getItemLevelDP(true)\n        .filter(d => d.id === 2)[0].value;\n    } else if (item.rareflag >= 3) { // 3 = TOTW\n      // if it is TOTW or other special, set it to TOTW. See enums.ItemRareType.\n      // Can only search for \"Specials\", not more specific on Rare Type\n      searchCriteria.level = factories.DataProvider.getItemLevelDP(true)\n        .filter(d => d.id === 3)[0].value;\n    }\n\n    searchCriteria.category = enums.SearchCategory.ANY;\n    searchCriteria.position = enums.SearchType.ANY;\n    if (maxBuy !== -1) {\n      searchCriteria.maxBuy = maxBuy;\n    }\n\n    return searchCriteria;\n  }\n  /* eslint-enable class-methods-use-this */\n\n  _find(searchCriteria) {\n    return new Promise((resolve, reject) => {\n      services.Item.searchTransferMarket(searchCriteria, 1).observe(\n        this,\n        function (obs, res) {\n          if (!res.success) {\n            obs.unobserve(this);\n            reject(res.status);\n          } else {\n            resolve(res.data.items);\n          }\n        },\n      );\n    });\n  }\n}\n"
  },
  {
    "path": "fut/utils.js",
    "content": "/* globals\nservices\n*/\n\nexport default {\n  /**\n   * Sleep for a while\n   *\n   * @param {number} min minimum sleep time in ms\n   * @param {number} variance maximum variation to add to the minimum in ms\n   */\n  sleep(min, variance = 1000) {\n    const delay = min + Math.floor(Math.random() * variance);\n    // new Logger().log(`Delay for ${delay} (requested: ${min}+${variance})`, 'Core');\n    return new Promise(resolve => setTimeout(resolve, delay));\n  },\n\n  getPlatform() {\n    if (services.User.getUser().getSelectedPersona().isPlaystation) {\n      return 'ps';\n    }\n    if (services.User.getUser().getSelectedPersona().isPC) {\n      return 'pc';\n    }\n    if (services.User.getUser().getSelectedPersona().isXbox) {\n      return 'xbox';\n    }\n\n    throw new Error('unknown platform');\n  },\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"futwebapp-single\",\n  \"version\": \"0.0.0-development\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"clean\": \"rm -rf dist\",\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n    \"start\": \"npm run standards:watch & webpack --watch\",\n    \"build\": \"npm run standards && webpack\",\n    \"build:production\": \"npm run standards && npm run semantic-release:dry && webpack --config webpack.config.prd.js\",\n    \"standards\": \"eslint .\",\n    \"standards:watch\": \"esw --watch\",\n    \"semantic-release:dry\": \"semantic-release --dry-run\",\n    \"semantic-release\": \"semantic-release\"\n  },\n  \"eslintIgnore\": [\n    \"dist\",\n    \"webpack.config.js\",\n    \"webpack.config.prd.js\",\n    \"tampermonkey-headers.js\"\n  ],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"devDependencies\": {\n    \"babel-cli\": \"^6.26.0\",\n    \"babel-core\": \"^6.26.3\",\n    \"babel-eslint\": \"^8.0.1\",\n    \"babel-loader\": \"^7.1.2\",\n    \"babel-plugin-transform-class-properties\": \"^6.24.1\",\n    \"babel-preset-env\": \"^1.7.0\",\n    \"css-loader\": \"^0.28.7\",\n    \"eslint\": \"^4.10.0\",\n    \"eslint-config-airbnb\": \"^16.1.0\",\n    \"eslint-plugin-import\": \"^2.8.0\",\n    \"eslint-plugin-jsx-a11y\": \"^6.0.2\",\n    \"eslint-plugin-react\": \"^7.4.0\",\n    \"eslint-watch\": \"^3.1.3\",\n    \"html-loader\": \"^0.5.1\",\n    \"last-release-git\": \"0.0.3\",\n    \"node-libs-browser\": \"webpack/node-libs-browser\",\n    \"node-sass\": \"^4.14.1\",\n    \"sass-loader\": \"^6.0.6\",\n    \"semantic-release\": \"^9.0.0\",\n    \"style-loader\": \"^0.19.0\",\n    \"webpack\": \"^3.8.1\",\n    \"webpack-cli\": \"^3.3.12\",\n    \"webpack-obfuscator\": \"^2.4.3\"\n  },\n  \"dependencies\": {\n    \"babel-polyfill\": \"^6.26.0\",\n    \"c3\": \"^0.4.18\",\n    \"d3\": \"^4.11.0\",\n    \"event-emitter-es6\": \"^1.1.5\",\n    \"math-statistics\": \"^1.2.0\",\n    \"moment\": \"^2.22.2\",\n    \"moment-duration-format\": \"^1.3.0\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/Mardaneus86/futwebapp-tampermonkey.git\"\n  },\n  \"release\": {\n    \"getLastRelease\": \"last-release-git\",\n    \"branch\": \"master\"\n  }\n}\n"
  },
  {
    "path": "tampermonkey-headers.js",
    "content": "// ==UserScript==\n// @name        FUT Enhancer\n// @version     VERSION\n// @description Enhances the FIFA Ultimate Team 21 Web app. Includes Futbin integration and other useful tools\n// @license     MIT\n// @author      Tim Klingeleers\n// @match       https://www.ea.com/fifa/ultimate-team/web-app/*\n// @match       https://www.easports.com/*/fifa/ultimate-team/web-app/*\n// @match       https://www.ea.com/*/fifa/ultimate-team/web-app/*\n// @namespace   https://github.com/Mardaneus86\n// @supportURL  https://github.com/Mardaneus86/futwebapp-tampermonkey/issues\n// @grant       GM_notification\n// @grant       GM_xmlhttpRequest\n// @grant       GM_getValue\n// @grant       GM_setValue\n// @grant       window.focus\n// @connect     ea.com\n// @connect     futbin.com\n// @connect     google-analytics.com\n// @updateURL   https://github.com/Mardaneus86/futwebapp-tampermonkey-web/raw/master/downloads/FUT_Enhancer.meta.js\n// @downloadURL https://github.com/Mardaneus86/futwebapp-tampermonkey-web/raw/master/downloads/FUT_Enhancer.user.js\n// ==/UserScript==\n"
  },
  {
    "path": "webpack.config.js",
    "content": "const webpack = require('webpack');\n\nmodule.exports = {\n  entry: './app/index.js',\n  output: {\n    filename: './dist/fut-enhancer.user.js',\n  },\n  node: {\n    fs: 'empty',\n    tls: 'empty',\n    net: 'empty'\n  },\n  devtool: 'eval-source-map',\n  plugins: [\n    new webpack.DefinePlugin({\n        'UA_TOKEN': JSON.stringify('UA-126264296-1')\n    })\n  ],\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        exclude: /(node_modules|bower_components)/,\n        use: {\n          loader: 'babel-loader',\n          options: {\n            presets: ['env'],\n            plugins: [\n              'transform-class-properties',\n            ],\n          },\n        },\n      },\n      {\n        test: /\\.scss$|\\.css$/,\n        use: [{\n          loader: 'style-loader', // creates style nodes from JS strings\n        }, {\n          loader: 'css-loader', // translates CSS into CommonJS\n        }, {\n          loader: 'sass-loader', // compiles Sass to CSS\n        }],\n      },\n      {\n        test: /\\.html$/,\n        use: [{\n          loader: 'html-loader',\n          options: {\n            minimize: true,\n            removeComments: false,\n            collapseWhitespace: false,\n          },\n        }],\n      },\n    ],\n  },\n};\n"
  },
  {
    "path": "webpack.config.prd.js",
    "content": "var fs = require(\"fs\");\n\nconst webpack = require('webpack');\nconst config = require('./webpack.config');\n\nmodule.exports = env => {\n  let header = fs.readFileSync('./tampermonkey-headers.js', 'utf8');\n  header = header.replace('VERSION', process.env.TM_VERSION); // set by the build process on Travis\n  \n  console.log('Changed Tampermonkey header version to ' + process.env.TM_VERSION);\n  \n  config.devtool = 'none';\n  config.plugins = [\n    new webpack.DefinePlugin({\n        'UA_TOKEN': JSON.stringify('UA-126264296-2')\n    }),\n    new webpack.BannerPlugin({ \n      banner: header,\n      raw: true, \n      entryOnly: true \n    }),\n  ];\n\n  return config;\n};\n"
  }
]