From e019d3e4587d5c61a7f2fbc0bd5f6119ae033e77 Mon Sep 17 00:00:00 2001 From: Jacky Yang Date: Tue, 27 Jan 2026 19:23:05 +0800 Subject: [PATCH] 1.6.2: Refreshed. --- database/L2/L2_Main.sqlite | Bin 60542976 -> 60542976 bytes database/Web/Web_App.sqlite | Bin 36864 -> 36864 bytes web/services/opponent_service.py | 10 ++++-- web/services/stats_service.py | 55 +++++++++++++++++++++++++++---- 4 files changed, 57 insertions(+), 8 deletions(-) diff --git a/database/L2/L2_Main.sqlite b/database/L2/L2_Main.sqlite index 8b0a7a844dc1ab18c0686d69b94c9a80a3eb67b3..48767a360ee06039912c3baabe01bffc1ca643c5 100644 GIT binary patch delta 5281 zcmaLbXK++i1Bdb1-LTmJfzTld1PCFtur1r@HKBJ1orIRqOA>lX2(Y1MgFt8+Y%~S2 z;6_D*l_)AU1QZb~7OG+aMEyU=%&Q;ret3Sfv$J#NoU`|y*}XZtuSVynK$Ib&FuJQD zAvbkZs^LhgaZkrm^X|dMS)tLE4c3sTTtoAake)-stA#fY>uWLuvdl?(H>GGvvT;lk zYi3qj)|3g>w0UV+X>&8J9=FSFxBGlHkJsU{xqP2G)?c zTtnQwG2_DeS}cJIp&BzTrTDtvVC+<6>(L>sC~>&4Uirg29X6Z8<8U$j>fy$m(4aXY ztw5vCxI5#hd!t8b^svAX%Re*Tm2Mou3w+8%LEppxx$nxx5~?Cm6oKcvBNRHr=iXp0Ju?at$%}CK$t(7CI^>>D`o~4u0d< z^49EiI&Fzwug}Js{ipm!Wl|xkf-?OqN=1St8i6CRSllRBzQs^;P{;e>Ff2RD;xDHAD?n!&HhIu12VlYLptS#;CDs zoJv(`YP_1DCaOtlvPxGOYKoewrm5*_hMK8nso83dnyWHZmdaN1)O@u-EmVutVzopq zRm;?Jm7{Xi3YDi;s#R*W@~eEcMy*xr)Oxi+Z45@16(8I)`_>FoLU zogDDyw$hEFv!^5&7+v1g>GgSRHfN&K;q&(@38+G~N!_P5t1W7)DpK3jcC|z8RJ+vu z>H$@(cB==~L+WAmhdei{px^vOg*j+szd4tbyz*Ao>EV%XVek(tO}|U^_+TMy`YY&7u8Fu zR2@?NWMcdP5yoW$I1!mO7!{R_~~H)k*c9dS88@K2#s6Q|e=NTAfj6)hFsx zbxwVz&a2PW1@(pcQhlYqR^O;^)kXE4`dW=zT{k1MK-%y~y2qp-D3Sfp%us|48gi25u!l4ROg$Sqy)u9H|gj!G= z>Oftn2aym3(NG^6Knyg5M$i~yp$Wu6Q;3IV&>UJoOK1fN&>E~@13Ngt35nnWH+aAc zK4=4Np&hh`4$u)gL1*X!U7;Ivha^ab9?%ndL2u{-eW4%phXF7U2Ekw$0z+XKq`+_( z0V81)jD|5V7REs;q``QY025&nOonvGfGIE)ronWW0W)D1%!WBI7cwCWvSA*~hXt?@ z7Qtdz0!v{TEQcJ(g%ywoD`6F^20!G(8dwYKU_ESrjZgpqD1=RLA8dv#uoa468*GOi zuoHH{{qO)3!)|yG9)gGA5qK2#z+Tt~`{4jQ29Lu*I0R3?VR#arf~VmbI0DZ?5K0WC zI=2j$JVEnr=P8}2@j6jG|M_KN354g()`{v#DXv!1)7ZE;!m`HLpg6YdIYY~a<^7~{ zIov*PVxp%YPahM{!wYZ}UWAvR6pq2m@Cv*Nufgl^1{{YncoW`&6Yw^?1Mk8~cn{u( z58y-i2u{Jra2n3QS@;A#g>oQKch0(=2q!dLJ$d;{OYMfeWBhaccaxCEEs3S5P2 za28p<@H^auTW}lxfIILf{AE~|S7y`;0~o;sAy5I#5DFFugNjfIDnmF_ zfvOMz)u1}mfSOPXYC|2U3-urpq97XTLj#C`hR_HaLo76bIA{v-&MM!2RI=ST;K)|c)Q4-;S_OoGXf4jC{7rouFs4l`gT%!1i4 z2j)U1WI;B}gZZ!k7Q!M}3`<}sEQ95c1G%sQ@?a&bg4N)Md{_f(VI8c84X_akAOMB1 z3GRch=GRC2pU5yG=Vs13h~eknnMd{39TRj zT7wmAUcO6c`R8U?hx!(J%(a!Z=8UG#C#PU?NO{$&d~iFa@Txz18ZR&tcMM-5egsx zg|Nv~s^ia;S5#`g(AJm{THCUjr&ZbUcc1eV8uBaLeRx%zoxa&Lw!A;(KAXes_Iho0 cPk||8vq@}$txyEpU_0zEm6|ekn(n0k4H|u1f&c&j delta 4783 zcmX}wWl&UK1IKZ97g)eTu>%wXOjJ}96AKetF;Gkp3j+ZQvA_hZwb#aOT}*7TySux) z{yx9&jPtzse&)=aJLlXN=bjgL%#wEn#;BzH29M+dbqyYIArnFjJ3@@BYNa{U&S)55 zc64?K@V3hzXRss(7=x|;UQdj!7PrPmPn&B)<0rGtA*9z`v%zfLVmocjZGG?C#^7zK z9c1*d`a9P&xLVqG)RXZ+##po6%nat4wruxDO)@(rg**ORbW5l)*pfZm=x6f@GhQ

{v3Vw)WK8A8al?EY)_q_}OAo zU8d>P4!kkMm>pwt{d@I=$6bQ0Enk{Ut`_%`E}rpj=`Jdp%C1~h4wX~oQn^(gl~?6c z`Bec`P`N30RY(<99;%2cs*0)NiuJuz2~|>+Ql(WHRaSYca;m(lpnOzCRY_G=zN(6< zs{B+nRbACkHB~KDTluRxs;;W1>Z=ASKs8j2RAbddHC4@2plYsKsFtdgYOUI+wyK?K zuYyzu)lqd)omH^vqPnUO)lGF*JycKCOZ8TvDopiJ;i|9dr~0b_YM_cxgVbO(L=9Du zYM6>r(JDp_S0mI&HA;^SjPl)_u_bl3xbIH#iw{gqQpswDnyF@~*=mlmsJUvMny(h9g=&#n ztgLE@TB??*S;!}E9{U>kHSL-o3MW-ks zMXgqA)LOMptyde=Mzu+8R$J6owM}hTJJe2PQ>ki~+O77ey=tG@uhP^3bx<8rht&~v zR2@^tRk}K%PO4Mtv^t~Cs&neRx}YwqOX{+^qOPiI>bkn2ZmL`Awz{M4s(b3bdY~Sv zN9wV9qMoW}>bZKMUaD8>wR)r8s(0$W`k+3lPwKP!qQ0tc>bv@(eyU&UxB8?0Q-7zp zB^VMF7{LT~U=I#p21jrLXUGVdATwlvtl$FKAUn814#)|)AUEWJypRv_LjfoVZr~1u zpfGqq5hx19pg4Gf7nFdKPzp*z87K?hP!7sN1@M82Pzfr7FI0i5;0M*9I@ExgPz!2< zKh%M`P!H-u0|538gf_*J@+tXver%CWhi8Ad~%n;FEK%5u^aZlUf2iwAq@_|K{y15 z;RqasV{jbO;RKw7Q*av2z*#s4=ivfegiCN4uE15e2G`*R+=N?j8}7hexCi&)0X&39 z@ED%JQ+Ni?;RU>eSMVC%z*~3+@8JV{gir7pzQ9-b2H)Wa{Dfcd8~(t5@YgUUA>F8f z0gPY*JFo`_FoPpFfiq-;OpqC}Kvr;pY>*vXAqV7yT#y^`KwiiP`Jn(51UGPpLQohy zpa>L&Vo)4B!3#=2Nhk%Sp$wD-Zzu=lp#u0oMW_Uo!56APRq%sqP#tPOO{fL6!5`{C zU8o23p#cOyLudqzp$RmFW)KL?p#`*rR?r&SKwD@B?I8#{Ku72Vogo;yKvxKXZqOZi zKu_oey&)9BpbvyYU+4$@VE_z-2p9x|VF(O`NEilD5DhUf97e!M7zLvt7REpvjD>M9 z9wxvR z3di6$q{9g~38&yRoPo1&4$i{`xCocvGF*YHa1E}*4Y&!n;5OWWyKoQg!vlB-kKi#p zfv4~cp2G`x39sNayn(my4&K8D_z0iiGkk%s@D0Ah5BLec;5Yn%|KP7t*PlrP0~o;s zc3=+fxM6p@kuVIRAR1y|IE;XiFbYOPER2CT7z^WIJWPOz z5Dy732`0l7mKHyEinBK{U3T(1P%ZI diff --git a/database/Web/Web_App.sqlite b/database/Web/Web_App.sqlite index eaf75f256fc4e06b3c0c38ba4eae750ffdbeefe3..1d393c66b1515ce9ec69808e57448cf1dca2af2b 100644 GIT binary patch delta 149 zcmZozz|^pSX@WFk;Y1l{#=?yW8n*0)Rz^T%I=SE062dk#n|$8Z48%6DGO?J^@8?#P`1%zH+xGqu=&Q5TkS0%!Um?3kJy`n M*alXnW{VsG0C4XnDF6Tf delta 149 zcmZozz|^pSX@WFk+C&*=#qcArY4j7Z7m^eV~fe>ZOx!;gULL0YCyJ$ zft8WTWJ?&^*m823T^dB(&}8yuJ97xzzdgh3!pI2Dnp~m ON9;`@Y$L-(4gmme?ItY% diff --git a/web/services/opponent_service.py b/web/services/opponent_service.py index cdddef2..3549917 100644 --- a/web/services/opponent_service.py +++ b/web/services/opponent_service.py @@ -109,9 +109,12 @@ class OpponentService: # Post-process for derived stats results = [] - for r in rows: + # Resolve avatar fallback from local static if missing + from web.services.stats_service import StatsService + for r in rows or []: d = dict(r) d['win_rate'] = (d['wins'] / d['matches']) if d['matches'] else 0 + d['avatar_url'] = StatsService.resolve_avatar_url(d.get('steam_id_64'), d.get('avatar_url')) results.append(d) return results, total @@ -209,6 +212,9 @@ class OpponentService: info = query_db('l2', "SELECT * FROM dim_players WHERE steam_id_64 = ?", [steam_id], one=True) if not info: return None + from web.services.stats_service import StatsService + player = dict(info) + player['avatar_url'] = StatsService.resolve_avatar_url(steam_id, player.get('avatar_url')) # 2. Match History vs Us (All matches this player played) # We define "Us" as matches where this player is an opponent. @@ -333,7 +339,7 @@ class OpponentService: }) return { - 'player': info, + 'player': player, 'history': processed_history, 'elo_stats': elo_stats, 'side_stats': dict(side_stats) if side_stats else {} diff --git a/web/services/stats_service.py b/web/services/stats_service.py index e82906b..2c4decd 100644 --- a/web/services/stats_service.py +++ b/web/services/stats_service.py @@ -1,7 +1,29 @@ -from web.database import query_db +from web.database import query_db, execute_db +from flask import current_app, url_for +import os class StatsService: @staticmethod + def resolve_avatar_url(steam_id, avatar_url): + try: + if avatar_url and str(avatar_url).strip(): + return avatar_url + base = os.path.join(current_app.root_path, 'static', 'avatars') + # Check jpg/png in order + for ext in ('.jpg', '.png'): + fname = f"{steam_id}{ext}" + if os.path.exists(os.path.join(base, fname)): + url = url_for('static', filename=f'avatars/{fname}') + try: + # Persist fallback URL into L2 for future reads + execute_db('l2', "UPDATE dim_players SET avatar_url = ? WHERE steam_id_64 = ?", [url, str(steam_id)]) + except Exception: + pass + return url + return None + except Exception: + return avatar_url + @staticmethod def get_team_stats_summary(): """ Calculates aggregate statistics for matches where at least 2 roster members played together. @@ -374,7 +396,13 @@ class StatsService: WHERE mp.match_id = ? ORDER BY mp.team_id, mp.rating DESC """ - return query_db('l2', sql, [match_id]) + rows = query_db('l2', sql, [match_id]) + result = [] + for r in rows or []: + d = dict(r) + d['avatar_url'] = StatsService.resolve_avatar_url(d.get('steam_id_64'), d.get('avatar_url')) + result.append(d) + return result @staticmethod def get_match_rounds(match_id): @@ -411,7 +439,12 @@ class StatsService: """ args.extend([per_page, offset]) - players = query_db('l2', sql, args) + rows = query_db('l2', sql, args) + players = [] + for r in rows or []: + d = dict(r) + d['avatar_url'] = StatsService.resolve_avatar_url(d.get('steam_id_64'), d.get('avatar_url')) + players.append(d) total = query_db('l2', f"SELECT COUNT(*) as cnt FROM dim_players WHERE {where_str}", args[:-2], one=True)['cnt'] return players, total @@ -419,7 +452,12 @@ class StatsService: @staticmethod def get_player_info(steam_id): sql = "SELECT * FROM dim_players WHERE steam_id_64 = ?" - return query_db('l2', sql, [steam_id], one=True) + r = query_db('l2', sql, [steam_id], one=True) + if not r: + return None + d = dict(r) + d['avatar_url'] = StatsService.resolve_avatar_url(steam_id, d.get('avatar_url')) + return d @staticmethod def get_daily_match_counts(days=365): @@ -442,7 +480,13 @@ class StatsService: return [] placeholders = ','.join('?' for _ in steam_ids) sql = f"SELECT * FROM dim_players WHERE steam_id_64 IN ({placeholders})" - return query_db('l2', sql, steam_ids) + rows = query_db('l2', sql, steam_ids) + result = [] + for r in rows or []: + d = dict(r) + d['avatar_url'] = StatsService.resolve_avatar_url(d.get('steam_id_64'), d.get('avatar_url')) + result.append(d) + return result @staticmethod def get_player_basic_stats(steam_id): @@ -841,4 +885,3 @@ class StatsService: result[r_num]['economy'][sid] = dict(eco) return result -