PageSourceSearch

https://game-stat.com/wp-content/plugins/steam-player-stats/js/ui.js?ver=1.8.18

js game-stat.com collected 2026-10-02 00:50:13 UTC 11,331 bytes, 324 lines download raw bytes

1// js/ui.js
2// Import versions must be bumped together with STEAM_PLAYER_STATS_VERSION so browser caches bust on release.
3
4import { getLocation, getLocationData } from './location.js?ver=1.8.17';
5
6import { getVisibility } from './visibility.js?ver=1.8.17';
7
8import { getStatus, getStatusWithGame } from './status.js?ver=1.8.17';
9
10import { getSteamId2, getSteamId3, getSteamIdHex } from './steamId2.js?ver=1.8.17';
11
12import { getFlagEmoji } from './flagEmoji.js?ver=1.8.17';
13
14import i18n from './i18n.js?ver=1.8.17';
15
16// Helper function to escape HTML for XSS protection
17const escapeHtml = (text) => {
18  if (!text) return '';
19  const div = document.createElement('div');
20  div.textContent = text;
21  return div.innerHTML;
22};
23
24export const updateRecentPlayers = (players) => {
25  const section = document.getElementById('steam-player-stats-recent');
26
27  if ( ! players || players.length === 0 ) {
28    if ( section ) section.style.display = 'none';
29    return;
30  }
31
32  let html = `<h3 class="steam-player-stats-recent-title">${escapeHtml( i18n.recentPlayers || 'Recently Checked Players' )}</h3>`;
33  html += '<div class="steam-player-stats-recent-grid">';
34
35  players.forEach( ( player ) => {
36    html += `<div class="steam-player-stats-recent-card" data-steamid="${escapeHtml( player.steamid )}">`;
37    html += `<div class="steam-player-stats-recent-avatar"><img src="${escapeHtml( player.avatar )}" alt="${escapeHtml( player.nickname )}" width="64" height="64"></div>`;
38    html += '<div class="steam-player-stats-recent-info">';
39    html += `<span class="steam-player-stats-recent-name">${escapeHtml( player.nickname )}</span>`;
40    html += `<span class="steam-player-stats-recent-steamid">${escapeHtml( player.steamid )}</span>`;
41    html += '</div>';
42    html += `<button class="steam-player-stats-recent-check" type="button">${escapeHtml( i18n.checkAgain || 'Check Again' )}</button>`;
43    html += '</div>';
44  } );
45
46  html += '</div>';
47
48  if ( section ) {
49    section.innerHTML = html;
50    section.style.display = '';
51  } else {
52    const newSection = document.createElement( 'div' );
53    newSection.id = 'steam-player-stats-recent';
54    newSection.className = 'steam-player-stats-recent';
55    newSection.innerHTML = html;
56    const resultsEl = document.getElementById( 'results' );
57    if ( resultsEl ) {
58      resultsEl.insertAdjacentElement( 'afterend', newSection );
59    }
60  }
61};
62
63export const displayPlayerInfo = async (data) => {
64  const userInfo = document.getElementById('user-info');
65  
66  // Get legacy location for backward compatibility
67  const location = getLocation(
68    data.loccountrycode,
69    data.locstatecode,
70    data.loccityid
71  );
72  
73  // Get detailed location data from SteamCountries
74  const locationData = await getLocationData(
75    data.loccountrycode,
76    data.locstatecode,
77    data.loccityid
78  );
79  
80  const flagIcon = getFlagEmoji(data.loccountrycode);
81  const steamId2 = getSteamId2(data.steamid);
82  const steamId3 = getSteamId3(steamId2);
83  const steamHex = getSteamIdHex(data.steamid);
84  const visibility = getVisibility(data.communityvisibilitystate);
85  const status = getStatusWithGame(data.personastate, data.gameextrainfo);
86  
87  // Форматирование времени последнего входа
88  const lastLogoff = data.last_logoff 
89      ? new Date(data.last_logoff * 1000).toLocaleString() 
90      : i18n.unknown;
91  
92  // Исправленное форматирование даты создания аккаунта
93  const accountCreated = data.timecreated && data.timecreated > 0
94      ? new Date(data.timecreated * 1000).toLocaleDateString()
95      : i18n.hidden;
96  
97  // Format ban status display
98  const formatBanStatus = () => {
99    if (data.vacBanned) {
100      return `<span class="ban-status banned">${i18n.vacBanned}: ${data.vacBanCount} ${i18n.bans} (${data.daysSinceLastBan} ${i18n.daysSince})</span>`;
101    } else {
102      return `<span class="ban-status clean">${i18n.noVacBans}</span>`;
103    }
104  };
105  
106  // Format trade ban status
107  const formatTradeBan = () => {
108    if (!data.economyBan || data.economyBan === 'none') {
109      return `<span class="trade-status allowed">${i18n.noTradeBans}</span>`;
110    } else {
111      return `<span class="trade-status banned">${i18n.tradeBanned}: ${data.economyBan}</span>`;
112    }
113  };
114  
115  // Format location display
116  const formatLocationDisplay = () => {
117    if (!locationData.hasData) {
118      return location ? location.toUpperCase() : 'N/A';
119    }
120    
121    const countryDisplay = locationData.country.name || locationData.country.code.toUpperCase();
122    return countryDisplay;
123  };
124  
125  // Format region display
126  const formatRegionDisplay = () => {
127    if (!locationData.hasData || !locationData.state.name) {
128      return i18n.unknown || 'Unknown';
129    }
130    return locationData.state.name;
131  };
132  
133  // Format city display
134  const formatCityDisplay = () => {
135    if (!locationData.hasData || !locationData.city.name) {
136      return i18n.unknown || 'Unknown';
137    }
138    return locationData.city.name;
139  };
140  
141  userInfo.innerHTML = `
142    <div class="card-body">
143      <div class="text-center">
144        <img
145          width="75"
146          height="75"
147          class="user-avatar"
148          loading="lazy"
149          alt="${i18n.avatar} ${data.nickname}"
150          src="${data.avatar}"
151        />
152        <!--<div class="lvl-wrap"><span>${i18n.level}</span> ${
153          data.playerlevel
154            ? `<div class="player-level" style="--text-length: ${data.playerlevel.toString().length}"><span>${data.playerlevel}</span></div>`
155            : 'N/A'
156        } </div>-->
157        <h3>${data.nickname}</h3>
158      </div>
159      
160      <div class="status-section">
161        <div class="ban-info">
162          ${formatBanStatus()}
163        </div>
164        <div class="trade-info">
165          ${formatTradeBan()}
166        </div>
167      </div>
168      
169      <hr />
170      
171      <dl class="row">
172        <dt>${i18n.steamID2}</dt>
173        <dd><span class="copyable">${steamId2}</span></dd>
174        
175        <dt>${i18n.steamID3}</dt>
176        <dd><span class="copyable">${steamId3}</span></dd>
177        
178        <dt>${i18n.steamID64}</dt>
179        <dd><span class="copyable">${data.steamid}</span></dd>
180        
181        <dt>${i18n.steamHex}</dt>
182        <dd><span class="copyable">${steamHex}</span></dd>
183        
184        <dt>${i18n.profileURL}</dt>
185        <dd><span class="copyable">${data.profileurl}</span></dd>
186        
187        <dt>${i18n.realName}</dt>
188        <dd>${data.realname ? data.realname : i18n.hidden}</dd>
189        
190        <dt>${i18n.accountCreated}</dt>
191        <dd>${accountCreated}</dd>
192        
193        <dt>${i18n.visibility}</dt>
194        <dd>${visibility}</dd>
195        
196        <dt>${i18n.status}</dt>
197        <dd>
198          ${status}
199          ${data.gameextrainfo && data.personastate > 0 ? `
200            <div class="current-game-badge">
201              <span class="game-icon">🎮</span>
202              <a href="https://store.steampowered.com/app/${data.gameid}"
203                 target="_blank"
204                 rel="noopener noreferrer"
205                 class="game-link"
206                 title="${i18n.viewInStore || 'View in Steam Store'}">
207                ${escapeHtml(data.gameextrainfo)}
208              </a>
209            </div>
210            ${data.gameserverip ? `
211              <div class="game-server-info">
212                <small>${i18n.gameServer || 'Server'}: ${escapeHtml(data.gameserverip)}</small>
213              </div>
214            ` : ''}
215          ` : ''}
216        </dd>
217
218        <dt>${i18n.location}</dt>
219        <dd><span class="profile-location">${formatLocationDisplay()}</span> <span class="profile-flag">${flagIcon}</span></dd>
220        
221        <dt>${i18n.region}</dt>
222        <dd><span class="profile-region">${formatRegionDisplay()}</span></dd>
223        
224        <dt>${i18n.city}</dt>
225        <dd><span class="profile-city">${formatCityDisplay()}</span></dd>
226        
227        <dt>${i18n.lastLogin}</dt>
228        <dd>${lastLogoff}</dd>
229      </dl>
230    </div>
231
232    <a href="${typeof steamApiData !== 'undefined' && steamApiData.calculator_url ? steamApiData.calculator_url : 'https://game-stat.com/steam/account-calculator/'}"
233       class="sps-calc-banner sps-calc-banner--vivid">
234      <svg width="38" height="38" viewBox="0 0 38 38" fill="none">
235        <circle cx="19" cy="19" r="19" fill="rgba(255,255,255,0.15)"/>
236        <text x="19" y="25" text-anchor="middle" font-size="18" fill="#fff">$</text>
237      </svg>
238      <div class="sps-calc-banner__text">
239        <span class="sps-calc-banner__title">${i18n.calcBannerTitle || 'Steam Account Calculator'}</span>
240        <span class="sps-calc-banner__sub">${i18n.calcBannerSub || 'Total library value · Top games by price · Free vs paid stats'}</span>
241      </div>
242      <span class="sps-calc-banner__btn">${i18n.calcBannerBtn || 'Check'}</span>
243    </a>
244
245    <style>
246      .profile-location, .profile-region, .profile-city {
247        font-weight: 500;
248        color: #444;
249      }
250
251      .profile-region, .profile-city {
252        text-transform: capitalize;
253      }
254
255      /* Current game display styles */
256      .current-game-badge {
257        margin-top: 8px;
258        padding: 8px 12px;
259        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
260        border-radius: 6px;
261        display: inline-flex;
262        align-items: center;
263        gap: 8px;
264      }
265
266      .game-icon {
267        font-size: 16px;
268      }
269
270      .game-link {
271        color: #fff;
272        text-decoration: none;
273        font-weight: 500;
274        transition: opacity 0.2s;
275      }
276
277      .game-link:hover {
278        opacity: 0.8;
279        text-decoration: underline;
280      }
281
282      .game-server-info {
283        margin-top: 4px;
284        color: #666;
285        font-size: 0.9em;
286      }
287    </style>
288  `;
289
290  // Добавляем блок Extended Information с кнопками после основной информации
291  const extendedInfoContainer = document.createElement('div');
292  extendedInfoContainer.className = 'steam-extended-info';
293  extendedInfoContainer.setAttribute('data-steam-id', data.steamid);
294  
295  // Генерируем HTML для блока Extended Information
296  extendedInfoContainer.innerHTML = `
297    <div class="steam-extended-header">
298      <h3 class="steam-extended-title">
299        <i class="steam-icon steam-icon-extended"></i>
300        ${i18n.extendedInformation || 'Extended Information'}
301      </h3>
302    </div>
303    
304    <div class="steam-extended-buttons">
305      <button class="steam-module-btn" data-module="games" data-steam-id="${data.steamid}">
306        <i class="steam-icon steam-icon-games"></i>
307        ${i18n.games || 'Games'}
308      </button>
309      <button class="steam-module-btn" data-module="friends" data-steam-id="${data.steamid}">
310        <i class="steam-icon steam-icon-friends"></i>
311        ${i18n.friends || 'Friends'}
312      </button>
313      <button class="steam-module-btn" data-module="inventory" data-steam-id="${data.steamid}">
314        <i class="steam-icon steam-icon-inventory"></i>
315        ${i18n.inventory || 'Inventory'}
316      </button>
317    </div>
318    
319    <div class="steam-modules-container" id="steam-modules-${data.steamid}"></div>
320  `;
321  
322  // Добавляем контейнер с блоком Extended Information после основной информации
323  userInfo.appendChild(extendedInfoContainer);
324};

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.