From 3b4217ca4a99fedeb96a654a8bf75ff64cfc0c4c Mon Sep 17 00:00:00 2001 From: Sendou Date: Fri, 11 Oct 2019 20:07:47 +0300 Subject: [PATCH] readded calendar and xleaderboard --- models/player.js | 108 ++-- react-ui/package-lock.json | 5 + react-ui/package.json | 1 + react-ui/public/robots.txt | 2 +- react-ui/src/assets/xleaderboardIcons/all.png | Bin 0 -> 9971 bytes .../src/assets/xleaderboardIcons/blasters.png | Bin 0 -> 2708 bytes .../src/assets/xleaderboardIcons/brellas.png | Bin 0 -> 4334 bytes .../src/assets/xleaderboardIcons/chargers.png | Bin 0 -> 3217 bytes .../src/assets/xleaderboardIcons/dualies.png | Bin 0 -> 4316 bytes .../src/assets/xleaderboardIcons/flex.png | Bin 0 -> 2688 bytes .../src/assets/xleaderboardIcons/rollers.png | Bin 0 -> 4433 bytes .../src/assets/xleaderboardIcons/shooters.png | Bin 0 -> 3446 bytes .../src/assets/xleaderboardIcons/sloshers.png | Bin 0 -> 3315 bytes .../assets/xleaderboardIcons/splatlings.png | Bin 0 -> 3034 bytes react-ui/src/components/calendar/Calendar.js | 77 +++ react-ui/src/components/root/MainMenu.js | 16 +- react-ui/src/components/root/Routes.js | 8 + .../xleaderboard/FlexLeaderboard.js | 72 +++ .../components/xleaderboard/FourWeapons.js | 33 ++ .../xleaderboard/WeaponLeaderboard.js | 77 +++ .../components/xleaderboard/XLeaderboard.js | 14 + .../xleaderboard/XLeaderboardMenu.js | 215 ++++++++ .../xleaderboard/XLeaderboardRoutes.js | 135 +++++ react-ui/src/graphql/queries/links.js | 12 + react-ui/src/graphql/queries/playerInfo.js | 34 ++ react-ui/src/graphql/queries/seachForTrend.js | 16 + .../src/graphql/queries/searchForBuilds.js | 16 + .../queries/searchForBuildsByWeapon.js | 23 + .../graphql/queries/searchForPlacements.js | 40 ++ .../src/graphql/queries/searchForPlayers.js | 12 + react-ui/src/graphql/queries/searchForUser.js | 14 + react-ui/src/graphql/queries/topBlasters.js | 23 + react-ui/src/graphql/queries/topBrellas.js | 23 + react-ui/src/graphql/queries/topChargers.js | 23 + react-ui/src/graphql/queries/topDualies.js | 23 + react-ui/src/graphql/queries/topFlex.js | 15 + react-ui/src/graphql/queries/topPlayers.js | 23 + .../src/graphql/queries/topPlayersOfWeapon.js | 19 + react-ui/src/graphql/queries/topRollers.js | 23 + react-ui/src/graphql/queries/topShooters.js | 23 + react-ui/src/graphql/queries/topSloshers.js | 23 + react-ui/src/graphql/queries/topSplatlings.js | 23 + react-ui/src/graphql/queries/userLean.js | 11 + react-ui/src/index.js | 7 +- react-ui/src/utils/lists.js | 24 + schemas/player.js | 226 +++++---- scripts/.vscode/settings.json | 3 + scripts/xrank.py | 462 +++++++++++------- 48 files changed, 1592 insertions(+), 312 deletions(-) create mode 100644 react-ui/src/assets/xleaderboardIcons/all.png create mode 100644 react-ui/src/assets/xleaderboardIcons/blasters.png create mode 100644 react-ui/src/assets/xleaderboardIcons/brellas.png create mode 100644 react-ui/src/assets/xleaderboardIcons/chargers.png create mode 100644 react-ui/src/assets/xleaderboardIcons/dualies.png create mode 100644 react-ui/src/assets/xleaderboardIcons/flex.png create mode 100644 react-ui/src/assets/xleaderboardIcons/rollers.png create mode 100644 react-ui/src/assets/xleaderboardIcons/shooters.png create mode 100644 react-ui/src/assets/xleaderboardIcons/sloshers.png create mode 100644 react-ui/src/assets/xleaderboardIcons/splatlings.png create mode 100644 react-ui/src/components/calendar/Calendar.js create mode 100644 react-ui/src/components/xleaderboard/FlexLeaderboard.js create mode 100644 react-ui/src/components/xleaderboard/FourWeapons.js create mode 100644 react-ui/src/components/xleaderboard/WeaponLeaderboard.js create mode 100644 react-ui/src/components/xleaderboard/XLeaderboard.js create mode 100644 react-ui/src/components/xleaderboard/XLeaderboardMenu.js create mode 100644 react-ui/src/components/xleaderboard/XLeaderboardRoutes.js create mode 100644 react-ui/src/graphql/queries/links.js create mode 100644 react-ui/src/graphql/queries/playerInfo.js create mode 100644 react-ui/src/graphql/queries/seachForTrend.js create mode 100644 react-ui/src/graphql/queries/searchForBuilds.js create mode 100644 react-ui/src/graphql/queries/searchForBuildsByWeapon.js create mode 100644 react-ui/src/graphql/queries/searchForPlacements.js create mode 100644 react-ui/src/graphql/queries/searchForPlayers.js create mode 100644 react-ui/src/graphql/queries/searchForUser.js create mode 100644 react-ui/src/graphql/queries/topBlasters.js create mode 100644 react-ui/src/graphql/queries/topBrellas.js create mode 100644 react-ui/src/graphql/queries/topChargers.js create mode 100644 react-ui/src/graphql/queries/topDualies.js create mode 100644 react-ui/src/graphql/queries/topFlex.js create mode 100644 react-ui/src/graphql/queries/topPlayers.js create mode 100644 react-ui/src/graphql/queries/topPlayersOfWeapon.js create mode 100644 react-ui/src/graphql/queries/topRollers.js create mode 100644 react-ui/src/graphql/queries/topShooters.js create mode 100644 react-ui/src/graphql/queries/topSloshers.js create mode 100644 react-ui/src/graphql/queries/topSplatlings.js create mode 100644 react-ui/src/graphql/queries/userLean.js create mode 100644 scripts/.vscode/settings.json diff --git a/models/player.js b/models/player.js index c5bebbd89..b64972574 100644 --- a/models/player.js +++ b/models/player.js @@ -1,78 +1,106 @@ -const mongoose = require('mongoose') +const mongoose = require("mongoose") const playerSchema = new mongoose.Schema({ - name: {type: String, required: true }, + name: { type: String, required: true }, unique_id: { type: String, unique: true, required: true }, alias: String, twitter: String, weapons: [String], topTotal: { - type: [{ + type: [ + { type: mongoose.Schema.Types.ObjectId, - ref: 'Placement' - } - ], validate: [arrayLimit, '{PATH} exceeds the limit of 4']}, + ref: "Placement" + } + ], + validate: [arrayLimit, "{PATH} exceeds the limit of 4"] + }, topTotalScore: Number, topShooter: { - type: [{ + type: [ + { type: mongoose.Schema.Types.ObjectId, - ref: 'Placement' - } - ], validate: [arrayLimit, '{PATH} exceeds the limit of 4']}, + ref: "Placement" + } + ], + validate: [arrayLimit, "{PATH} exceeds the limit of 4"] + }, topShooterScore: Number, topBlaster: { - type: [{ + type: [ + { type: mongoose.Schema.Types.ObjectId, - ref: 'Placement' - } - ], validate: [arrayLimit, '{PATH} exceeds the limit of 4']}, + ref: "Placement" + } + ], + validate: [arrayLimit, "{PATH} exceeds the limit of 4"] + }, topBlasterScore: Number, topRoller: { - type: [{ + type: [ + { type: mongoose.Schema.Types.ObjectId, - ref: 'Placement' - } - ], validate: [arrayLimit, '{PATH} exceeds the limit of 4']}, + ref: "Placement" + } + ], + validate: [arrayLimit, "{PATH} exceeds the limit of 4"] + }, topRollerScore: Number, topCharger: { - type: [{ + type: [ + { type: mongoose.Schema.Types.ObjectId, - ref: 'Placement' - } - ], validate: [arrayLimit, '{PATH} exceeds the limit of 4']}, + ref: "Placement" + } + ], + validate: [arrayLimit, "{PATH} exceeds the limit of 4"] + }, topChargerScore: Number, topSlosher: { - type: [{ + type: [ + { type: mongoose.Schema.Types.ObjectId, - ref: 'Placement' - } - ], validate: [arrayLimit, '{PATH} exceeds the limit of 4']}, + ref: "Placement" + } + ], + validate: [arrayLimit, "{PATH} exceeds the limit of 4"] + }, topSlosherScore: Number, topSplatling: { - type: [{ + type: [ + { type: mongoose.Schema.Types.ObjectId, - ref: 'Placement' - } - ], validate: [arrayLimit, '{PATH} exceeds the limit of 4']}, + ref: "Placement" + } + ], + validate: [arrayLimit, "{PATH} exceeds the limit of 4"] + }, topSplatlingScore: Number, topDualies: { - type: [{ + type: [ + { type: mongoose.Schema.Types.ObjectId, - ref: 'Placement' - } - ], validate: [arrayLimit, '{PATH} exceeds the limit of 4']}, + ref: "Placement" + } + ], + validate: [arrayLimit, "{PATH} exceeds the limit of 4"] + }, topDualiesScore: Number, topBrella: { - type: [{ + type: [ + { type: mongoose.Schema.Types.ObjectId, - ref: 'Placement' - } - ], validate: [arrayLimit, '{PATH} exceeds the limit of 4']}, - topBrellaScore: Number + ref: "Placement" + } + ], + validate: [arrayLimit, "{PATH} exceeds the limit of 4"] + }, + topBrellaScore: Number, + weaponsCount: Number }) function arrayLimit(val) { return val.length <= 4 } -module.exports = mongoose.model('Player', playerSchema) \ No newline at end of file +module.exports = mongoose.model("Player", playerSchema) diff --git a/react-ui/package-lock.json b/react-ui/package-lock.json index 420cadab4..b45093d67 100644 --- a/react-ui/package-lock.json +++ b/react-ui/package-lock.json @@ -7958,6 +7958,11 @@ "verror": "1.10.0" } }, + "jstz": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/jstz/-/jstz-2.1.1.tgz", + "integrity": "sha512-8hfl5RD6P7rEeIbzStBz3h4f+BQHfq/ABtoU6gXKQv5OcZhnmrIpG7e1pYaZ8hS9e0mp+bxUj08fnDUbKctYyA==" + }, "jsx-ast-utils": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz", diff --git a/react-ui/package.json b/react-ui/package.json index 408803a98..8e8b8767c 100644 --- a/react-ui/package.json +++ b/react-ui/package.json @@ -6,6 +6,7 @@ "@apollo/react-hooks": "^3.1.1", "apollo-boost": "^0.4.4", "graphql": "^14.5.8", + "jstz": "^2.1.1", "react": "^16.10.1", "react-color": "^2.17.3", "react-dom": "^16.10.1", diff --git a/react-ui/public/robots.txt b/react-ui/public/robots.txt index 01b0f9a10..6f27bb66a 100644 --- a/react-ui/public/robots.txt +++ b/react-ui/public/robots.txt @@ -1,2 +1,2 @@ -# https://www.robotstxt.org/robotstxt.html User-agent: * +Disallow: \ No newline at end of file diff --git a/react-ui/src/assets/xleaderboardIcons/all.png b/react-ui/src/assets/xleaderboardIcons/all.png new file mode 100644 index 0000000000000000000000000000000000000000..5e4edc051a26386f5357bed893d8724df558c61e GIT binary patch literal 9971 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 PNdsuA>Zl-;tRns&>DofG literal 0 HcmV?d00001 diff --git a/react-ui/src/assets/xleaderboardIcons/blasters.png b/react-ui/src/assets/xleaderboardIcons/blasters.png new file mode 100644 index 0000000000000000000000000000000000000000..f462ef570015a546a8f101d4668526cf69462b53 GIT binary patch literal 2708 zcmbtWdpy(YAOB7^moY6du~{lVxuhJw+;f*(N3;p2kV|yB%x$x|%;YwgkW`#5=297o zQ>PP_BnuN!B4OAHt94{7$*rB)`uF$O?~mvEygr}jc|M=_=ktD^_v@SE?d7VhsHq45 zfU>)r^AYi!x-sM-;xE%Sq(?k~;*PjF0`+~!Phvwh(&1MJ0JvWWT?+w=ZG~vJfH(kv zF*gRND~cX2c52{V{PCEm2s|+)_9PG;@;eT1j5~?n{$FDgW3zQHg^vKB2zGaNz>*{Z@JLHbyz^c5=I~`i z!OSrrV{9r)JrEm}v$1tbB}Jj^0rlitPD2jcPC(?m$^U0hk(2B57v>IEC;x~7MeDt` zs~9scPxKV$)9)kOBWT+$2jt~(CYzr4rAzkSvT%WNrhHD6xyrC7c=q3}ifcP^BPrFG zeCm!%>KH>kT#~CpKNxrzAiD?!HOB2{O>7Z^%Kfkha9qY{D@SWZ$&DP_K`n5LSp&bS zBkyV>1j8OQI>S(K!{7`IxFj0kKzU+)UW!eTvS-ZT?Q0a$u~MLOye#it9$$jlW27N1 z>4s5ic;xa|i;L0@3D3MU`1|d*P>a`;*~nKegsa8k;`-uF)Um*9M7X5A*lF|f-0Muw z@;v?-H!%gZEpTiChVb;8iIkFB8yW0*Ov^R~y)0hbnp+0eD$u&N>SBaFLP|0=GxF=& zHf#l~%mJ_Zv2tbGPH`c=#&X(^ZhN4Fgp=Rix0zA%+0{yOeD3||Shg#sddyV{u>$HvP#K@}I z8c8l?_KhHGf|;c24&6R!Z1B-8DI1OpVRg%q_A)nSz*KYGVrsX_)^XGWN|By7NrZGXbF(AR< zz3!!ygM{}Now{-t{5c0qC*Ns7ui8D|WE=DrBS#Tbh}NUWCvHjL`#UsM5Y=fnxH1|{ zaGMh|6m&=lJu?KEaO+~}@_*r_LbqQ*L(6w07uYSXm1(Kd=sjiW%m9G5c{PZJDG46! zpis{AA>t!)_#HabFi10;ErXj_Ki7;M{~X6p^t878ZE0|69@C_IsDR(4Lw&5&{-JLY z^c38x)iK8r3e-8U5=+zOrV$Ux7d5boM zomPJ~vEY8CrI3-Y*1YGP&rQpzeZ$oLPZVG=zhtzf@SH=_yTNm3>1kc`u>iO>B*K+2 zbCJ2Lr=k4Y1kEs?@kQNNph2)U6z=}BGBJ)7GC+;8z1te_dwAGW<<53Aa){7)vcqcX z+`TShTQ2{l4%MC3n6?r@w6eA3E5)a54)#EK&MT#ZAONZQ`O<~L|oD*VSxAKsXf}b#eHtw!5Tg#d+#Wsj|?CCt_2mBH2_!E`U-++3xI#AP7&*H1wFRqT= zd)8eSGnVHNh;tc$JGn@bb+@-{nOw6UUEuW`h{OWRF@@AHSBDD*Dn#NjmU2&9fKTy2b39Y3EhXB+#y7 z!HorQ?1oC*x3^Vd=07ME9+TDjRBZd^an^p3wgWMhd(bMphV-L3p-?m+ePFxU7z?(y zT*T)h>a<9qOz9|A4`zusDm5h=SfIyuxT%P2o^=*T6eCl3QseBy^hMb}rH`B&y|;4I zpPIGmT`QFc?wax-#Cq4zw5deB)ZLbfc=B`W)K;KA&0Qi-_q}m)r_=R2l9c`KtYRB# zemY72oF6?!O^t$$kjpPg0`4we&h?Hb(*6bh3=-V{ literal 0 HcmV?d00001 diff --git a/react-ui/src/assets/xleaderboardIcons/brellas.png b/react-ui/src/assets/xleaderboardIcons/brellas.png new file mode 100644 index 0000000000000000000000000000000000000000..679e27de4a1d6d6bfbb361f96385362817b3c25d GIT binary patch literal 4334 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 I_An{_KfA8zQvd(} literal 0 HcmV?d00001 diff --git a/react-ui/src/assets/xleaderboardIcons/chargers.png b/react-ui/src/assets/xleaderboardIcons/chargers.png new file mode 100644 index 0000000000000000000000000000000000000000..5c7a28ddc52af32446396a617ba8d9d6e56ae960 GIT binary patch literal 3217 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ͅhVfk=c~R1cBa&_8)FWM#r-Cd8f~n zGdx#ITQCykXAixu~mX>{+@IxxL+8ePa~|^K%QKPaW`O5?3EP->3*k( z)JG*nfn6<&V3jOZ+aHBk<96=y_z~I!38CUy95Vvo`aV63kpYTf|5(S?&nY0+$K02w z%fUS&pLEVF?EjIU+WH z^;i|aL5pW~e?Rt+N~HukZ6-QUT+RhlR!E*`OfE`>rru zPOtB5=#^&QW51B(+c)WsWKT zA}C{I=8M!(k|PsGGtFe3hbGYrPNO-?S>P}VmP@e)e;WOnsARiV_(&d-{;BZ z_|rywz98H(j+*$RK3HYl9i*PAG0v-BChfAv+s<_=0NTJ*UW!8FCk*ObHD5Kd9k2|2 zYY6zj21Nw4yW7mY_)Da$0d* z%NkR6S|>Dyx-0+43MBOvE)4Vfy-z?FtZ@)!x*rq1oD|JJ@X_yp-wNPQc)C74IF0JI z(T`;C!m?qw)J%DUu=>=?=>$wqu0k;a<)?o5$z$w((<01|B*ONQmH@zRS0)>2fYdy% zAZ@n;My6B2A!>~Q%xO3SC2p%ES{kBttkOa>#TRaLNUzWMhIBU!#fu_!+9~+`O_o;;%*I)F>im!Onam7Yu$zs7X2pk zp*1$7w>YWpzAl#mz;ewbXCtO($9$tzD$-?P@^EhE6~y8wI#R#SW<3)hrlVVbQR)o> z-ZzM&isg0p^kSC+NC_X%d4EkB_dy16)kw-Et3$9RU#|-=8IKB7%w%9HSp{Rn9fyr5 zm)QG?s{#2mRy+thfsWb3&yj4m?-xP9;XzjuK^fNC( zbdBp0fvU18OvG5uBg>noGeD6Xz9-jO!WoL^x{&j{+YZpbnJh(Z=-iujJ1u#XSA)cl zu46Nno67D1#+4TLOJ=76cXJtNd_E2iY|c_a)}(2g*NH7RpGdPB>ub@Y*UTBD@NW=y zZ3nifK|SLg#aT@FRp)M_mcOv%SKn!k+M!QPa1`^AS_PYk1z%`!B9}0^f7gG`*V>gY zg($0K6I#4+^VCN@ce9QhGHW-I+m1cVDD*rHxSCd#g-#p`?>LgvKkV&Oedivjc0b;I z3E{qi-F7E~8@Sf-N!sWh0jTPzanh%MFKf&7z0!_;?upJ=puGY|j$iSsIJaQTVvig0 zvR6~T6{oF`Xwbigvst1v;v5a-r?j$Lv-IOVwD&#@cs6wkVCvZ}KXk^=f3uZepf7RD zkI#X=Kz<0E6DWn18?_T;3S$4?J`{UV*aUD!qJTmqAl5%s^Q1Gl6o zF6Se04wLk2May_VtVWRn*tC00jm2wh6;1R@bgYF}S}U(M)i(;r8u^j6POiu41mupZ z9Jvr%2m`H{gy{`#Zx>jKJ>x!Lix#P=dlELE-4pl6_bxqda~^&Y@>$$y_6^f3D`(8- zbYxUd95)kGw{qgluN-C*Q;Cvxf>spLZ!!Yci$QpORn!GolCM!O2CVqhIHI-afqKUL z`-Cma2<}-K1lb1%tP)h6EGpvvun6w^Jz@>(p#`nuaZe(?IR4?!zyvY1_og6rt!!7l z=-$Oyp?Zx_^xOVaPSmj!X~(rs%NU!FlX;Vf+yy#sLRbP@w9%a?=>D{h`aj|}1FmN( zgz1(Wxo0}WJ6D@)cKn}AqIdgJIk%pg`;BFFdq~U*{gg}+8etV2uTlyK+7~ld<}-A> T`a78Z^8qk7u{N$WbW8j{ApFJn literal 0 HcmV?d00001 diff --git a/react-ui/src/assets/xleaderboardIcons/flex.png b/react-ui/src/assets/xleaderboardIcons/flex.png new file mode 100644 index 0000000000000000000000000000000000000000..b173e69d6444e06c23d75f25e1d565ac06d3b08e GIT binary patch literal 2688 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 literal 0 HcmV?d00001 diff --git a/react-ui/src/assets/xleaderboardIcons/sloshers.png b/react-ui/src/assets/xleaderboardIcons/sloshers.png new file mode 100644 index 0000000000000000000000000000000000000000..f5e9ebc8c166bce9a81bad839e48d7427bfbf1c8 GIT binary patch literal 3315 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�H1s8nf*7NoCq-98w*13(O1$8{S#Cika)BD*Gr(WNC$U z1klND{owVQgojlV5XZu=l8VR-!>{CZHLcL;L$pSf-4MeiW}4*3Pd_vyK68Z&Rb_W9 z_IFoH|Mn6+mp`Vl$6EHCV`76Ao7FaFUxqcxVnWsXb;PSmgQ;-6@?m*B4uwIu9f zJr{-Os9^&h3dNq|>5J_5mM;?6#2T~|8l3r%yHI3w7nuIpzlJ0$*M7L&YFYzde;1hg zM+T%c-tt_*Owu>RFQ$Uwc&Btmgz)qRO4D^RBtR+9gA=@=g~NR)OGq7?>j8BO%cLzU zA49bl&2*JF4c}TS8y?o=aT27+Ij1pL-Ai~5v=X$+IzxCAEg!W~z&+_Q8V9~_JPMtM z%UgH(=Z(Ms3=ghg4%->_&rNBBHiR|z=`kOp+i9sz-TF-v0XzlD<%5~v(iFwnC3Yyi zZzk1w28{0*T+6Q#Th1f0|E}EMV^QEV4u7>%(SkK_On7!8lWUpB??iOaYv8Z!;p!S> z5CfwFW4u&TzV9#KB(;Rx*_+sTGU!iRFwZE&lUloSlzZ}Rzw81Ce>cKU%Pi6^R&K&q z4us-Lx4JgB-MaKV0bF-`;zI=yPzp?t8yp=09XSNo%HBd#Vf{f;Q}m6JCm}*i*eY}< zEc-Sb_1YfWfjzjKSpEpB5lqVe)#v|9zFP?A(8CIT7CPn~R1w?zCjrjto9LCEb_)L& D0(3pA literal 0 HcmV?d00001 diff --git a/react-ui/src/assets/xleaderboardIcons/splatlings.png b/react-ui/src/assets/xleaderboardIcons/splatlings.png new file mode 100644 index 0000000000000000000000000000000000000000..8d1404abeb25b8850f9dc7a3d904e95550e281b3 GIT binary patch literal 3034 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 S4_dl<1Kx1ogDoBr`2PY#Nyr)i literal 0 HcmV?d00001 diff --git a/react-ui/src/components/calendar/Calendar.js b/react-ui/src/components/calendar/Calendar.js new file mode 100644 index 000000000..b5fcd794b --- /dev/null +++ b/react-ui/src/components/calendar/Calendar.js @@ -0,0 +1,77 @@ +import React, { useState, useEffect, useRef } from "react" +import { Button, Segment } from "semantic-ui-react" +import jstz from "jstz" + +import useWindowDimensions from "../../utils/useWindowDimensions" + +const PageCalendar = () => { + const [showCode, setShowCode] = useState(false) + const { containerWidth } = useWindowDimensions() + const calenderDiv = useRef(null) + + const iFrameHTML = `` + + //This is a weird solution but I couldn't get it working just setting the iframe src from a variable + useEffect(() => { + document.title = "Competitive Calendar - sendou.ink" + calenderDiv.current.innerHTML = iFrameHTML + }, [iFrameHTML]) + + return ( + + ) +} + +export default PageCalendar diff --git a/react-ui/src/components/root/MainMenu.js b/react-ui/src/components/root/MainMenu.js index 6f18e53fa..834b6446d 100644 --- a/react-ui/src/components/root/MainMenu.js +++ b/react-ui/src/components/root/MainMenu.js @@ -19,36 +19,36 @@ const MainMenu = () => { - + Maplist Generator - + Rotation - + Map Planner - + Calendar - + Builds - + Leaderboards - + Top 500 Browser - + Trends diff --git a/react-ui/src/components/root/Routes.js b/react-ui/src/components/root/Routes.js index 13e9e9f64..d0a3b434e 100644 --- a/react-ui/src/components/root/Routes.js +++ b/react-ui/src/components/root/Routes.js @@ -5,6 +5,8 @@ import Loading from "../common/Loading" const MapListGenerator = lazy(() => import("../maps/MapListGenerator")) const Rotations = lazy(() => import("../rotation/Rotations")) const MapPlanner = lazy(() => import("../plans/MapPlanner")) +const Calendar = lazy(() => import("../calendar/Calendar")) +const XLeaderboard = lazy(() => import("../xleaderboard/XLeaderboard")) const Routes = () => { return ( @@ -19,6 +21,12 @@ const Routes = () => { + + + + + + ) diff --git a/react-ui/src/components/xleaderboard/FlexLeaderboard.js b/react-ui/src/components/xleaderboard/FlexLeaderboard.js new file mode 100644 index 000000000..8ec7b1746 --- /dev/null +++ b/react-ui/src/components/xleaderboard/FlexLeaderboard.js @@ -0,0 +1,72 @@ +import React, { useEffect } from "react" +import { Table, Icon, Popup, Segment } from "semantic-ui-react" +import { useQuery } from "@apollo/react-hooks" +import { Link } from "react-router-dom" + +import { topFlex } from "../../graphql/queries/topFlex" +import Error from "../common/Error" +import Loading from "../common/Loading" + +const FlexLeaderboard = () => { + useEffect(() => { + document.title = "Flex Leaderboard - sendou.ink" + }, []) + const result = useQuery(topFlex) + + if (result.loading) { + return ( +
+ +
+ ) + } + if (result.error) { + return + } + const leaderboard = result.data["topFlex"] + + return ( + + + + + Name + + Power + } + content="Flex Power is the amount of unique weapons the player has reached top 500 with. Different kits count as unique weapons but reskins (e.g. Tentatek & Octoshot) don't." + position="bottom center" + /> + + Total Power + + + + + {leaderboard.map((player, index) => ( + + {index + 1} + + + {player.alias ? player.alias : player.name} + + {player.twitter ? ( + + + + ) : null} + + {player.weaponsCount} + {player.topTotalScore} + + ))} + +
+ ) +} + +export default FlexLeaderboard diff --git a/react-ui/src/components/xleaderboard/FourWeapons.js b/react-ui/src/components/xleaderboard/FourWeapons.js new file mode 100644 index 000000000..c3a407759 --- /dev/null +++ b/react-ui/src/components/xleaderboard/FourWeapons.js @@ -0,0 +1,33 @@ +import React from "react" +import { Popup } from "semantic-ui-react" +import weaponDict from "../../utils/english_internal.json" +import { modes, months } from "../../utils/lists" + +const FourWeapons = ({ weapons }) => { + return ( + <> + {weapons.map(w => { + const mode = modes[w.mode] + const month = months[w.month] + return ( + + } + header={w.name} + content={`${w.x_power} (${w.rank} ${mode} - ${month} ${w.year})`} + /> + ) + })} + + ) +} + +export default FourWeapons diff --git a/react-ui/src/components/xleaderboard/WeaponLeaderboard.js b/react-ui/src/components/xleaderboard/WeaponLeaderboard.js new file mode 100644 index 000000000..4e25cd6ca --- /dev/null +++ b/react-ui/src/components/xleaderboard/WeaponLeaderboard.js @@ -0,0 +1,77 @@ +import React, { useEffect } from "react" +import { Table, Icon, Popup } from "semantic-ui-react" +import { useQuery } from "@apollo/react-hooks" +import { Link } from "react-router-dom" + +import FourWeapons from "./FourWeapons" +import Loading from "../common/Loading" +import Error from "../common/Error" + +const WeaponLeaderboard = ({ query, queryName, scoreField, weaponsField }) => { + useEffect(() => { + document.title = `${weaponsField.substring(3)} Leaderboard - sendou.ink` + }, [weaponsField]) + + const result = useQuery(query) + + if (result.loading) { + return ( +
+ +
+ ) + } + if (result.error) { + return + } + + const leaderboard = result.data[queryName] + return ( +
+ + + + + Name + + Power + } + content="Leaderboard explanation" + position="bottom center" + /> + + Weapons + + + + + {leaderboard.map((player, index) => ( + + {index + 1} + + + {player.alias ? player.alias : player.name} + + {player.twitter ? ( + + + + ) : null} + + {player[scoreField]} + + + + + ))} + +
+
+ ) +} + +export default WeaponLeaderboard diff --git a/react-ui/src/components/xleaderboard/XLeaderboard.js b/react-ui/src/components/xleaderboard/XLeaderboard.js new file mode 100644 index 000000000..0d1226156 --- /dev/null +++ b/react-ui/src/components/xleaderboard/XLeaderboard.js @@ -0,0 +1,14 @@ +import React from "react" +import XLeaderBoardMenu from "./XLeaderboardMenu" +import XLeaderboardRoutes from "./XLeaderboardRoutes" + +const XLeaderboard = () => { + return ( + <> + + + + ) +} + +export default XLeaderboard diff --git a/react-ui/src/components/xleaderboard/XLeaderboardMenu.js b/react-ui/src/components/xleaderboard/XLeaderboardMenu.js new file mode 100644 index 000000000..17b8385be --- /dev/null +++ b/react-ui/src/components/xleaderboard/XLeaderboardMenu.js @@ -0,0 +1,215 @@ +import React from "react" +import { Menu, Responsive } from "semantic-ui-react" +import { NavLink } from "react-router-dom" + +import allIcon from "../../assets/xleaderboardIcons/all.png" +import flexIcon from "../../assets/xleaderboardIcons/flex.png" +import blasterIcon from "../../assets/xleaderboardIcons/blasters.png" +import brellaIcon from "../../assets/xleaderboardIcons/brellas.png" +import chargerIcon from "../../assets/xleaderboardIcons/chargers.png" +import dualieIcon from "../../assets/xleaderboardIcons/dualies.png" +import rollerIcon from "../../assets/xleaderboardIcons/rollers.png" +import shooterIcon from "../../assets/xleaderboardIcons/shooters.png" +import slosherIcon from "../../assets/xleaderboardIcons/sloshers.png" +import splatlingIcon from "../../assets/xleaderboardIcons/splatlings.png" + +const XLeaderBoardMenu = () => { + const wpnIcon = { + paddingBottom: "5px" + } + return ( +
+
+ + + + All Icon + All + + + + Flex icon + Flex + + + + Shooter Icon + Shooters + + + + Blaster Icon + Blasters + + + + Roller Icon + Rollers + + + + Charger Icon + Chargers + + + + Splatling Icon + Splatlings + + + + Slosher Icon + Sloshers + + + + Dualie Icon + Dualies + + + + Brella Icon + Brellas + + + + + + + All Icon + All + + + + Flex icon + Flex + + + + Shooter Icon + Shooters + + + + Blaster Icon + Blasters + + + + Roller Icon + Rollers + + + + Charger Icon + Chargers + + + + + Splatling Icon + Splatlings + + + + Slosher Icon + Sloshers + + + + Dualie Icon + Dualies + + + + Brella Icon + Brellas + + + +
+
+ ) +} + +export default XLeaderBoardMenu diff --git a/react-ui/src/components/xleaderboard/XLeaderboardRoutes.js b/react-ui/src/components/xleaderboard/XLeaderboardRoutes.js new file mode 100644 index 000000000..f715e9ab9 --- /dev/null +++ b/react-ui/src/components/xleaderboard/XLeaderboardRoutes.js @@ -0,0 +1,135 @@ +import React from "react" +import { Route } from "react-router-dom" +import WeaponLeaderboard from "./WeaponLeaderboard" +import { topTotalPlayers } from "../../graphql/queries/topPlayers" +import { topShooterPlayers } from "../../graphql/queries/topShooters" +import { topBlasterPlayers } from "../../graphql/queries/topBlasters" +import { topBrellaPlayers } from "../../graphql/queries/topBrellas" +import { topChargerPlayers } from "../../graphql/queries/topChargers" +import { topDualiesPlayers } from "../../graphql/queries/topDualies" +import { topRollerPlayers } from "../../graphql/queries/topRollers" +import { topSlosherPlayers } from "../../graphql/queries/topSloshers" +import { topSplatlingPlayers } from "../../graphql/queries/topSplatlings" +import FlexLeaderboard from "./FlexLeaderboard" + +const XLeaderboardRoutes = () => { + return ( + <> + ( + + )} + /> + } + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + + ) +} + +export default XLeaderboardRoutes diff --git a/react-ui/src/graphql/queries/links.js b/react-ui/src/graphql/queries/links.js new file mode 100644 index 000000000..79669ede4 --- /dev/null +++ b/react-ui/src/graphql/queries/links.js @@ -0,0 +1,12 @@ +import { gql } from 'apollo-boost' + +export const links = gql` +{ + links { + title + url + description + type + } +} +` \ No newline at end of file diff --git a/react-ui/src/graphql/queries/playerInfo.js b/react-ui/src/graphql/queries/playerInfo.js new file mode 100644 index 000000000..c554a6d1e --- /dev/null +++ b/react-ui/src/graphql/queries/playerInfo.js @@ -0,0 +1,34 @@ +import { gql } from 'apollo-boost' + +export const playerInfo = gql` +query playerInfo($uid: String, $twitter: String) { + playerInfo(uid: $uid, twitter: $twitter) { + player { + name + weapons + alias + twitter + discord_id + topTotalScore + topShooterScore + topBlasterScore + topRollerScore + topChargerScore + topSlosherScore + topSplatlingScore + topDualiesScore + topBrellaScore + } + placements { + id + name + weapon + rank + mode + x_power + month + year + } + } +} +` \ No newline at end of file diff --git a/react-ui/src/graphql/queries/seachForTrend.js b/react-ui/src/graphql/queries/seachForTrend.js new file mode 100644 index 000000000..3674d2d21 --- /dev/null +++ b/react-ui/src/graphql/queries/seachForTrend.js @@ -0,0 +1,16 @@ +import { gql } from 'apollo-boost' + +export const searchForTrend = gql` +query searchForTrend($weapon: String!) { + searchForTrend(weapon: $weapon) { + weapon + counts { + year + SZ + TC + RM + CB + } + } +} +` \ No newline at end of file diff --git a/react-ui/src/graphql/queries/searchForBuilds.js b/react-ui/src/graphql/queries/searchForBuilds.js new file mode 100644 index 000000000..42349de59 --- /dev/null +++ b/react-ui/src/graphql/queries/searchForBuilds.js @@ -0,0 +1,16 @@ +import { gql } from 'apollo-boost' + +export const searchForBuilds = gql` +query searchForBuilds($discord_id: String!) { + searchForBuilds(discord_id: $discord_id) { + id + weapon + title + headgear + clothing + shoes + updatedAt + top + } +} +` \ No newline at end of file diff --git a/react-ui/src/graphql/queries/searchForBuildsByWeapon.js b/react-ui/src/graphql/queries/searchForBuildsByWeapon.js new file mode 100644 index 000000000..61aaf17c9 --- /dev/null +++ b/react-ui/src/graphql/queries/searchForBuildsByWeapon.js @@ -0,0 +1,23 @@ +import { gql } from 'apollo-boost' + +export const searchForBuildsByWeapon = gql` +query searchForBuildsByWeapon($weapon: String!, $page: Int) { + searchForBuildsByWeapon(weapon: $weapon, page: $page) { + builds { + id + discord_id + top + headgear + clothing + shoes + title + updatedAt + discord_user { + username + discriminator + } + } + pageCount + } +} +` \ No newline at end of file diff --git a/react-ui/src/graphql/queries/searchForPlacements.js b/react-ui/src/graphql/queries/searchForPlacements.js new file mode 100644 index 000000000..47a0e44df --- /dev/null +++ b/react-ui/src/graphql/queries/searchForPlacements.js @@ -0,0 +1,40 @@ +import { gql } from 'apollo-boost' + +export const searchForPlacements = gql` +query searchForPlacements( + $name: String + $weapon: String + $mode: Int + $unique_id: String + $month: Int + $year: Int + $page: Int +) { + searchForPlacements( + name: $name + weapon: $weapon + mode: $mode + unique_id: $unique_id + month: $month + year: $year + page: $page + ) { + placements { + name + x_power + weapon + rank + id + unique_id + month + year + mode + player { + twitter + } + } + pageCount + } +} + +` \ No newline at end of file diff --git a/react-ui/src/graphql/queries/searchForPlayers.js b/react-ui/src/graphql/queries/searchForPlayers.js new file mode 100644 index 000000000..480771eed --- /dev/null +++ b/react-ui/src/graphql/queries/searchForPlayers.js @@ -0,0 +1,12 @@ +import { gql } from 'apollo-boost' + +export const searchForPlayers = gql` +query searchForPlayers($name: String!, $exact: Boolean) { + searchForPlayers(name: $name, exact: $exact) { + name + x_power + weapon + unique_id + } +} +` \ No newline at end of file diff --git a/react-ui/src/graphql/queries/searchForUser.js b/react-ui/src/graphql/queries/searchForUser.js new file mode 100644 index 000000000..cd2043a97 --- /dev/null +++ b/react-ui/src/graphql/queries/searchForUser.js @@ -0,0 +1,14 @@ +import { gql } from 'apollo-boost' + +export const searchForUser = gql` +query searchForUser($discord_id: String) { + searchForUser(discord_id: $discord_id) { + username + discriminator + discord_id + avatar + twitch_name + twitter_name + } +} +` \ No newline at end of file diff --git a/react-ui/src/graphql/queries/topBlasters.js b/react-ui/src/graphql/queries/topBlasters.js new file mode 100644 index 000000000..142820668 --- /dev/null +++ b/react-ui/src/graphql/queries/topBlasters.js @@ -0,0 +1,23 @@ +import { gql } from 'apollo-boost' + +export const topBlasterPlayers = gql` +{ + topBlasterPlayers { + id + unique_id + name + alias + twitter + topBlasterScore + topBlaster { + name + weapon + mode + x_power + rank + month + year + } + } +} +` \ No newline at end of file diff --git a/react-ui/src/graphql/queries/topBrellas.js b/react-ui/src/graphql/queries/topBrellas.js new file mode 100644 index 000000000..3317e29e4 --- /dev/null +++ b/react-ui/src/graphql/queries/topBrellas.js @@ -0,0 +1,23 @@ +import { gql } from 'apollo-boost' + +export const topBrellaPlayers = gql` +{ + topBrellaPlayers { + id + unique_id + name + alias + twitter + topBrellaScore + topBrella { + name + weapon + mode + x_power + rank + month + year + } + } +} +` \ No newline at end of file diff --git a/react-ui/src/graphql/queries/topChargers.js b/react-ui/src/graphql/queries/topChargers.js new file mode 100644 index 000000000..e2e399991 --- /dev/null +++ b/react-ui/src/graphql/queries/topChargers.js @@ -0,0 +1,23 @@ +import { gql } from 'apollo-boost' + +export const topChargerPlayers = gql` +{ + topChargerPlayers { + id + unique_id + name + alias + twitter + topChargerScore + topCharger { + name + weapon + mode + x_power + rank + month + year + } + } +} +` \ No newline at end of file diff --git a/react-ui/src/graphql/queries/topDualies.js b/react-ui/src/graphql/queries/topDualies.js new file mode 100644 index 000000000..74170cb4d --- /dev/null +++ b/react-ui/src/graphql/queries/topDualies.js @@ -0,0 +1,23 @@ +import { gql } from 'apollo-boost' + +export const topDualiesPlayers = gql` +{ + topDualiesPlayers { + id + unique_id + name + alias + twitter + topDualiesScore + topDualies { + name + weapon + mode + x_power + rank + month + year + } + } +} +` \ No newline at end of file diff --git a/react-ui/src/graphql/queries/topFlex.js b/react-ui/src/graphql/queries/topFlex.js new file mode 100644 index 000000000..d304ba9b1 --- /dev/null +++ b/react-ui/src/graphql/queries/topFlex.js @@ -0,0 +1,15 @@ +import { gql } from 'apollo-boost' + +export const topFlex = gql` +{ + topFlex { + id + unique_id + name + alias + twitter + weaponsCount + topTotalScore + } +} +` \ No newline at end of file diff --git a/react-ui/src/graphql/queries/topPlayers.js b/react-ui/src/graphql/queries/topPlayers.js new file mode 100644 index 000000000..51118f663 --- /dev/null +++ b/react-ui/src/graphql/queries/topPlayers.js @@ -0,0 +1,23 @@ +import { gql } from 'apollo-boost' + +export const topTotalPlayers = gql` +{ + topTotalPlayers { + id + unique_id + name + alias + twitter + topTotalScore + topTotal { + name + weapon + mode + x_power + rank + month + year + } + } +} +` \ No newline at end of file diff --git a/react-ui/src/graphql/queries/topPlayersOfWeapon.js b/react-ui/src/graphql/queries/topPlayersOfWeapon.js new file mode 100644 index 000000000..5fee09dc2 --- /dev/null +++ b/react-ui/src/graphql/queries/topPlayersOfWeapon.js @@ -0,0 +1,19 @@ +import { gql } from 'apollo-boost' + +export const topPlayersOfWeapon = gql` +query topPlayers($weapon: String!) { + topPlayers(weapon: $weapon) { + modeCount + placements { + id + name + rank + mode + x_power + unique_id + month + year + } + } +} +` \ No newline at end of file diff --git a/react-ui/src/graphql/queries/topRollers.js b/react-ui/src/graphql/queries/topRollers.js new file mode 100644 index 000000000..f318883df --- /dev/null +++ b/react-ui/src/graphql/queries/topRollers.js @@ -0,0 +1,23 @@ +import { gql } from 'apollo-boost' + +export const topRollerPlayers = gql` +{ + topRollerPlayers { + id + unique_id + name + alias + twitter + topRollerScore + topRoller { + name + weapon + mode + x_power + rank + month + year + } + } +} +` \ No newline at end of file diff --git a/react-ui/src/graphql/queries/topShooters.js b/react-ui/src/graphql/queries/topShooters.js new file mode 100644 index 000000000..d72713b0d --- /dev/null +++ b/react-ui/src/graphql/queries/topShooters.js @@ -0,0 +1,23 @@ +import { gql } from 'apollo-boost' + +export const topShooterPlayers = gql` +{ + topShooterPlayers { + id + unique_id + name + alias + twitter + topShooterScore + topShooter { + name + weapon + mode + x_power + rank + month + year + } + } +} +` \ No newline at end of file diff --git a/react-ui/src/graphql/queries/topSloshers.js b/react-ui/src/graphql/queries/topSloshers.js new file mode 100644 index 000000000..4924869ca --- /dev/null +++ b/react-ui/src/graphql/queries/topSloshers.js @@ -0,0 +1,23 @@ +import { gql } from 'apollo-boost' + +export const topSlosherPlayers = gql` +{ + topSlosherPlayers { + id + unique_id + name + alias + twitter + topSlosherScore + topSlosher { + name + weapon + mode + x_power + rank + month + year + } + } +} +` \ No newline at end of file diff --git a/react-ui/src/graphql/queries/topSplatlings.js b/react-ui/src/graphql/queries/topSplatlings.js new file mode 100644 index 000000000..4f4520e17 --- /dev/null +++ b/react-ui/src/graphql/queries/topSplatlings.js @@ -0,0 +1,23 @@ +import { gql } from 'apollo-boost' + +export const topSplatlingPlayers = gql` +{ + topSplatlingPlayers { + id + unique_id + name + alias + twitter + topSplatlingScore + topSplatling { + name + weapon + mode + x_power + rank + month + year + } + } +} +` \ No newline at end of file diff --git a/react-ui/src/graphql/queries/userLean.js b/react-ui/src/graphql/queries/userLean.js new file mode 100644 index 000000000..039883f51 --- /dev/null +++ b/react-ui/src/graphql/queries/userLean.js @@ -0,0 +1,11 @@ +import { gql } from 'apollo-boost' + +export const userLean = gql` +{ + user { + username + avatar + discord_id + } +} +` \ No newline at end of file diff --git a/react-ui/src/index.js b/react-ui/src/index.js index a1e270e80..c0dd40471 100644 --- a/react-ui/src/index.js +++ b/react-ui/src/index.js @@ -8,7 +8,12 @@ import { BrowserRouter as Router } from "react-router-dom" import "semantic-ui-css/semantic.min.css" import "./index.css" -const client = new ApolloClient({ uri: "https://www.sendou.ink/graphql" }) +const client = new ApolloClient({ + uri: + process.env.REACT_APP_APOLLO_URI === "local" + ? "http://localhost:3001/graphql" + : "https://www.sendou.ink/graphql" +}) ReactDOM.render( diff --git a/react-ui/src/utils/lists.js b/react-ui/src/utils/lists.js index 28c7c2f6c..9ed6e434d 100644 --- a/react-ui/src/utils/lists.js +++ b/react-ui/src/utils/lists.js @@ -1,3 +1,27 @@ +export const modes = [ + "", + "Splat Zones", + "Tower Control", + "Rainmaker", + "Clam Blitz" +] + +export const months = [ + "", + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December" +] + export const weapons = [ "Sploosh-o-matic", "Neo Sploosh-o-matic", diff --git a/schemas/player.js b/schemas/player.js index a6b3b4184..5e880aaed 100644 --- a/schemas/player.js +++ b/schemas/player.js @@ -1,25 +1,27 @@ -const { UserInputError, AuthenticationError, gql } = require('apollo-server-express') -const Player = require('../models/player') -const User = require('../models/user') +const { + UserInputError, + AuthenticationError, + gql +} = require("apollo-server-express") +const Player = require("../models/player") +const User = require("../models/user") const typeDef = gql` extend type Query { playerCount: Int! - topTotalPlayers (amount: Int): [Player!]! - topShooterPlayers (amount: Int): [Player!]! - topBlasterPlayers (amount: Int): [Player!]! - topRollerPlayers (amount: Int): [Player!]! - topChargerPlayers (amount: Int): [Player!]! - topSlosherPlayers (amount: Int): [Player!]! - topSplatlingPlayers (amount: Int): [Player!]! - topDualiesPlayers (amount: Int): [Player!]! - topBrellaPlayers (amount: Int): [Player!]! + topTotalPlayers(amount: Int): [Player!]! + topShooterPlayers(amount: Int): [Player!]! + topBlasterPlayers(amount: Int): [Player!]! + topRollerPlayers(amount: Int): [Player!]! + topChargerPlayers(amount: Int): [Player!]! + topSlosherPlayers(amount: Int): [Player!]! + topSplatlingPlayers(amount: Int): [Player!]! + topDualiesPlayers(amount: Int): [Player!]! + topBrellaPlayers(amount: Int): [Player!]! + topFlex: [Player!]! } extend type Mutation { - updateTwitter( - unique_id: String! - twitter: String! - ): Boolean + updateTwitter(unique_id: String!, twitter: String!): Boolean } type Player { id: ID! @@ -47,30 +49,38 @@ const typeDef = gql` topDualiesScore: Float topBrella: [Placement] topBrellaScore: Float + weaponsCount: Int } ` const resolvers = { Query: { playerCount: () => Player.collection.countDocuments(), + topFlex: async (root, args) => { + return Player.find({}) + .sort({ weaponsCount: "desc", topTotalScore: "desc" }) + .limit(50) + }, topTotalPlayers: (root, args) => { if (!args.amount) { args.amount = 50 } if (args.amount < 1 || args.amount > 50) { - throw new UserInputError('amount requested has to be between 1 and 50', { - invalidArgs: args, - }) + throw new UserInputError( + "amount requested has to be between 1 and 50", + { + invalidArgs: args + } + ) } - - return Player - .find({ topTotalScore: { $ne: null} }) - .sort({ "topTotalScore": "desc" }) + + return Player.find({ topTotalScore: { $ne: null } }) + .sort({ topTotalScore: "desc" }) .limit(args.amount) - .populate("topTotal", {"unique_id": 0}) + .populate("topTotal", { unique_id: 0 }) .catch(e => { throw new UserInputError(e.message, { - invalidArgs: args, + invalidArgs: args }) }) }, @@ -80,16 +90,18 @@ const resolvers = { } if (args.amount < 1 || args.amount > 50) { - throw new UserInputError('amount requested has to be between 1 and 50', { - invalidArgs: args, - }) + throw new UserInputError( + "amount requested has to be between 1 and 50", + { + invalidArgs: args + } + ) } - return Player - .find({ topShooterScore: { $ne: null} }) - .sort({ "topShooterScore": "desc" }) + return Player.find({ topShooterScore: { $ne: null } }) + .sort({ topShooterScore: "desc" }) .limit(args.amount) - .populate("topShooter", {"unique_id": 0}) + .populate("topShooter", { unique_id: 0 }) }, topBlasterPlayers: (root, args) => { if (!args.amount) { @@ -97,16 +109,18 @@ const resolvers = { } if (args.amount < 1 || args.amount > 50) { - throw new UserInputError('amount requested has to be between 1 and 50', { - invalidArgs: args, - }) + throw new UserInputError( + "amount requested has to be between 1 and 50", + { + invalidArgs: args + } + ) } - return Player - .find({ topBlasterScore: { $ne: null} }) - .sort({ "topBlasterScore": "desc" }) + return Player.find({ topBlasterScore: { $ne: null } }) + .sort({ topBlasterScore: "desc" }) .limit(args.amount) - .populate("topBlaster", {"unique_id": 0}) + .populate("topBlaster", { unique_id: 0 }) }, topRollerPlayers: (root, args) => { if (!args.amount) { @@ -114,16 +128,18 @@ const resolvers = { } if (args.amount < 1 || args.amount > 50) { - throw new UserInputError('amount requested has to be between 1 and 50', { - invalidArgs: args, - }) + throw new UserInputError( + "amount requested has to be between 1 and 50", + { + invalidArgs: args + } + ) } - return Player - .find({ topRollerScore: { $ne: null} }) - .sort({ "topRollerScore": "desc" }) + return Player.find({ topRollerScore: { $ne: null } }) + .sort({ topRollerScore: "desc" }) .limit(args.amount) - .populate("topRoller", {"unique_id": 0}) + .populate("topRoller", { unique_id: 0 }) }, topChargerPlayers: (root, args) => { if (!args.amount) { @@ -131,16 +147,18 @@ const resolvers = { } if (args.amount < 1 || args.amount > 50) { - throw new UserInputError('amount requested has to be between 1 and 50', { - invalidArgs: args, - }) + throw new UserInputError( + "amount requested has to be between 1 and 50", + { + invalidArgs: args + } + ) } - return Player - .find({ topChargerScore: { $ne: null} }) - .sort({ "topChargerScore": "desc" }) + return Player.find({ topChargerScore: { $ne: null } }) + .sort({ topChargerScore: "desc" }) .limit(args.amount) - .populate("topCharger", {"unique_id": 0}) + .populate("topCharger", { unique_id: 0 }) }, topSlosherPlayers: (root, args) => { if (!args.amount) { @@ -148,16 +166,18 @@ const resolvers = { } if (args.amount < 1 || args.amount > 50) { - throw new UserInputError('amount requested has to be between 1 and 50', { - invalidArgs: args, - }) + throw new UserInputError( + "amount requested has to be between 1 and 50", + { + invalidArgs: args + } + ) } - return Player - .find({ topSlosherScore: { $ne: null} }) - .sort({ "topSlosherScore": "desc" }) + return Player.find({ topSlosherScore: { $ne: null } }) + .sort({ topSlosherScore: "desc" }) .limit(args.amount) - .populate("topSlosher", {"unique_id": 0}) + .populate("topSlosher", { unique_id: 0 }) }, topSplatlingPlayers: (root, args) => { if (!args.amount) { @@ -165,16 +185,18 @@ const resolvers = { } if (args.amount < 1 || args.amount > 50) { - throw new UserInputError('amount requested has to be between 1 and 50', { - invalidArgs: args, - }) + throw new UserInputError( + "amount requested has to be between 1 and 50", + { + invalidArgs: args + } + ) } - return Player - .find({ topSplatlingScore: { $ne: null} }) - .sort({ "topSplatlingScore": "desc" }) + return Player.find({ topSplatlingScore: { $ne: null } }) + .sort({ topSplatlingScore: "desc" }) .limit(args.amount) - .populate("topSplatling", {"unique_id": 0}) + .populate("topSplatling", { unique_id: 0 }) }, topDualiesPlayers: (root, args) => { if (!args.amount) { @@ -182,16 +204,18 @@ const resolvers = { } if (args.amount < 1 || args.amount > 50) { - throw new UserInputError('amount requested has to be between 1 and 50', { - invalidArgs: args, - }) + throw new UserInputError( + "amount requested has to be between 1 and 50", + { + invalidArgs: args + } + ) } - return Player - .find({ topDualiesScore: { $ne: null} }) - .sort({ "topDualiesScore": "desc" }) + return Player.find({ topDualiesScore: { $ne: null } }) + .sort({ topDualiesScore: "desc" }) .limit(args.amount) - .populate("topDualies", {"unique_id": 0}) + .populate("topDualies", { unique_id: 0 }) }, topBrellaPlayers: (root, args) => { if (!args.amount) { @@ -199,29 +223,34 @@ const resolvers = { } if (args.amount < 1 || args.amount > 50) { - throw new UserInputError('amount requested has to be between 1 and 50', { - invalidArgs: args, - }) + throw new UserInputError( + "amount requested has to be between 1 and 50", + { + invalidArgs: args + } + ) } - return Player - .find({ topBrellaScore: { $ne: null} }) - .sort({ "topBrellaScore": "desc" }) + return Player.find({ topBrellaScore: { $ne: null } }) + .sort({ topBrellaScore: "desc" }) .limit(args.amount) - .populate("topBrella", {"unique_id": 0}) - }, + .populate("topBrella", { unique_id: 0 }) + } }, Mutation: { updateTwitter: async (root, args, ctx) => { - if (ctx.user.discord_id !== process.env.ADMIN_ID) throw new AuthenticationError('not admin') + if (ctx.user.discord_id !== process.env.ADMIN_ID) + throw new AuthenticationError("not admin") - const player = await Player.findOne({ unique_id: args.unique_id.trim() }).catch(e => { + const player = await Player.findOne({ + unique_id: args.unique_id.trim() + }).catch(e => { throw new UserInputError(e.message, { - invalidArgs: args, + invalidArgs: args }) }) - if (!player) throw new UserInputError + if (!player) throw new UserInputError() player.twitter = args.twitter.trim().toLowerCase() @@ -229,26 +258,27 @@ const resolvers = { await player.save() } catch (error) { throw new UserInputError(error.message, { - invalidArgs: args, + invalidArgs: args }) } - + return true } }, Player: { - discord_id: async (root) => { + discord_id: async root => { if (!root.twitter) return null - const user = await User - .findOne({ twitter_name: root.twitter }) - .catch(e => { - throw new UserInputError, { - invalidArgs: args, - } - }) + const user = await User.findOne({ twitter_name: root.twitter }).catch( + e => { + throw (new UserInputError(), + { + invalidArgs: args + }) + } + ) if (!user) return null - + return user.discord_id } } @@ -257,4 +287,4 @@ const resolvers = { module.exports = { Player: typeDef, playerResolvers: resolvers -} \ No newline at end of file +} diff --git a/scripts/.vscode/settings.json b/scripts/.vscode/settings.json new file mode 100644 index 000000000..44aef9f94 --- /dev/null +++ b/scripts/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.formatting.provider": "black" +} \ No newline at end of file diff --git a/scripts/xrank.py b/scripts/xrank.py index cf86115fd..63de64b6f 100644 --- a/scripts/xrank.py +++ b/scripts/xrank.py @@ -5,50 +5,158 @@ import calendar import pymongo from config import uri -shooters = ["Sploosh-o-matic", "Neo Sploosh-o-matic", "Sploosh-o-matic 7", -"Splattershot Jr.", "Custom Splattershot Jr.", "Kensa Splattershot Jr.", -"Splash-o-matic", "Neo Splash-o-matic", "Aerospray MG", "Aerospray RG", -"Aerospray PG", "Splattershot", "Tentatek Splattershot", "Kensa Splattershot", -".52 Gal", ".52 Gal Deco", "Kensa .52 Gal", "N-ZAP '85", "N-ZAP '89", -"N-ZAP '83", "Splattershot Pro", "Forge Splattershot Pro", "Kensa Splattershot Pro", -".96 Gal", ".96 Gal Deco", "Jet Squelcher", "Custom Jet Squelcher", -"L-3 Nozzlenose", "L-3 Nozzlenose D", "Kensa L-3 Nozzlenose", -"H-3 Nozzlenose", "H-3 Nozzlenose D", "Cherry H-3 Nozzlenose", "Squeezer", -"Foil Squeezer"] +shooters = [ + "Sploosh-o-matic", + "Neo Sploosh-o-matic", + "Sploosh-o-matic 7", + "Splattershot Jr.", + "Custom Splattershot Jr.", + "Kensa Splattershot Jr.", + "Splash-o-matic", + "Neo Splash-o-matic", + "Aerospray MG", + "Aerospray RG", + "Aerospray PG", + "Splattershot", + "Tentatek Splattershot", + "Kensa Splattershot", + ".52 Gal", + ".52 Gal Deco", + "Kensa .52 Gal", + "N-ZAP '85", + "N-ZAP '89", + "N-ZAP '83", + "Splattershot Pro", + "Forge Splattershot Pro", + "Kensa Splattershot Pro", + ".96 Gal", + ".96 Gal Deco", + "Jet Squelcher", + "Custom Jet Squelcher", + "L-3 Nozzlenose", + "L-3 Nozzlenose D", + "Kensa L-3 Nozzlenose", + "H-3 Nozzlenose", + "H-3 Nozzlenose D", + "Cherry H-3 Nozzlenose", + "Squeezer", + "Foil Squeezer", +] -blasters = ["Luna Blaster", "Luna Blaster Neo", "Kensa Luna Blaster", -"Blaster", "Custom Blaster", "Range Blaster", "Custom Range Blaster", -"Grim Range Blaster", "Rapid Blaster", "Rapid Blaster Deco", "Kensa Rapid Blaster", -"Rapid Blaster Pro", "Rapid Blaster Pro Deco", "Clash Blaster", "Clash Blaster Neo"] +blasters = [ + "Luna Blaster", + "Luna Blaster Neo", + "Kensa Luna Blaster", + "Blaster", + "Custom Blaster", + "Range Blaster", + "Custom Range Blaster", + "Grim Range Blaster", + "Rapid Blaster", + "Rapid Blaster Deco", + "Kensa Rapid Blaster", + "Rapid Blaster Pro", + "Rapid Blaster Pro Deco", + "Clash Blaster", + "Clash Blaster Neo", +] -rollers = ["Carbon Roller", "Carbon Roller Deco", "Splat Roller", "Krak-On Splat Roller", -"Kensa Splat Roller", "Dynamo Roller", "Gold Dynamo Roller", "Kensa Dynamo Roller", -"Flingza Roller", "Foil Flingza Roller", "Inkbrush", "Inkbrush Nouveau", -"Permanent Inkbrush", "Octobrush", "Octobrush Nouveau", "Kensa Octobrush"] +rollers = [ + "Carbon Roller", + "Carbon Roller Deco", + "Splat Roller", + "Krak-On Splat Roller", + "Kensa Splat Roller", + "Dynamo Roller", + "Gold Dynamo Roller", + "Kensa Dynamo Roller", + "Flingza Roller", + "Foil Flingza Roller", + "Inkbrush", + "Inkbrush Nouveau", + "Permanent Inkbrush", + "Octobrush", + "Octobrush Nouveau", + "Kensa Octobrush", +] -chargers = ["Classic Squiffer", "New Squiffer", "Fresh Squiffer", "Splat Charger", -"Firefin Splat Charger", "Kensa Charger", "Splatterscope", "Firefin Splatterscope", -"Kensa Splatterscope", "E-liter 4K", "Custom E-liter 4K", "E-liter 4K Scope", -"Custom E-liter 4K Scope", "Bamboozler 14 Mk I", "Bamboozler 14 Mk II", -"Bamboozler 14 Mk III", "Goo Tuber", "Custom Goo Tuber"] +chargers = [ + "Classic Squiffer", + "New Squiffer", + "Fresh Squiffer", + "Splat Charger", + "Firefin Splat Charger", + "Kensa Charger", + "Splatterscope", + "Firefin Splatterscope", + "Kensa Splatterscope", + "E-liter 4K", + "Custom E-liter 4K", + "E-liter 4K Scope", + "Custom E-liter 4K Scope", + "Bamboozler 14 Mk I", + "Bamboozler 14 Mk II", + "Bamboozler 14 Mk III", + "Goo Tuber", + "Custom Goo Tuber", +] -sloshers = ["Slosher", "Slosher Deco", "Soda Slosher", "Tri-Slosher", -"Tri-Slosher Nouveau", "Sloshing Machine", "Sloshing Machine Neo", -"Kensa Sloshing Machine", "Bloblobber", "Bloblobber Deco", "Explosher", -"Custom Explosher"] +sloshers = [ + "Slosher", + "Slosher Deco", + "Soda Slosher", + "Tri-Slosher", + "Tri-Slosher Nouveau", + "Sloshing Machine", + "Sloshing Machine Neo", + "Kensa Sloshing Machine", + "Bloblobber", + "Bloblobber Deco", + "Explosher", + "Custom Explosher", +] -splatlings = ["Mini Splatling", "Zink Mini Splatling", "Kensa Mini Splatling", -"Heavy Splatling", "Heavy Splatling Deco", "Heavy Splatling Remix", -"Hydra Splatling", "Custom Hydra Splatling", "Ballpoint Splatling", -"Ballpoint Splatling Nouveau", "Nautilus 47", "Nautilus 79"] +splatlings = [ + "Mini Splatling", + "Zink Mini Splatling", + "Kensa Mini Splatling", + "Heavy Splatling", + "Heavy Splatling Deco", + "Heavy Splatling Remix", + "Hydra Splatling", + "Custom Hydra Splatling", + "Ballpoint Splatling", + "Ballpoint Splatling Nouveau", + "Nautilus 47", + "Nautilus 79", +] -dualies = ["Dapple Dualies", "Dapple Dualies Nouveau", "Clear Dapple Dualies", -"Splat Dualies", "Enperry Splat Dualies", "Kensa Splat Dualies", "Glooga Dualies", -"Glooga Dualies Deco", "Kensa Glooga Dualies", "Dualie Squelchers", -"Custom Dualie Squelchers", "Dark Tetra Dualies", "Light Tetra Dualies"] +dualies = [ + "Dapple Dualies", + "Dapple Dualies Nouveau", + "Clear Dapple Dualies", + "Splat Dualies", + "Enperry Splat Dualies", + "Kensa Splat Dualies", + "Glooga Dualies", + "Glooga Dualies Deco", + "Kensa Glooga Dualies", + "Dualie Squelchers", + "Custom Dualie Squelchers", + "Dark Tetra Dualies", + "Light Tetra Dualies", +] -brellas = ["Splat Brella", "Sorella Brella", "Tenta Brella", "Tenta Sorella Brella", -"Tenta Camo Brella", "Undercover Brella", "Undercover Sorella Brella", "Kensa Undercover Brella"] +brellas = [ + "Splat Brella", + "Sorella Brella", + "Tenta Brella", + "Tenta Sorella Brella", + "Tenta Camo Brella", + "Undercover Brella", + "Undercover Sorella Brella", + "Kensa Undercover Brella", +] client = pymongo.MongoClient(uri) db = client.production @@ -57,136 +165,162 @@ script_dir = os.path.dirname(__file__) rel_path = "xrank_data/*.json" abs_file_path = os.path.join(script_dir, rel_path) -year = 2019 # needs to be updated manually when we process data from different years +year = 2019 # needs to be updated manually when we process data from different years + def resolve_top_array(key_name, player, result, x_power): - if key_name not in player: - player[key_name] = [result.inserted_id] - else: - if len(player[key_name]) <= 3: - player[key_name].append(result.inserted_id) + if key_name not in player: + player[key_name] = [result.inserted_id] else: - lowest_power = 10000 - lowest_power_index = -1 - sum_of_powers = x_power - for index, placement_id in enumerate(player[key_name]): - high_placement = db.placements.find_one({ "_id": placement_id }) - if high_placement is None: - raise ValueError(f'Placement id {placement_id} not found in the database.') - sum_of_powers += high_placement["x_power"] - if high_placement["x_power"] < x_power: - if high_placement["x_power"] < lowest_power: - lowest_power = high_placement["x_power"] - lowest_power_index = index - - if lowest_power_index != -1: - player[key_name][lowest_power_index] = result.inserted_id - sum_of_powers -= lowest_power - power_score = round((sum_of_powers / 4), 1) - player[f"{key_name}Score"] = power_score - return player - -for filepath in glob.iglob(abs_file_path): # iterate through .json files in the xrank_data folder - if filepath.endswith(".json"): - path_without_folder = filepath.replace("xrank_data\\", "") - file_parts = path_without_folder.split("_") - print(path_without_folder) - month = list(calendar.month_name).index(file_parts[0].capitalize()) - - if "splat" in file_parts[1]: - mode = 1 - elif "tower" in file_parts[1]: - mode = 2 - elif "rainmaker" in file_parts[1]: - mode = 3 - else: - mode = 4 - with open(filepath) as f: - data = json.load(f) - for placement in data: - if placement["cheater"]: - continue - - rank = placement["rank"] - if rank > 500: - break - - name = placement["name"] - x_power = placement["x_power"] - unique_id = placement["unique_id"] - - weapon = placement["weapon"]["name"].strip() - # If weapon is one of the reskins it gets converted to the regular version - if weapon == "Hero Shot Replica": - weapon = "Splattershot" - elif weapon == "Octo Shot Replica": - weapon = "Tentatek Splattershot" - elif weapon == "Hero Blaster Replica": - weapon = "Blaster" - elif weapon == "Hero Roller Replica": - weapon = "Splat Roller" - elif weapon == "Herobrush Replica": - weapon = "Octobrush" - elif weapon == "Hero Charger Replica": - weapon = "Splat Charger" - elif weapon == "Hero Slosher Replica": - weapon = "Slosher" - elif weapon == "Hero Splatling Replica": - weapon = "Heavy Splatling" - elif weapon == "Hero Dualie Replicas": - weapon = "Splat Dualies" - elif weapon == "Hero Brella Replica": - weapon = "Splat Brella" - - print(f"{month} {year} - {mode} - {name} {unique_id} {rank} {x_power} {weapon}") - - placement_document = {"name": name, - "weapon": weapon, - "rank": rank, - "mode": mode, - "x_power": x_power, - "unique_id": unique_id, - "month": month, - "year": year} - - result = db.placements.insert_one(placement_document) - - player = db.players.find_one({ "unique_id": unique_id }) - - if player is None: - player = {"name": name, - "unique_id": unique_id, - "weapons": [weapon]} + if len(player[key_name]) <= 3: + player[key_name].append(result.inserted_id) else: - player["name"] = name - playerWeapons = player["weapons"] - playerWeapons.append(weapon) - player["weapons"] = list(dict.fromkeys(playerWeapons)) - - player = resolve_top_array("topTotal", player, result, x_power) - - if weapon in shooters: - player = resolve_top_array("topShooter", player, result, x_power) - elif weapon in blasters: - player = resolve_top_array("topBlaster", player, result, x_power) - elif weapon in rollers: - player = resolve_top_array("topRoller", player, result, x_power) - elif weapon in chargers: - player = resolve_top_array("topCharger", player, result, x_power) - elif weapon in sloshers: - player = resolve_top_array("topSlosher", player, result, x_power) - elif weapon in splatlings: - player = resolve_top_array("topSplatling", player, result, x_power) - elif weapon in dualies: - player = resolve_top_array("topDualies", player, result, x_power) - elif weapon in brellas: - player = resolve_top_array("topBrella", player, result, x_power) - else: - raise ValueError(f'Weapon "{weapon}"doesn\'t belong in any category') + lowest_power = 10000 + lowest_power_index = -1 + sum_of_powers = x_power + for index, placement_id in enumerate(player[key_name]): + high_placement = db.placements.find_one({"_id": placement_id}) + if high_placement is None: + raise ValueError( + f"Placement id {placement_id} not found in the database." + ) + sum_of_powers += high_placement["x_power"] + if high_placement["x_power"] < x_power: + if high_placement["x_power"] < lowest_power: + lowest_power = high_placement["x_power"] + lowest_power_index = index - if len(player["topTotal"]) == 1: # if player was just added - db.players.insert_one(player) - else: - db.players.find_one_and_replace({"unique_id": unique_id}, player) + if lowest_power_index != -1: + player[key_name][lowest_power_index] = result.inserted_id + sum_of_powers -= lowest_power + power_score = round((sum_of_powers / 4), 1) + player[f"{key_name}Score"] = power_score + return player - + +for filepath in glob.iglob( + abs_file_path +): # iterate through .json files in the xrank_data folder + if filepath.endswith(".json"): + path_without_folder = filepath.replace("xrank_data\\", "") + file_parts = path_without_folder.split("_") + print(path_without_folder) + month = list(calendar.month_name).index(file_parts[0].capitalize()) + + if "splat" in file_parts[1]: + mode = 1 + elif "tower" in file_parts[1]: + mode = 2 + elif "rainmaker" in file_parts[1]: + mode = 3 + else: + mode = 4 + with open(filepath) as f: + data = json.load(f) + for placement in data: + if placement["cheater"]: + continue + + rank = placement["rank"] + if rank > 500: + break + + name = placement["name"] + x_power = placement["x_power"] + unique_id = placement["unique_id"] + + weapon = placement["weapon"]["name"].strip() + # If weapon is one of the reskins it gets converted to the regular version + if weapon == "Hero Shot Replica": + weapon = "Splattershot" + elif weapon == "Octo Shot Replica": + weapon = "Tentatek Splattershot" + elif weapon == "Hero Blaster Replica": + weapon = "Blaster" + elif weapon == "Hero Roller Replica": + weapon = "Splat Roller" + elif weapon == "Herobrush Replica": + weapon = "Octobrush" + elif weapon == "Hero Charger Replica": + weapon = "Splat Charger" + elif weapon == "Hero Slosher Replica": + weapon = "Slosher" + elif weapon == "Hero Splatling Replica": + weapon = "Heavy Splatling" + elif weapon == "Hero Dualie Replicas": + weapon = "Splat Dualies" + elif weapon == "Hero Brella Replica": + weapon = "Splat Brella" + + print( + f"{month} {year} - {mode} - {name} {unique_id} {rank} {x_power} {weapon}" + ) + + placement_document = { + "name": name, + "weapon": weapon, + "rank": rank, + "mode": mode, + "x_power": x_power, + "unique_id": unique_id, + "month": month, + "year": year, + } + + result = db.placements.insert_one(placement_document) + + player = db.players.find_one({"unique_id": unique_id}) + + if player is None: + player = {"name": name, "unique_id": unique_id, "weapons": [weapon]} + else: + player["name"] = name + playerWeapons = player["weapons"] + playerWeapons.append(weapon) + player["weapons"] = list(dict.fromkeys(playerWeapons)) + + player = resolve_top_array("topTotal", player, result, x_power) + + if weapon in shooters: + player = resolve_top_array("topShooter", player, result, x_power) + elif weapon in blasters: + player = resolve_top_array("topBlaster", player, result, x_power) + elif weapon in rollers: + player = resolve_top_array("topRoller", player, result, x_power) + elif weapon in chargers: + player = resolve_top_array("topCharger", player, result, x_power) + elif weapon in sloshers: + player = resolve_top_array("topSlosher", player, result, x_power) + elif weapon in splatlings: + player = resolve_top_array("topSplatling", player, result, x_power) + elif weapon in dualies: + player = resolve_top_array("topDualies", player, result, x_power) + elif weapon in brellas: + player = resolve_top_array("topBrella", player, result, x_power) + else: + raise ValueError( + f'Weapon "{weapon}"doesn\'t belong in any category' + ) + + if len(player["topTotal"]) == 1: # if player was just added + db.players.insert_one(player) + else: + db.players.find_one_and_replace({"unique_id": unique_id}, player) + +# Updating weaponsCount +players = db.players.find({}) +for document in players: + amount_of_weapons = len(document["weapons"]) + if "weaponsCount" not in document or amount_of_weapons != document["weaponsCount"]: + db.players.update_one( + {"unique_id": document["unique_id"]}, + {"$set": {"weaponsCount": amount_of_weapons}}, + ) + if "weaponsCount" in document: + print( + f"{document['name']} updated! {document['weaponsCount']} -> {amount_of_weapons}" + ) + else: + print(f"New player: {document['name']} with {amount_of_weapons} weapons!") + +print("All done with updating the weaponsCount attributes.")