Skip to content

Instantly share code, notes, and snippets.

@xPaw
Last active December 13, 2025 09:28
Show Gist options
  • Select an option

  • Save xPaw/34eddc8846868be7a6d176e10cc77136 to your computer and use it in GitHub Desktop.

Select an option

Save xPaw/34eddc8846868be7a6d176e10cc77136 to your computer and use it in GitHub Desktop.
Display "friend since" dates on Steam Community friend pages
// ==UserScript==
// @name Steam Friends Since Display
// @namespace xpaw-steam-friends-since-display
// @version 1.0.0
// @description Display "friend since" dates on Steam Community friend pages
// @author Claude
// @match https://steamcommunity.com/id/*/friends*
// @match https://steamcommunity.com/profiles/*/friends*
// @icon https://steamcommunity.com/favicon.ico
// @grant none
// @run-at document-end
// ==/UserScript==
'use strict';
// Inject CSS styles
const style = document.createElement('style');
style.textContent = `
#friends_list {
/* Remove fixed height from friend blocks to accommodate new content */
.friend_block_v2 {
height: auto !important;
.friend_block_content {
margin-bottom: 0;
}
.player_avatar {
display: flex;
margin-bottom: 0;
}
/* Set fixed size for friend avatars */
.player_avatar img {
width: 64px;
height: 64px;
border-radius: 0;
}
/* Style for friend since date */
.friend_since_date {
font-weight: normal;
font-size: 11px;
color: #ababab;
}
}
}
`;
// Create global date formatters
const dateFormatter = new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
/**
* Format Unix timestamp to readable date string with relative time
* @param {number} timestamp - Unix timestamp
* @returns {string} Formatted date string with relative time
*/
function formatDate(timestamp) {
const date = new Date(timestamp * 1000);
const absoluteDate = dateFormatter.format(date);
// Calculate difference for relative time
const now = Date.now();
const diffMs = now - (timestamp * 1000);
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
const diffMonths = Math.floor(diffDays / 30.44);
const diffYears = Math.floor(diffDays / 365.25);
let relativeTime;
if (diffYears >= 1) {
relativeTime = `${diffYears} year${diffYears !== 1 ? 's' : ''}`;
} else if (diffMonths >= 1) {
relativeTime = `${diffMonths} month${diffMonths !== 1 ? 's' : ''}`;
} else {
relativeTime = `${diffDays} day${diffDays !== 1 ? 's' : ''}`;
}
return `${absoluteDate} (${relativeTime})`;
}
/**
* Main function to fetch and display friend since dates
*/
async function displayFriendSinceDates() {
try {
// Parse application config to get access token and API base URL
const applicationConfigElement = document.getElementById('application_config');
if (!applicationConfigElement) {
console.log('[Steam Friends Since] No application config found, user may not be logged in');
return;
}
const applicationConfig = JSON.parse(applicationConfigElement.dataset.config);
const accessToken = JSON.parse(applicationConfigElement.dataset.loyalty_webapi_token);
if (!accessToken) {
console.log('[Steam Friends Since] No access token found, user may not be logged in');
return;
}
const apiBaseUrl = applicationConfig.WEBAPI_BASE_URL || 'https://api.steampowered.com/';
// Build API URL
const params = new URLSearchParams();
params.set('access_token', accessToken);
const apiUrl = `${apiBaseUrl}ISteamUserOAuth/GetFriendList/v1/?${params.toString()}`;
// Fetch friend list
const response = await fetch(apiUrl);
if (!response.ok) {
console.error('[Steam Friends Since] API request failed:', response.status);
return;
}
const data = await response.json();
if (!data.friends || !Array.isArray(data.friends)) {
console.error('[Steam Friends Since] Invalid API response format');
return;
}
// Build map of steamid -> friend_since
const friendMap = new Map();
for (const friend of data.friends) {
if (friend.steamid && friend.friend_since) {
friendMap.set(friend.steamid, friend.friend_since);
}
}
document.head.appendChild(style);
// Process each friend block
const friendBlocks = document.querySelectorAll('#friends_list .friend_block_v2');
for (const block of friendBlocks) {
const steamId = block.dataset.steamid;
if (!steamId) continue;
const friendSince = friendMap.get(steamId);
if (!friendSince) continue;
// Store friend_since as data attribute for sorting
block.dataset.friendSince = friendSince;
// Check if date already added
const blockContent = block.querySelector('.friend_block_content');
if (!blockContent || blockContent.querySelector('.friend_since_date')) continue;
// Create and append date element
const dateDiv = document.createElement('div');
dateDiv.className = 'friend_since_date';
dateDiv.textContent = `Since: ${formatDate(friendSince)}`;
blockContent.appendChild(dateDiv);
}
// Add sort button
addSortButton();
console.log(`[Steam Friends Since] Successfully added dates to ${friendBlocks.length} friend blocks`);
} catch (error) {
console.error('[Steam Friends Since] Error:', error);
}
}
/**
* Add sort button to the page
*/
function addSortButton() {
const searchResults = document.querySelector('.searchBarContainer');
if (!searchResults || document.getElementById('sort_friends_button')) return;
const button = document.createElement('button');
button.id = 'sort_friends_button';
button.className = 'profile_friends manage_link btnv6_blue_hoverfade btn_medium';
const span = document.createElement('span');
span.textContent = 'Sort by oldest friend';
button.appendChild(span);
button.addEventListener('click', sortAndRemoveButton);
searchResults.append(button);
}
/**
* Sort friends and remove the button
*/
function sortAndRemoveButton() {
const button = document.getElementById('sort_friends_button');
const searchResults = document.getElementById('search_results');
if (!searchResults) return;
// Sort by friend since
sortByFriendSince(searchResults);
// Remove the button
button.remove();
}
/**
* Sort friend blocks by friend_since within each group
*/
function sortByFriendSince(container) {
// Get all state blocks and friend blocks
const stateBlocks = Array.from(container.querySelectorAll('.state_block'));
const friendBlocks = Array.from(container.querySelectorAll('.friend_block_v2'));
// Group friends by their state
const groups = {};
for (const block of friendBlocks) {
// Extract group from classes (e.g., "in-game", "online", "offline")
const classList = Array.from(block.classList);
let group = null;
for (const stateBlock of stateBlocks) {
const dataGroup = stateBlock.dataset.group;
if (classList.includes(dataGroup)) {
group = dataGroup;
break;
}
}
if (!group) group = 'ungrouped';
if (!groups[group]) {
groups[group] = [];
}
groups[group].push(block);
}
// Sort each group by friend_since (oldest first)
for (const group in groups) {
groups[group].sort((a, b) => {
const aTime = parseInt(a.dataset.friendSince) || 0;
const bTime = parseInt(b.dataset.friendSince) || 0;
return aTime - bTime; // Oldest first
});
}
// Rebuild the container with sorted groups
container.innerHTML = '';
for (const stateBlock of stateBlocks) {
const dataGroup = stateBlock.dataset.group;
const groupFriends = groups[dataGroup];
if (groupFriends && groupFriends.length > 0) {
container.appendChild(stateBlock);
for (const friend of groupFriends) {
container.appendChild(friend);
}
}
}
// Add any ungrouped friends at the end
if (groups['ungrouped']) {
for (const friend of groups['ungrouped']) {
container.appendChild(friend);
}
}
}
// Run the main function
displayFriendSinceDates();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment