1// Edge fixes 2 3Object.fromEntries = 4 Object.fromEntries || 5 function (arr) { 6 return arr.reduce(function (acc, curr) { 7 acc[curr[0]] = curr[1]; 8 return acc; 9 }, {}); 10 }; 11 12let proxy = "https://cors2.fploptimized.com"; 13// let proxy = "https://cors.alpscode.com" 14// let proxy = "https://cors.fploptimized.com" 15 16if (!Array.prototype.flat) { 17 Object.defineProperty(Array.prototype, "flat", { 18 value: function (depth = 1, stack = []) { 19 for (let item of this) { 20 if (item instanceof Array && depth > 0) { 21 item.flat(depth - 1, stack); 22 } else { 23 stack.push(item); 24 } 25 } 26 return stack; 27 }, 28 }); 29} 30 31if (!Promise.allSettled) { 32 function allSettled(promises) { 33 let wrappedPromises = promises.map((p) => 34 Promise.resolve(p).then( 35 (val) => ({ status: "fulfilled", value: val }), 36 (err) => ({ status: "rejected", reason: err }) 37 ) 38 ); 39 return Promise.all(wrappedPromises); 40 } 41 Promise.allSettled = allSettled; 42} 43 44let team_codes = { 45 3: { name: "Arsenal", short: "ARS" }, 46 7: { name: "Aston Villa", short: "AVL" }, 47 91: { name: "Bournemouth", short: "BOU" }, 48 94: { name: "Brentford", short: "BRE" }, 49 36: { name: "Brighton", short: "BHA" }, 50 90: { name: "Burnley", short: "BUR" }, 51 8: { name: "Chelsea", short: "CHE" }, 52 31: { name: "Crystal Palace", short: "CRY" }, 53 11: { name: "Everton", short: "EVE" }, 54 54: { name: "Fulham", short: "FUL" }, 55 2: { name: "Leeds", short: "LEE" }, 56 40: { name: "Ipswich", short: "IPS" }, 57 13: { name: "Leicester", short: "LEI" }, 58 14: { name: "Liverpool", short: "LIV" }, 59 43: { name: "Man City", short: "MCI" }, 60 1: { name: "Man Utd", short: "MUN" }, 61 4: { name: "Newcastle", short: "NEW" }, 62 17: { name: "Nott'm Forest", short: "NFO" }, 63 56: { name: "Sunderland", short: "SUN" }, 64 45: { name: "Norwich", short: "NOR" }, 65 49: { name: "Sheffield Utd", short: "SHU" }, 66 20: { name: "Southampton", short: "SOU" }, 67 6: { name: "Spurs", short: "TOT" }, 68 57: { name: "Watford", short: "WAT" }, 69 35: { name: "West Brom", short: "WBA" }, 70 21: { name: "West Ham", short: "WHU" }, 71 39: { name: "Wolves", short: "WOL" }, 72 102: { name: "Luton", short: "LUT" }, 73 9: { name: "Coventry", short: "COV" }, 74 88: { name: "Hull", short: "HUL" }, 75}; 76 77let team_short_dict = _.fromPairs( 78 _(team_codes) 79 .map((val, key) => [val.short, key]) 80 .value() 81); 82 83let season_teams = [ 84 "ARS", 85 "AVL", 86 "BOU", 87 "BRE", 88 "BHA", 89 "CHE", 90 "COV", 91 "CRY", 92 "EVE", 93 "FUL", 94 "HUL", 95 "IPS", 96 "LEE", 97 "LIV", 98 "MCI", 99 "MUN", 100 "NEW", 101 "NFO", 102 "SUN", 103 "TOT", 104]; 105 106// Index must match the FPL team id (teams_ordered[team_id - 1] is used 107// throughout), which FPL assigns by sorting its `name` field -- note that 108// puts Spurs before Sunderland. Regenerate from bootstrap-static each season. 109let teams_ordered = [ 110 { name: "Arsenal", short: "ARS" }, 111 { name: "Aston Villa", short: "AVL" }, 112 { name: "Bournemouth", short: "BOU", long: "AFC Bournemouth" }, 113 { name: "Brentford", short: "BRE" }, 114 { name: "Brighton", short: "BHA", long: "Brighton and Hove Albion" }, 115 { name: "Chelsea", short: "CHE" }, 116 { name: "Coventry", short: "COV", long: "Coventry City" }, 117 { name: "Crystal Palace", short: "CRY" }, 118 { name: "Everton", short: "EVE" }, 119 { name: "Fulham", short: "FUL" }, 120 { name: "Hull", short: "HUL", long: "Hull City" }, 121 { name: "Ipswich", short: "IPS", long: "Ipswich Town" }, 122 { name: "Leeds", short: "LEE", long: "Leeds United" }, 123 { name: "Liverpool", short: "LIV" }, 124 { name: "Man City", short: "MCI", long: "Manchester City" }, 125 { name: "Man Utd", short: "MUN", long: "Manchester United" }, 126 { name: "Newcastle", short: "NEW" }, 127 { name: "Nott'm Forest", short: "NFO", long: "Nottingham Forest" }, 128 { name: "Spurs", short: "TOT", long: "Tottenham Hotspur" }, 129 { name: "Sunderland", short: "SUN" }, 130]; 131 132let pl_team_colors = { 133 ARS: ["#EF0107", "#FFFFFF"], 134 AVL: ["#95BFE5", "#670E36"], 135 BRE: ["#FFFFFF", "#e30613"], 136 BHA: ["#FFFFFF", "#40a8ff"], 137 BUR: ["#6C1D45", "#99D6EA"], 138 CHE: ["#034694", "#D1D3D4"], 139 CRY: ["#1B458F", "#D16475"], 140 EVE: ["#003399", "#FFFFFF"], 141 LEI: ["#003090", "#FDBE11"], 142 LEE: ["#FFFFFF", "#0030b9"], 143 LIV: ["#C8102E", "#0bcdc3"], 144 MCI: ["#6CABDD", "#1C2C5B"], 145 MUN: ["#DA291C", "#FBE122"], 146 NEW: ["#241F20", "#FFFFFF"],
147 NOR: ["#FFF200", "#00A650"], 148 SOU: ["#ed1a3b", "#FFFFFF"], 149 TOT: ["#132257", "#FFFFFF"], 150 WAT: ["#FBEE23", "#ED2127"], 151 WHU: ["#7A263A", "#F3D459"], 152 WOL: ["#FDB913", "#231F20"], 153 FUL: ["#000000", "#FFFFFF"], 154 BOU: ["#DA291C", "#EFDBB2"], 155 NFO: ["#E53233", "#FFFFFF"], 156 LUT: ["#F78F1E", "#002D62"], 157 SHU: ["#EE2737", "#000000"], 158 IPS: ["#DE2C37", "#3A64A3"], 159 WBA: ["#122F67", "#FFFFFF"], 160 SUN: ["#E10600", "#FFFFFF"], 161 COV: ["#78D0F3", "#1C2C5B"], 162 HUL: ["#F5A12D", "#000000"], 163}; 164 165// Defensive contribution banks 2 points once a player reaches the threshold 166// for their position: 10 for defenders (clearances, blocks, interceptions, 167// tackles), 12 for midfielders and forwards, whose count also includes ball 168// recoveries. Goalkeepers are not eligible. 169let defcon_thresholds = { 2: 10, 3: 12, 4: 12 }; 170 171let element_type = { 172 1: { name: "Goalkeeper", short: "GK", id: 1, min: 1, max: 1, cnt: 2 }, 173 2: { name: "Defender", short: "DF", id: 2, min: 3, max: 5, cnt: 5 }, 174 3: { name: "Midfielder", short: "MD", id: 3, min: 2, max: 5, cnt: 5 }, 175 4: { name: "Forward", short: "FW", id: 4, min: 1, max: 3, cnt: 3 }, 176 // 5: { name: "Managers ", short: "MNG", id: 5, min: 0, max: 1, cnt: 1 }, 177}; 178 179let stat_types = { 180 goals_scored: { name: "Goals", icon: "fas fa-futbol" }, 181 assists: { name: "Assists", icon: "fas fa-hands-helping" }, 182 bonus: { name: "Bonus", icon: "fas fa-coins" }, 183 bps: { name: "BPS" }, 184 own_goals: { name: "Own Goals", icon: "far fa-grimace" }, 185 penalties_missed: { name: "Penalties Missed", icon: "far fa-thumbs-down" }, 186 penalties_saved: { name: "Penalties Saved", icon: "fas fa-chess-rook" }, 187 saves: { name: "Saves", icon: "far fa-hand-paper" }, 188 red_cards: { name: "Red Cards", icon: "fas fa-door-closed" }, 189 yellow_cards: { name: "Yellow Cards", icon: "fas fa-door-open" }, 190 bps_provisional: { name: "BPS (Live)", icon: "fas fa-calculator" }, 191 defensive_contribution: { name: "Def Con", icon: "fas fa-shield-alt" }, 192 // mng_win: { name: "AM Win", icon: "fas fa-trophy" }, 193 // mng_draw: { name: "AM Draw", icon: "fas fa-trophy" }, 194 // mng_loss: { name: "AM Loss", icon: "fas fa-trophy" }, 195 // mng_underdog_win: { name: "AM Underdog Win", icon: "fas fa-trophy" }, 196 // mng_underdog_draw: { name: "AM Underdog Draw", icon: "fas fa-trophy" }, 197 // mng_clean_sheets: { name: "AM Clean Sheet", icon: "fas fa-trophy" }, 198 // mng_goals_scored: { name: "AM Goals Scored", icon: "fas fa-trophy" }, 199}; 200 201let player_stat_types = { 202 minutes: { name: "Minutes", pp: true }, 203 clean_sheets: { name: "Clean Sheets", pp: false }, 204 goals_scored: { name: "Goals", pp: true }, 205 assists: { name: "Assists", pp: true }, 206 bonus: { name: "Bonus", pp: true }, 207 defensive_contribution: { name: "Def Con", pp: true }, 208 saves: { name: "Saves", pp: true }, 209 penalties_saved: { name: "Penalties Saved", pp: false }, 210 goals_conceded: { name: "Goals Conceded", pp: true }, 211 own_goals: { name: "Own Goals", pp: false }, 212 yellow_cards: { name: "Yellow Cards", pp: false }, 213 red_cards: { name: "Red Cards", pp: false }, 214 penalties_missed: { name: "Penalties Missed", pp: false }, 215 // mng_win: { name: "AM Win", pp: false }, 216 // mng_draw: { name: "AM Draw", pp: false }, 217 // mng_loss: { name: "AM Loss", pp: false }, 218 // mng_underdog_win: { name: "AM Underdog Win", pp: false }, 219 // mng_underdog_draw: { name: "AM Underdog Draw", pp: false }, 220 // mng_clean_sheets: { name: "AM Clean Sheets", pp: false }, 221 // mng_goals_scored: { name: "AM Goals Scored", pp: false }, 222}; 223 224function rounded(val, digits = 2) { 225 if (val === undefined || val === "") { 226 return "-"; 227 } else { 228 return parseFloat(val).toFixed(digits); 229 } 230} 231 232function getWithSign(val, digits = 2) { 233 if (val === undefined) { 234 return ""; 235 } 236 if (val >= 0) { 237 return "+" + parseFloat(val).toFixed(digits); 238 } else { 239 return val.toFixed(digits); 240 } 241} 242 243function getSum(arr) { 244 return arr.reduce((a, b) => a + b, 0); 245} 246 247function getAvg(arr) { 248 return arr.reduce((a, b) => a + b, 0) / Math.max(arr.length, 1); 249} 250 251function getMin(arr) { 252 return Math.min(...arr); 253} 254 255function getMax(arr) { 256 return Math.max(...arr); 257} 258 259const downloadToFile = (content, filename, contentType) => { 260 const a = document.createElement("a"); 261 const file = new Blob([content], { type: contentType }); 262 263 a.href = URL.createObjectURL(file); 264 a.download = filename; 265 document.body.append(a); 266 setTimeout(function () { 267 a.click(); 268 a.remove(); 269 setTimeout(() => URL.revokeObjectURL(a.href), 2000); 270 }, 100); 271}; 272 273function get_fpl_main_data() { 274 return new Promise((resolve, reject) => { 275 $.ajax({ 276 type: "GET", 277 url: `${proxy}/fantasy.premierleague.com/api/bootstrap-static/`, 278 dataType: "json", 279 async: true, 280 success: function (data) { 281 resolve(data); 282 }, 283 error: function () { 284 console.log("Cannot get FPL main data"); 285 reject("No data"); 286 }, 287 }); 288 }); 289} 290 291async function read_cached_static(req_season) { 292 return $.ajax({ 293 type: "GET", 294 url: `data/${req_season}/static.json`, 295 async: true, 296 dataType: "json", 297 success: (data) => { 298 return data; 299 }, 300 error: (xhr, status, error) => { 301 console.log(error); 302 console.error(xhr, status, error); 303 }, 304 }); 305} 306 307function get_entire_fixture() { 308 return new Promise((resolve, reject) => { 309 $.ajax({ 310 type: "GET", 311 url: `${proxy}/fantasy.premierleague.com/api/fixtures/`, 312 dataType: "json", 313 async: true, 314 success: function (data) { 315 resolve(data); 316 }, 317 error: function () { 318 console.log("Cannot get fixture"); 319 reject("No data"); 320 }, 321 }); 322 }); 323} 324 325function get_fixture(gw) { 326 return new Promise((resolve, reject) => { 327 $.ajax({ 328 type: "GET", 329 url: `${proxy}/fantasy.premierleague.com/api/fixtures/?event=${gw}`, 330 dataType: "json", 331 async: true, 332 success: function (data) { 333 let filtered_games = data.filter((i) => i.event == gw); 334 resolve(filtered_games); 335 }, 336 error: function () { 337 console.log("Cannot get fixture"); 338 reject("No data"); 339 }, 340 }); 341 }); 342} 343 344function get_sample_data(season, target_gw) { 345 let regular_sample = new Promise((resolve, reject) => { 346 $.ajax({ 347 type: "GET", 348 url: `sample/${season}/${target_gw}/fpl_sampled.json`, 349 dataType: "json", 350 async: true, 351 success: function (data) { 352 resolve(data); 353 }, 354 error: function () { 355 console.log("GW" + target_gw + " has no sample data"); 356 reject("No data"); 357 }, 358 }); 359 }); 360 361 // let prime_sample = new Promise((resolve, reject) => { 362 // $.ajax({ 363 // type: "GET", 364 // url: `sample/${season}/${target_gw}/top_managers.json`, 365 // dataType: "json", 366 // async: true, 367 // success: function(data) { 368 // resolve(data); 369 // }, 370 // error: function() { 371 // console.log("GW" + target_gw + " has no sample data"); 372 // reject("No data"); 373 // } 374 // }); 375 // }); 376 377 let promises = [regular_sample]; //, prime_sample] 378 379 return Promise.allSettled(promises); 380} 381 382// FPL awards real bonus once a fixture reaches finished_provisional, which is 383// hours before `finished` flips on the overnight update. Provisional bonus 384// derived from BPS must only be applied while no real bonus exists
384yet, 385// otherwise it is added on top of the real thing and counted twice. 386function needsProvisionalBonus(game) { 387 if (!game.started || game.finished || game.finished_provisional) { 388 return false; 389 } 390 var awarded = game.stats.find(function (i) { 391 return i.identifier == "bonus"; 392 }); 393 if (awarded && (awarded.h.length > 0 || awarded.a.length > 0)) { 394 return false; 395 } 396 return true; 397} 398 399function get_cached_element_data({ season, gw, date }) { 400 return new Promise((resolve, reject) => { 401 $.ajax({ 402 type: "GET", 403 url: `data/${season}/${gw}/${date}/input/element.csv`, 404 dataType: "text", 405 async: true, 406 success: function (data) { 407 // Proper CSV parsing: element.csv has quoted fields containing 408 // commas (price_change_projections), which a naive split breaks. 409 resolve($.csv.toObjects(data)); 410 }, 411 error: function (xhr, status, error) { 412 console.log(error); 413 console.error(xhr, status, error); 414 reject("No data"); 415 }, 416 }); 417 }); 418} 419 420function get_team_info(team_id) { 421 return new Promise((resolve, reject) => { 422 if (team_id == "-1") { 423 reject("Invalid team ID"); 424 } 425 $.ajax({ 426 type: "GET", 427 url: `${proxy}/fantasy.premierleague.com/api/entry/${team_id}/`, 428 async: true, 429 headers: { 430 "Access-Control-Allow-Origin": "*", 431 "Access-Control-Allow-Methods": "GET", 432 "X-Requested-With": "XMLHttpRequest", 433 "Referrer-Policy": "no-referrer-when-downgrade", 434 }, 435 success: function (data) { 436 resolve(data); 437 }, 438 error: function (xhr, status, error) { 439 reject("Cannot get team info"); 440 }, 441 }); 442 }); 443} 444 445function get_team_history(team_id) { 446 return new Promise((resolve, reject) => { 447 if (team_id == "-1") { 448 reject("Invalid team ID"); 449 } 450 $.ajax({ 451 type: "GET", 452 url: `${proxy}/fantasy.premierleague.com/api/entry/${team_id}/history/`, 453 dataType: "json", 454 async: true, 455 headers: { 456 "Access-Control-Allow-Origin": "*", 457 "Access-Control-Allow-Methods": "GET", 458 "X-Requested-With": "XMLHttpRequest", 459 "Referrer-Policy": "no-referrer-when-downgrade", 460 }, 461 success: function (data) { 462 resolve(data); 463 }, 464 error: function (xhr, status, error) { 465 reject("Cannot get team info"); 466 }, 467 }); 468 }); 469} 470 471function get_team_picks({ gw, team_id, force_last_gw }) { 472 return new Promise((resolve, reject) => { 473 if (team_id == "-1") { 474 reject("Team ID not valid"); 475 } 476 $.ajax({ 477 type: "GET", 478 url: `${proxy}/fantasy.premierleague.com/api/entry/${team_id}/event/${gw}/picks/`, 479 dataType: "json", 480 async: true, 481 headers: { 482 "Access-Control-Allow-Origin": "*", 483 "Access-Control-Allow-Headers": "X-Requested-With", 484 "Access-Control-Allow-Methods": "GET", 485 "X-Requested-With": "XMLHttpRequest", 486 "Referrer-Policy": "no-referrer-when-downgrade", 487 }, 488 success: function (data) { 489 resolve({ body: data, is_last_gw: false }); 490 }, 491 error: function (xhr, status, error) { 492 if (force_last_gw) { 493 let last_gw = "" + (parseInt(gw) - 1); 494 495 $.ajax({ 496 type: "GET", 497 url: `${proxy}/fantasy.premierleague.com/api/entry/${team_id}/event/${last_gw}/picks/`, 498 dataType: "json", 499 async: true, 500 headers: { 501 "Access-Control-Allow-Origin": "*", 502 "Access-Control-Allow-Methods": "GET", 503 "X-Requested-With": "XMLHttpRequest", 504 "Referrer-Policy": "no-referrer-when-downgrade", 505 }, 506 success: function (data) { 507 resolve({ body: data, is_last_gw: true }); 508 }, 509 error: function (xhr, status, error) { 510 reject("Cannot get picks for last GW"); 511 }, 512 }); 513 } else { 514 reject("Cannot get picks for this GW"); 515 } 516 }, 517 }); 518 }); 519} 520 521function teamInfoDetails(team_info, field) { 522 try { 523 switch (field) { 524 case "last_gw_rank_overall": 525 return team_info.leagues.classic.find((i) => i.name == "Overall") 526 .entry_last_rank; 527 case "current_rank": 528 return team_info.leagues.classic.find((i) => i.name == "Overall") 529 .entry_rank; 530 } 531 } catch { 532 return "-"; 533 } 534} 535 536function getXPData({ season, gw, date }) { 537 return new Promise((resolve, reject) => { 538 $.ajax({ 539 type: "GET", 540 url: `data/${season}/${gw}/${date}/input/element_gameweek.csv`, 541 dataType: "text", 542 async: true, 543 success: (data) => { 544 let xp_data = $.csv.toObjects(data); 545 let filtered_data = xp_data.filter((i) => i.event == gw.slice(2)); 546 resolve(filtered_data); 547 }, 548 error: function (xhr, status, error) {
549 reject("Could not get XP data"); 550 }, 551 }); 552 }); 553} 554 555// Seasons where element_gameweek.csv is stored unencrypted 556const UNENCRYPTED_SEASONS = ["2020-21", "2025-26", "2026-27"]; 557 558function getXPData_Fernet({ season, gw, date }) { 559 if (UNENCRYPTED_SEASONS.includes(season)) { 560 return getXPData({ season, gw, date }); 561 } 562 return new Promise((resolve, reject) => { 563 $.ajax({ 564 type: "GET", 565 url: `data/${season}/${gw}/${date}/input/element_gameweek.csv-encrypted`, 566 dataType: "text", 567 async: true, 568 success: (data) => { 569 var secret = new fernet.Secret( 570 "symZ1LvXcAtjllNsDmhSp-GT1gDNPXw0P5eijrU8ogQ=" 571 ); 572 var token = new fernet.Token({ 573 secret: secret, 574 token: data, 575 ttl: 0, 576 }); 577 let raw = token.decode(); 578 let csvdata = $.csv.toObjects(raw); 579 resolve(csvdata.filter((i) => i.event == gw.slice(2))); 580 }, 581 error: () => { 582 reject("Could not get XP data"); 583 }, 584 }); 585 }); 586} 587 588function getProjectionData({ season, gw, date }) { 589 return new Promise((resolve, reject) => { 590 let gwNum = parseInt(gw.replace("GW", "")); 591 $.ajax({ 592 type: "GET", 593 url: `static/projection/gw${gwNum}.csv`, 594 dataType: "text", 595 async: true, 596 success: (data) => { 597 try { 598 let csvdata = $.csv.toObjects(data); 599 // Convert projection format to expected element_gameweek format 600 let convertedData = csvdata.map((row) => ({ 601 player_id: parseInt(row.ID), 602 event: gwNum, 603 event_id: gwNum, // Using GW number as event_id for now 604 web_name: row.Name, 605 points_md: parseFloat(row[`${gwNum}_Pts`] || 0), 606 xmins_md: parseFloat(row[`${gwNum}_xMins`] || 0), 607 team: row.Team, 608 opp_team: "", // Not available in projection data 609 })); 610 resolve(convertedData); 611 } catch (error) { 612 console.error("Error parsing projection data:", error); 613 reject("Could not parse projection data"); 614 } 615 }, 616 error: () => { 617 reject("Could not get projection data"); 618 }, 619 }); 620 }); 621} 622 623function getDetailedData({ season, gw, date }) { 624 return new Promise((resolve, reject) => { 625 $.ajax({ 626 type: "GET", 627 url: `data/${season}/${gw}/${date}/input/detailed-fplreview-free-planner.csv-encrypted`, 628 dataType: "text", 629 async: true, 630 success: (data) => { 631 var secret = new fernet.Secret( 632 "symZ1LvXcAtjllNsDmhSp-GT1gDNPXw0P5eijrU8ogQ=" 633 ); 634 var token = new fernet.Token({ 635 secret: secret, 636 token: data, 637 ttl: 0, 638 }); 639 let raw = token.decode(); 640 let csvdata = $.csv.toObjects(raw); 641 resolve(csvdata); 642 }, 643 error: () => { 644 reject("Could not get detailed data"); 645 }, 646 }); 647 }); 648} 649 650function getSeasonProjection({ season }) { 651 return new Promise((resolve, reject) => { 652 $.ajax({ 653 type: "GET", 654 url: `data/${season}/season_projection.csv`, 655 dataType: "text", 656 async: true, 657 success: (data) => { 658 let csvdata = $.csv.toObjects(data); 659 resolve(csvdata); 660 }, 661 error: () => { 662 reject("Could not get detailed data"); 663 }, 664 }); 665 }); 666} 667 668async function read_cached_xp(req_season) { 669 return $.ajax({ 670 type: "GET", 671 url: `data/${req_season}/xp.csv`, 672 async: true, 673 dataType: "text", 674 success: (data) => { 675 return data; 676 }, 677 error: (xhr, status, error) => { 678 console.log(error); 679 console.error(xhr, status, error); 680 }, 681 }); 682} 683 684function getRPData(gw) { 685 return new Promise((resolve, reject) => { 686 $.ajax({ 687 type: "GET", 688 url: `${proxy}/fantasy.premierleague.com/api/event/${gw}/live/`, 689 dataType: "json", 690 async: true, 691 headers: { 692 "Access-Control-Allow-Origin": "*", 693 "Access-Control-Allow-Methods": "GET", 694 "X-Requested-With": "XMLHttpRequest", 695 "Referrer-Policy": "no-referrer-when-downgrade", 696 }, 697 success: function (data) { 698 resolve(data.elements); 699 }, 700 error: function (xhr, status, error) { 701 reject("Error when getting live RP data"); 702 }, 703 }); 704 }); 705} 706 707function getSeasonRPData(max_gw = 38) { 708 return new Promise((resolve, reject) => {
709 let calls = []; 710 for (gw = 1; gw <= max_gw; gw++) { 711 let current_gw = gw; 712 let call = getRPData(gw) 713 .then((data) => { 714 let d = { 715 gw: current_gw, 716 data: data 717 .filter((i) => i.stats.minutes != 0) 718 .map((i) => { 719 return { id: i.id, e: i.explain }; 720 }), 721 }; 722 return d; 723 }) 724 .catch((e) => { 725 // ignore 726 }); 727 calls.push(call); 728 } 729 730 Promise.allSettled(calls) 731 .then((data) => { 732 let combined = data 733 .filter((i) => i.status == "fulfilled") 734 .map((i) => i.value) 735 .map((i) => [i.gw, i.data]); 736 let all_rp = Object.fromEntries(combined); 737 resolve(all_rp); 738 }) 739 .catch((e) => { 740 reject(e); 741 }); 742 }); 743} 744 745function read_local_file(url) { 746 return new Promise((resolve, reject) => { 747 $.ajax({ 748 type: "GET", 749 url: url, 750 async: true, 751 success: function (data) { 752 resolve(data); 753 }, 754 error: function () { 755 reject("No data"); 756 }, 757 }); 758 }); 759} 760 761function read_local_file_Fernet(url) { 762 return new Promise((resolve, reject) => { 763 $.ajax({ 764 type: "GET", 765 url: url, 766 dataType: "text", 767 async: true, 768 success: (data) => { 769 var secret = new fernet.Secret( 770 "symZ1LvXcAtjllNsDmhSp-GT1gDNPXw0P5eijrU8ogQ=" 771 ); 772 var token = new fernet.Token({ 773 secret: secret, 774 token: data, 775 ttl: 0, 776 }); 777 let raw = token.decode(); 778 resolve(raw); 779 }, 780 error: () => { 781 reject("Could not get XP data"); 782 }, 783 }); 784 }); 785} 786 787async function read_cached_rp(req_season) { 788 return $.ajax({ 789 type: "GET", 790 url: `data/${req_season}/points.json?ts=${ts}`, 791 async: true, 792 dataType: "json", 793 success: (data) => { 794 return data; 795 }, 796 error: (xhr, status, error) => { 797 console.log(error); 798 console.error(xhr, status, error); 799 }, 800 }); 801} 802 803function get_analytics_data({ season, gw }) { 804 return new Promise((resolve, reject) => { 805 $.ajax({ 806 type: "GET", 807 url: `sample/${season}/${gw}/analytics_league.json`, 808 dataType: "json", 809 async: true, 810 success: (data) => { 811 resolve(data); 812 }, 813 error: () => { 814 reject("Could not find league data"); 815 }, 816 }); 817 }); 818} 819 820let rank_formatter = Intl.NumberFormat("en"); 821 822function formatted_rank(value) { 823 return rank_formatter.format(value); 824} 825 826function modify_time(origin, hour) { 827 let t0 = new Date(origin); 828 t0.setHours(t0.getHours() + hour); 829 return t0.getTime(); 830} 831 832function getScreenCoords(x, y, ctm) { 833 var xn = ctm.e + x * ctm.a + y * ctm.c; 834 var yn = ctm.f + x * ctm.b + y * ctm.d; 835 return { x: xn, y: yn }; 836} 837 838function convertToJS(value) { 839 if (value == "true") { 840 return true; 841 } else if (value == "false") { 842 return false; 843 } else { 844 return value; 845 } 846} 847 848function millisecondsToStr(milliseconds) { 849 let temp = milliseconds / 1000; 850 const days = Math.floor((temp %= 31536000) / 86400), 851 hours = Math.floor((temp %= 86400) / 3600), 852 minutes = Math.floor((temp %= 3600) / 60), 853 seconds = temp % 60; 854 855 if (days || hours || seconds || minutes) { 856 return ( 857 (days ? days + " days " : "") + 858 (hours ? hours + " hours " : "") + 859 (minutes ? minutes + " minutes " : "") + 860 Number.parseFloat(seconds).toFixed(0) + 861 " seconds" 862 ); 863 } 864 865 return "< 1 second"; 866} 867 868function exportTableToCSV(selector, filename) { 869 var csv = []; 870 var rows = document.querySelectorAll(selector + " tr"); 871 872 for (var i = 0; i < rows.length; i++) { 873 var row = [], 874 cols = rows[i].querySelectorAll("td, th"); 875 for (var j = 0; j < cols.length; j++) row.push(cols[j].innerText); 876 csv.push(row.join(",")); 877 } 878 downloadCSV(csv.join("\n"), filename); 879} 880 881function downloadCSV(csv, filename) { 882 var csvFile; 883 var downloadLink; 884 885 csvFile = new Blob([csv], { type: "text/csv" }); 886 887 downloadLink = document.createElement("a"); 888 downloadLink.download = filename; 889 downloadLink.href = window.URL.createObjectURL(csvFile); 890 downloadLink.style.display = "none"; 891 document.body.appendChild(downloadLink); 892 downloadLink.click(); 893 setTimeout(() => { 894 downloadLink.remove(); 895 }, 500); 896} 897 898function saveAs(uri, filename) { 899 var link = document.createElement("a"); 900 if (typeof link.download === "string") { 901 link.href = uri; 902 link.download = filename; 903 //Firefox requires the link to be in the body 904 document.body.appendChild(link); 905 //simulate click 906 link.click(); 907 //remove the link when done 908 document.body.removeChild(link); 909 } else { 910 window.open(uri); 911 } 912} 913 914function createTeamFromList( 915 sorted, 916 picks, 917 cap, 918 vice_cap, 919 tc, 920 el_dict, 921 xp_data 922) { 923 if (sorted) { 924 let team = {}; 925 team["picks"] = []; 926 picks.forEach((v, i) => { 927 team.picks.push({ 928 element: v, 929 multiplier: v == cap ? (tc == 1 ? 3 : 2) : i <= 10 ? 1 : 0, 930 is_captain: v == cap, 931 is_vice_captain: v == vice_cap, 932 position: i + 1, 933 }); 934 }); 935 936 let have_cap = team.picks.find((i) => i.is_captain); 937 if (have_cap == undefined) { 938 let e = team.picks.find((i) => !i.is_vice_captain && i.multiplier > 0); 939 e.is_captain = true; 940 e.multiplier = 2; 941 } 942 943 let have_vcap = team.picks.find((i) => i.is_vice_captain); 944 if (have_vcap == undefined) { 945 let e = team.picks.find((i) => !i.is_captain && i.multiplier > 0); 946 e.is_vice_captain = true; 947 } 948 949 return _.cloneDeep(team); 950 } else { 951 let team = {}; 952 team["picks"] = []; 953 // id, element_type, name, xp, xmin_threshold, xp_if_plays 954 let picks_with_xp = picks.map((i) => [ 955 i, 956 el_dict[i].element_type, 957 el_dict[i].web_name, 958 parseFloat((xp_data[i] && xp_data[i][0]) || 0), 959 parseFloat((xp_data[i] && xp_data[i][1]) || 0), 960 parseFloat((xp_data[i] && xp_data[i][2]) || 0), 961 ]); 962 let ordered_picks = _.orderBy(picks_with_xp, ["4", "5"], ["desc", "desc"]); 963 // 1) sort by if played over 60 + points per scenario 964 let position_bounds = { 965 // 1: {'min': 1, 'max': 1}, 966 2: { min: 3, max: 5 }, 967 3: { min: 2, max: 5 }, 968 4: { min: 1, max: 3 }, 969 }; 970 971 let main_gk; 972 let bench_gk; 973 let lineup = []; 974 let bench = []; 975 976 main_gk = ordered_picks.filter((i) => i[1] == 1)[0][0]; 977 bench_gk = ordered_picks.filter((i) => i[1] == 1)[1][0]; 978 979 for (let pos in position_bounds) { 980 if (pos == 1) { 981 continue; 982 } 983 let picked = ordered_picks 984 .filter((i) => i[1] == pos) 985 .slice(0, position_bounds[pos].min); 986 picked.forEach((i) => lineup.push(i[0])); 987 } 988 // 2) then use averages for remaining players 989 let remaining = _.orderBy( 990 ordered_picks.filter((i) => !lineup.includes(i[0]) && i[1] != 1), 991 "3", 992 "desc" 993 ); 994 for (let i = lineup.length; i < 10; i++) { 995 lineup.push(remaining.shift()[0]); 996 } 997 bench = remaining.map((i) => i[0]); 998 999 cap = ordered_picks[0][0]; 1000 vice_cap = 1001 ordered_picks[0][1] == 1 && ordered_picks[1][1] == 1 1002 ? ordered_picks[2][0] 1003 : ordered_picks[1][0]; 1004 let squad = [main_gk].concat(lineup).concat([bench_gk]).concat(bench); 1005
1006 squad.forEach((v, i) => { 1007 team.picks.push({ 1008 element: v, 1009 multiplier: v == cap ? 2 : i <= 10 ? 1 : 0, 1010 is_captain: v == cap, 1011 is_vice_captain: v == vice_cap, 1012 position: i + 1, 1013 }); 1014 }); 1015 1016 return _.cloneDeep(team); 1017 } 1018} 1019 1020function read_xp_storage() { 1021 let raw_data = window.localStorage.getItem("xp_storage"); 1022 if (raw_data) { 1023 let json_data = JSON.parse(raw_data); 1024 return json_data; 1025 } 1026 return undefined; 1027}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.