// ==UserScript==
// @name Steam 好友动态美化
// @namespace http://tampermonkey.net/
// @version 1.3.5
// @description 美化Steam好友动态页面样式,支持截图/游戏/成就/评测筛选,好友列表悬浮面板,修复游戏封面图显示为SVG的问题
// @author SmallFork
// @match https://steamcommunity.com/id/*/home
// @match https://steamcommunity.com/id/*/myactivity
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// @connect store.steampowered.com
// @run-at document-end
// @license MIT
// @icon data:image/svg+xml,
// @tag Steam
// @tag games
// ==/UserScript==
(function() {
'use strict';
const _ver = typeof GM_info !== 'undefined' ? GM_info.script.version : 'unknown';
console.log(`%c[Steam 好友动态美化] v${_ver} 已启动`, 'color:#66c0f4;font-weight:bold;font-size:13px');
/* ========== 页面全局变量访问 ========== */
// 沙箱环境下 window 可能无法访问页面全局变量,每次动态检测
function getPageVar(name) {
try { if (typeof unsafeWindow !== 'undefined' && unsafeWindow[name] !== undefined) return unsafeWindow[name]; } catch {}
try { if (window[name] !== undefined) return window[name]; } catch {}
try { if (document.defaultView && document.defaultView[name] !== undefined) return document.defaultView[name]; } catch {}
return undefined;
}
/* ========== 配置常量 ========== */
const CONFIG = {
WAIT_TIMEOUT: 15000, // 等待元素超时(ms)
Z_INDEX: 9999, // UI 层级
BTN_SIZE: 30, // 按钮尺寸(px)
PANEL_WIDTH: 300, // 好友面板宽度(px)
PANEL_MAX_H: '80vh', // 好友面板最大高度
MAX_GAME_SHOW: 12, // 游戏购买动态最大封面数
ANIM_DURATION: 250, // 查看器动画时长(ms)
ZOOM_MIN: 0.2, // 最小缩放比例
ZOOM_MAX: 8, // 最大缩放比例
ZOOM_STEP: 0.15, // 缩放步进
DL_IMG_SIZE: 5000 // 下载原图尺寸参数(imw/imh)
};
/* ========== 筛选按钮配置 ========== */
const FILTER_BTNS = [
{ id: 'sf-screenshot-btn', icon: 'screenshot', title: '只看截图', filter: 'screenshot', gradient: 'var(--sf-green),#3d8b40', shadow: '76,175,80' },
{ id: 'sf-game-btn', icon: 'game', title: '只看游戏', filter: 'game', gradient: 'var(--sf-orange),#f57c00', shadow: '255,152,0' },
{ id: 'sf-achievement-btn', icon: 'achievement', title: '只看成就', filter: 'achievement', gradient: '#9c27b0,#7b1fa2', shadow: '156,39,176' },
{ id: 'sf-review-btn', icon: 'review', title: '只看评测', filter: 'review', gradient: '#e91e63,#c2185b', shadow: '233,30,99' },
{ id: 'sf-friend-btn', icon: 'friend', title: '好友列表', filter: null, gradient: 'var(--sf-accent),#4a9bc7', shadow: '102,192,244' }
];
/* ========== 筛选匹配规则 ========== */
const FILTER_MATCHERS = {
screenshot: block => !!block.querySelector('.blotter_screenshot'),
game: block => !!block.querySelector('.blotter_gamepurchase'),
achievement: block => [...block.querySelectorAll('.blotter_daily_rollup_line')]
.some(line => { const t = line.textContent || ''; return t.includes('达成了《') || t.includes('的成就'); }),
review: block => !!block.querySelector('.blotter_recommendation')
};
/* ========== 每日汇总行分类 ========== */
function classifyRollupLine(line) {
const t = line.textContent || '';
if (/成为了好友/.test(t)) return 'friend';
if (/第一次玩/.test(t)) return 'firstplay';
if (/添加到.*愿望单/.test(t)) return 'wishlist';
if (/达成了|的成就/.test(t)) return 'achievement';
return null;
}
function injectRollupStyles() {
document.querySelectorAll('.blotter_daily_rollup_line').forEach(line => {
if (line.dataset.sfClassified) return;
line.dataset.sfClassified = '1';
const cat = classifyRollupLine(line);
if (cat) line.classList.add('sf-rollup-' + cat);
});
}
/* ========== SVG 图标 ========== */
const ICONS = {
friend: '',
close: '',
screenshot: '',
game: '',
achievement: '',
review: '',
download: '',
top: '',
prev: '',
next: ''
};
/* ========== 工具函数 ========== */
const $ = (sel, ctx = document) => ctx.querySelector(sel);
const $$ = (sel, ctx = document) => [...ctx.querySelectorAll(sel)];
const escapeHtml = t => String(t).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"');
/* ========== CSS 变量主题 ========== */
const CSS_VARS = `:root{--sf-bg:#1b2838;--sf-bg2:#171a21;--sf-card:rgba(23,26,33,.8);--sf-border:rgba(255,255,255,.06);--sf-hover:rgba(255,255,255,.08);--sf-text:#c7d5e0;--sf-dim:#8f98a0;--sf-accent:#66c0f4;--sf-green:#4caf50;--sf-orange:#ff9800;--sf-radius:12px;--sf-shadow:0 2px 12px rgba(0,0,0,.2);--sf-transition:all .3s ease}`;
/* ========== 样式表 ========== */
const CSS = `${CSS_VARS}
/* 页面布局 */
.pagecontent,.bluebg{background:linear-gradient(180deg,var(--sf-bg) 0%,var(--sf-bg2) 100%)!important}
#blotter_page{display:block!important;padding:0!important;max-width:940px!important;margin:0 auto!important}
.blotter_content,#blotter_content{float:none!important;width:auto!important;min-width:0!important;max-width:none!important;padding:0 16px!important;box-sizing:border-box!important}
#friendactivity_right_column{display:none!important}
.blotter_block,.blotter_status_entry,.blotter_eventsblock,.friend_activity_header{margin-left:0!important;margin-right:0!important}
.blotter_daily_rollup_line>span{max-width:none!important}
/* 页面标题 & 头部 */
.blotter_page_title h1{font-size:28px!important;font-weight:700!important;color:#fff!important;margin-bottom:20px!important}
.friend_activity_header{padding:20px!important;background:rgba(23,26,33,.6)!important;border-radius:var(--sf-radius)!important;margin-bottom:16px!important}
/* 动态卡片 */
.blotter_block{background:var(--sf-card)!important;border-radius:var(--sf-radius)!important;border:1px solid var(--sf-border)!important;margin-bottom:16px!important;overflow:hidden!important;box-shadow:var(--sf-shadow)!important;transition:var(--sf-transition)!important}
.blotter_block:hover{transform:translateY(-4px)!important;box-shadow:0 8px 24px rgba(0,0,0,.4)!important;border-color:rgba(102,192,244,.2)!important}
.blotter_block.sf-hidden{display:none!important}
/* 作者信息 */
.blotter_author_block{padding:14px 16px!important;background:rgba(0,0,0,.2)!important}
.blotter_avatar_holder .playerAvatar{border-radius:8px!important;box-shadow:0 2px 8px rgba(0,0,0,.3)!important}
.blotter_avatar_holder .playerAvatar img{border-radius:8px!important}
.blotter_author_block a[data-miniprofile]{font-size:14px!important;font-weight:600!important;color:var(--sf-accent)!important}
.blotter_author_block a[data-miniprofile]:hover{color:#fff!important}
/* 截图动态 */
.blotter_screenshot{border-radius:0!important;border:none!important}
.blotter_screenshot_image{border-radius:0!important;overflow:hidden!important}
.blotter_screenshot_image img{transition:transform .3s ease!important}
.blotter_block:hover .blotter_screenshot_image img{transform:scale(1.03)!important}
.blotter_screenshot_title{padding:12px 16px!important;font-size:13px!important;color:var(--sf-text)!important;line-height:1.5!important;background:rgba(0,0,0,.1)!important}
.blotter_screenshot_gallery_image{border-radius:8px!important;margin:4px!important}
/* 游戏状态动态 */
.blotter_userstatus_game{height:auto!important}
.blotter_userstatus_game img{height:auto!important}
.highlight_strip{overflow-x:auto!important;overflow-y:hidden!important}
.highlight_strip_scroll{width:auto!important;display:flex!important;flex-wrap:nowrap!important;overflow-x:auto!important;overflow-y:hidden!important;gap:4px!important;padding:4px!important}
.highlight_strip_scroll::-webkit-scrollbar{height:6px}
.highlight_strip_scroll::-webkit-scrollbar-thumb{background:rgba(255,255,255,.15);border-radius:3px}
.highlight_strip_scroll::-webkit-scrollbar-track{background:transparent}
/* 游戏购买动态 */
.blotter_gamepurchase{border:none!important}
.blotter_gamepurchase_content{padding:16px!important;display:flex!important;flex-wrap:wrap!important;gap:8px!important}
.blotter_gamepurchase_logo{border-radius:8px!important;overflow:hidden!important;transition:transform .2s ease!important;box-shadow:0 2px 8px rgba(0,0,0,.3)!important}
.blotter_gamepurchase_logo:hover{transform:scale(1.05)!important}
.blotter_gamepurchase_logo img{border-radius:8px!important}
.blotter_gamepurchase_logo.single_item{width:200px!important}
.plus_block{background:rgba(255,255,255,.05)!important;border-radius:8px!important;color:var(--sf-dim)!important;font-weight:600!important}
.blotter_gamepurchase_details{padding:12px 16px!important;font-size:12px!important;color:var(--sf-dim)!important}
/* 评测动态 */
.blotter_recommendation{padding:16px!important}
.blotter_recommendation_game{display:flex!important;align-items:center!important;gap:12px!important;margin-bottom:12px!important}
.blotter_recommendation_game img{border-radius:8px!important}
.blotter_recommendation_game_name{font-size:15px!important;font-weight:600!important;color:#fff!important}
.blotter_recommendation_rating{display:flex!important;align-items:center!important;gap:8px!important;margin-bottom:10px!important}
.blotter_recommendation_rating img{width:24px!important;height:24px!important}
.blotter_recommendation_desc{font-size:13px!important;color:var(--sf-text)!important;line-height:1.6!important;padding:12px!important;background:rgba(0,0,0,.15)!important;border-radius:8px!important}
/* 创意工坊动态 */
.blotter_workshopitempublished{border:none!important}
.blotter_workshopitem{padding:16px!important}
/* 外链卡片 */
.dynamiclink_box{border-radius:8px!important;overflow:hidden!important;background:rgba(0,0,0,.3)!important;border:1px solid var(--sf-border)!important;transition:all .2s ease!important}
.dynamiclink_box:hover{border-color:rgba(102,192,244,.3)!important}
.dynamiclink_preview{border-radius:8px 8px 0 0!important}
.dynamiclink_content{padding:12px!important}
.dynamiclink_name{font-size:14px!important;font-weight:600!important;color:#fff!important}
.dynamiclink_type{font-size:12px!important;color:var(--sf-dim)!important}
.dynamiclink_author{font-size:12px!important;color:var(--sf-text)!important}
.dynamiclink_authorname{color:var(--sf-accent)!important}
.dynamiclink_description{font-size:12px!important;color:var(--sf-dim)!important;margin-top:8px!important}
/* 评论 & 操作区 */
.blotter_control_container{padding:12px 16px!important;background:rgba(0,0,0,.15)!important;border-top:1px solid rgba(255,255,255,.05)!important}
.btn_grey_grey,.btn_grey_white_innerfade{background:rgba(255,255,255,.08)!important;border:1px solid rgba(255,255,255,.1)!important;border-radius:6px!important;color:var(--sf-text)!important;transition:all .2s ease!important}
.btn_grey_grey:hover,.btn_grey_white_innerfade:hover{background:rgba(255,255,255,.15)!important;color:#fff!important;border-color:rgba(255,255,255,.2)!important}
.ico16.thumb_up{filter:brightness(1.2)!important}
.blotter_voters_names{font-size:12px!important;color:var(--sf-dim)!important}
.blotter_voters_names a{color:var(--sf-accent)!important}
.blotter_comment_thread{background:rgba(0,0,0,.1)!important;border-radius:8px!important;margin:12px 16px!important}
.comment_footer_ctn{display:none!important}
.commentthread_area{background:transparent!important;border:none!important}
.commentthread_textarea{background:rgba(0,0,0,.4)!important;border:1px solid rgba(255,255,255,.1)!important;border-radius:8px!important;color:#fff!important;font-size:13px!important;padding:10px 12px!important;height:60px!important;min-height:60px!important;max-height:150px!important;resize:vertical!important;box-sizing:border-box!important}
.commentthread_textarea:focus{border-color:rgba(102,192,244,.5)!important;box-shadow:inset 0 0 0 1px rgba(102,192,244,.3)!important}
.btn_green_white_innerfade{background:linear-gradient(135deg,#4caf50 0%,#3d8b40 100%)!important;border-radius:6px!important;font-weight:500!important}
/* 日期分组 */
.blotter_day{margin-top:20px!important}
.blotter_day_header{display:flex!important;align-items:center!important;padding:10px 16px!important;background:rgba(0,0,0,.3)!important;border-radius:8px!important;margin-bottom:12px!important}
.blotter_day_header h2{font-size:14px!important;font-weight:600!important;color:var(--sf-dim)!important}
/* 发帖区域 */
.blotter_status_entry{background:var(--sf-card)!important;border-radius:var(--sf-radius)!important;border:1px solid var(--sf-border)!important;padding:12px!important;margin-bottom:16px!important;box-sizing:border-box!important}
#blotter_statuspost_textarea{background:rgba(0,0,0,.4)!important;border:1px solid rgba(255,255,255,.1)!important;border-radius:8px!important;color:#fff!important;font-size:14px!important;padding:12px!important;height:80px!important;min-height:80px!important;max-height:200px!important;resize:vertical!important;box-sizing:border-box!important}
#blotter_statuspost_textarea:focus{border-color:rgba(102,192,244,.5)!important;box-shadow:inset 0 0 0 1px rgba(102,192,244,.3)!important}
.blotter_appselect{background:rgba(255,255,255,.05)!important;border-radius:6px!important;border:1px solid rgba(255,255,255,.1)!important}
#blotter_appselect_suggestions{background:rgba(23,26,33,.95)!important;border:1px solid rgba(255,255,255,.1)!important;border-radius:8px!important;box-shadow:0 8px 24px rgba(0,0,0,.5)!important;overflow:hidden!important;backdrop-filter:blur(8px)!important;-webkit-backdrop-filter:blur(8px)!important}
#blotter_appselect_suggestions .option{color:var(--sf-text)!important;padding:5px 12px!important;font-size:12px!important;border-bottom:1px solid rgba(255,255,255,.04)!important;transition:background .15s ease!important;line-height:1.4!important}
#blotter_appselect_suggestions .option:last-child{border-bottom:none!important}
#blotter_appselect_suggestions .option:hover{background:rgba(102,192,244,.15)!important;color:#fff!important}
.blotter_status_submit_ctn{display:flex!important;align-items:center!important;gap:10px!important;justify-content:flex-end!important}
.blotter_eventsblock{background:rgba(23,26,33,.6)!important;border-radius:var(--sf-radius)!important;border:1px solid var(--sf-border)!important;padding:16px!important;margin-bottom:16px!important}
.blotter_eventsblock_title{font-size:14px!important;font-weight:600!important;color:#fff!important;margin-bottom:12px!important}
/* Steam 原生全屏截图 */
.blotter_screenshot_fullscreen{background:rgba(0,0,0,.95)!important}
.blotter_screenshot_fullscreen img{border-radius:8px!important;max-width:95vw!important;max-height:90vh!important}
/* Steam 原生弹窗美化 */
#modalContent{background:var(--sf-card)!important;border-radius:var(--sf-radius)!important;border:1px solid var(--sf-border)!important;box-shadow:0 8px 40px rgba(0,0,0,.5)!important;overflow:hidden!important}
#modalContentTitleBar{background:linear-gradient(135deg,var(--sf-bg) 0%,#2a475e 100%)!important;border-bottom:1px solid var(--sf-border)!important;padding:10px 16px!important;display:flex!important;align-items:center!important;gap:10px!important}
#modalContentTitleBarLink{color:var(--sf-accent)!important;font-size:13px!important;font-weight:500!important;text-decoration:none!important;flex:1!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}
#modalContentTitleBarLink:hover{color:#fff!important}
#modalContentTitleBarImageLink img{opacity:.5!important;transition:opacity .2s!important}
#modalContentTitleBarImageLink:hover img{opacity:1!important}
#modalContentDismiss{width:28px!important;height:28px!important;border-radius:50%!important;background:rgba(255,255,255,.1)!important;border:none!important;cursor:pointer!important;display:flex!important;align-items:center!important;justify-content:center!important;transition:all .2s!important;padding:0!important}
#modalContentDismiss:hover{background:rgba(255,255,255,.2)!important}
#modalContentDismiss img{display:none!important}
#modalContentDismiss::after{content:'✕';color:var(--sf-text)!important;font-size:14px!important}
#modalContentFrameContainer{background:rgba(0,0,0,.2)!important}
.modalContent_iFrame{border:none!important;border-radius:0!important}
/* 右侧按钮组 */
#sf-btn-group{position:fixed!important;bottom:30px!important;right:30px!important;display:flex!important;flex-direction:column!important;gap:10px!important;z-index:${CONFIG.Z_INDEX}!important}
#sf-friend-btn,#sf-screenshot-btn,#sf-game-btn,#sf-achievement-btn,#sf-review-btn{width:${CONFIG.BTN_SIZE}px!important;height:${CONFIG.BTN_SIZE}px!important;border-radius:50%!important;border:none!important;cursor:pointer!important;box-shadow:0 4px 20px rgba(0,0,0,.3)!important;transition:var(--sf-transition)!important;display:flex!important;align-items:center!important;justify-content:center!important}
#sf-screenshot-btn{background:linear-gradient(135deg,var(--sf-green) 0%,#3d8b40 100%)!important;box-shadow:0 4px 20px rgba(76,175,80,.4)!important}
#sf-game-btn{background:linear-gradient(135deg,var(--sf-orange) 0%,#f57c00 100%)!important;box-shadow:0 4px 20px rgba(255,152,0,.4)!important}
#sf-achievement-btn{background:linear-gradient(135deg,#9c27b0 0%,#7b1fa2 100%)!important;box-shadow:0 4px 20px rgba(156,39,176,.4)!important}
#sf-review-btn{background:linear-gradient(135deg,#e91e63 0%,#c2185b 100%)!important;box-shadow:0 4px 20px rgba(233,30,99,.4)!important}
#sf-friend-btn{background:linear-gradient(135deg,var(--sf-accent) 0%,#4a9bc7 100%)!important;box-shadow:0 4px 20px rgba(102,192,244,.4)!important}
#sf-friend-btn:hover,#sf-screenshot-btn:hover,#sf-game-btn:hover,#sf-achievement-btn:hover,#sf-review-btn:hover{transform:scale(1.1)!important}
#sf-friend-btn svg,#sf-screenshot-btn svg,#sf-game-btn svg,#sf-achievement-btn svg,#sf-review-btn svg{width:28px!important;height:28px!important;fill:#fff!important}
/* 筛选按钮激活/未激活状态 */
#sf-btn-group.filtering #sf-screenshot-btn,#sf-btn-group.filtering #sf-game-btn,#sf-btn-group.filtering #sf-achievement-btn,#sf-btn-group.filtering #sf-review-btn{opacity:.35!important;filter:grayscale(.6)!important;transform:scale(.9)!important}
#sf-btn-group.filtering #sf-screenshot-btn.active,#sf-btn-group.filtering #sf-game-btn.active,#sf-btn-group.filtering #sf-achievement-btn.active,#sf-btn-group.filtering #sf-review-btn.active{opacity:1!important;filter:none!important;transform:scale(1.1)!important}
/* 每日汇总行分类样式 */
.sf-rollup-friend{box-shadow:inset 3px 0 0 #2196F3;background:rgba(33,150,243,.06)!important}
.sf-rollup-firstplay{box-shadow:inset 3px 0 0 #4CAF50;background:rgba(76,175,80,.06)!important}
.sf-rollup-wishlist{box-shadow:inset 3px 0 0 #FF9800;background:rgba(255,152,0,.06)!important}
.sf-rollup-achievement{box-shadow:inset 3px 0 0 #9C27B0;background:rgba(156,39,176,.06)!important}
/* 回到顶部 */
#sf-top-btn{width:${CONFIG.BTN_SIZE}px!important;height:${CONFIG.BTN_SIZE}px!important;border-radius:50%!important;border:none!important;cursor:pointer!important;box-shadow:0 4px 20px rgba(0,0,0,.3)!important;transition:var(--sf-transition)!important;display:flex!important;align-items:center!important;justify-content:center!important;background:linear-gradient(135deg,#546e7a 0%,#37474f 100%)!important;opacity:0!important;visibility:hidden!important;transform:scale(.8)!important}
#sf-top-btn.show{opacity:1!important;visibility:visible!important;transform:scale(1)!important}
#sf-top-btn:hover{transform:scale(1.1)!important;background:linear-gradient(135deg,#607d8b 0%,#455a64 100%)!important}
#sf-top-btn svg{width:28px!important;height:28px!important;fill:#fff!important}
/* 好友面板 */
#sf-friend-panel{position:fixed!important;bottom:200px!important;right:30px!important;width:${CONFIG.PANEL_WIDTH}px!important;max-height:${CONFIG.PANEL_MAX_H}!important;background:rgba(23,26,33,.95)!important;border-radius:16px!important;border:1px solid rgba(255,255,255,.1)!important;box-shadow:0 8px 40px rgba(0,0,0,.5)!important;z-index:${CONFIG.Z_INDEX - 1}!important;overflow:hidden!important;opacity:0!important;visibility:hidden!important;transform:translateY(20px) scale(.95)!important;transition:var(--sf-transition)!important}
#sf-friend-panel.open{opacity:1!important;visibility:visible!important;transform:translateY(0) scale(1)!important}
.sf-panel-header{padding:16px!important;background:rgba(0,0,0,.3)!important;border-bottom:1px solid var(--sf-border)!important;display:flex!important;align-items:center!important;justify-content:space-between!important}
.sf-panel-title{font-size:16px!important;font-weight:600!important;color:#fff!important}
.sf-panel-close{width:28px!important;height:28px!important;border-radius:50%!important;background:rgba(255,255,255,.1)!important;border:none!important;cursor:pointer!important;display:flex!important;align-items:center!important;justify-content:center!important;transition:all .2s!important}
.sf-panel-close:hover{background:rgba(255,255,255,.2)!important}
.sf-panel-close svg{width:14px!important;height:14px!important;fill:var(--sf-text)!important}
.sf-panel-content{padding:12px!important;max-height:calc(70vh - 60px)!important;overflow-y:auto!important}
.sf-panel-content::-webkit-scrollbar{width:6px!important}
.sf-panel-content::-webkit-scrollbar-thumb{background:rgba(255,255,255,.2)!important;border-radius:3px!important}
/* 好友列表项 */
.sf-friend-item{display:flex!important;align-items:center!important;gap:12px!important;padding:10px!important;border-radius:10px!important;background:rgba(255,255,255,.03)!important;margin-bottom:8px!important;transition:all .2s ease!important;cursor:pointer!important}
.sf-friend-item:hover{background:var(--sf-hover)!important}
.sf-friend-avatar{width:40px!important;height:40px!important;border-radius:8px!important;position:relative!important}
.sf-friend-avatar img{width:100%!important;height:100%!important;border-radius:8px!important;object-fit:cover!important}
.sf-friend-frame{position:absolute!important;inset:-6px!important;pointer-events:none!important}
.sf-friend-frame img{width:100%!important;height:100%!important}
.sf-friend-info{flex:1!important;min-width:0!important}
.sf-friend-name{font-size:14px!important;font-weight:500!important;color:var(--sf-accent)!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important}
.sf-friend-status{font-size:12px!important;color:var(--sf-dim)!important}
.sf-status-dot{width:10px!important;height:10px!important;border-radius:50%!important}
.sf-dot-online{background:var(--sf-green)!important}
.sf-dot-offline{background:var(--sf-dim)!important}
.sf-dot-ingame{background:var(--sf-accent)!important}
.sf-own-item{background:rgba(102,192,244,.1)!important;border:1px solid rgba(102,192,244,.2)!important}
.sf-action-btn{display:flex!important;align-items:center!important;justify-content:center!important;padding:12px!important;margin-top:8px!important;border-radius:8px!important;background:rgba(255,255,255,.05)!important;color:var(--sf-text)!important;font-size:14px!important;cursor:pointer!important;transition:all .2s ease!important}
.sf-action-btn:hover{background:rgba(102,192,244,.15)!important;color:var(--sf-accent)!important}
.sf-stats-block{padding:10px!important;margin:8px 0!important;background:rgba(255,255,255,.05)!important;border-radius:8px!important;font-size:13px!important;color:var(--sf-text)!important;text-align:center!important}
.sf-section-title{padding:12px 8px 8px!important;font-size:13px!important;font-weight:600!important;color:var(--sf-dim)!important;border-top:1px solid var(--sf-border)!important;margin-top:8px!important}
.sf-ingame{color:var(--sf-accent)!important}
.sf-view-all{padding:10px!important;text-align:center!important;font-size:12px!important;color:var(--sf-accent)!important;cursor:pointer!important}
.sf-view-all:hover{color:#fff!important}
.sf-menu-item{display:flex!important;align-items:center!important;padding:8px 12px!important;border-radius:6px!important;background:rgba(255,255,255,.03)!important;color:var(--sf-text)!important;font-size:13px!important;cursor:pointer!important;transition:all .2s ease!important;margin-bottom:4px!important}
.sf-menu-item:hover{background:var(--sf-hover)!important;color:var(--sf-accent)!important}
/* 移动端适配 */
@media(max-width:600px){.blotter_page_title h1{font-size:20px!important}.friend_activity_header{padding:12px!important}#sf-btn-group{bottom:20px!important;right:20px!important}#sf-friend-btn,#sf-screenshot-btn,#sf-game-btn,#sf-achievement-btn,#sf-review-btn{width:48px!important;height:48px!important}#sf-friend-panel{right:20px!important;width:calc(100vw - 40px)!important;max-width:300px!important}}
/* 截图查看器 */
#sf-viewer{position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,.85);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);display:flex;align-items:center;justify-content:center;opacity:0;visibility:hidden;transition:opacity .35s ease,visibility .35s ease}
#sf-viewer.open{opacity:1;visibility:visible}
#sf-viewer-img{max-width:90vw;max-height:85vh;object-fit:contain;border-radius:4px;box-shadow:0 8px 40px rgba(0,0,0,.6);cursor:grab;user-select:none;-webkit-user-drag:none}
#sf-viewer img:active{cursor:grabbing}
#sf-viewer-close{position:fixed;top:20px;right:20px;width:40px;height:40px;border-radius:50%;border:none;background:rgba(255,255,255,.12);color:#fff;font-size:20px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .2s;z-index:100000}
#sf-viewer-close:hover{background:rgba(255,255,255,.25)}
#sf-viewer-download{position:fixed;bottom:24px;right:24px;padding:10px 20px;border-radius:8px;border:none;background:rgba(102,192,244,.2);color:#66c0f4;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s;z-index:100000;display:flex;align-items:center;gap:8px}
#sf-viewer-download:hover{background:rgba(102,192,244,.35);color:#fff}
#sf-viewer-zoom{position:fixed;bottom:24px;left:24px;padding:8px 14px;border-radius:8px;background:rgba(0,0,0,.5);color:#8f98a0;font-size:13px;z-index:100000;pointer-events:none}
#sf-viewer-hint{position:fixed;bottom:70px;left:50%;transform:translateX(-50%);padding:6px 16px;border-radius:6px;background:rgba(0,0,0,.5);color:#8f98a0;font-size:12px;z-index:100000;pointer-events:none;opacity:0;transition:opacity .3s}
#sf-viewer-hint.show{opacity:1}
.sf-toast{position:fixed;top:24px;left:50%;transform:translateX(-50%) translateY(-10px);padding:10px 24px;border-radius:8px;font-size:14px;font-weight:500;z-index:100001;opacity:0;transition:all .3s ease;pointer-events:none;box-shadow:0 4px 16px rgba(0,0,0,.5)}
.sf-toast--visible{opacity:1;transform:translateX(-50%) translateY(0)}
.sf-toast--info{background:#2a475e;color:#c7d5e0;border:1px solid #4a6a7a}
.sf-toast--warn{background:rgba(234,179,8,.9);color:#1a1a1a}
/* 切图按钮 */
#sf-viewer-prev,#sf-viewer-next{position:fixed;top:50%;transform:translateY(-50%);width:44px;height:44px;border-radius:50%;border:none;background:rgba(255,255,255,.1);color:#fff;font-size:22px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s;z-index:100000;opacity:0;pointer-events:none}
#sf-viewer-prev{left:20px}
#sf-viewer-next{right:20px}
#sf-viewer-prev.show,#sf-viewer-next.show{opacity:1;pointer-events:auto}
#sf-viewer-prev:hover,#sf-viewer-next:hover{background:rgba(255,255,255,.25);transform:translateY(-50%) scale(1.1)}
/* 图片计数 */
#sf-viewer-counter{position:fixed;top:20px;left:50%;transform:translateX(-50%);padding:6px 14px;border-radius:6px;background:rgba(0,0,0,.5);color:#8f98a0;font-size:13px;z-index:100000;pointer-events:none;display:none}
#sf-viewer-counter.show{display:block}`;
/* ========== 注入样式 ========== */
if (typeof GM_addStyle !== 'undefined') GM_addStyle(CSS);
else { const s = document.createElement('style'); s.textContent = CSS; document.head.appendChild(s); }
/* ========== 筛选状态 ========== */
let activeFilter = null;
/* ========== 创建 UI ========== */
function createUI() {
if ($('#sf-btn-group')) return;
const grp = document.createElement('div');
grp.id = 'sf-btn-group';
// 通过配置数组创建筛选按钮
FILTER_BTNS.forEach(({ id, icon, title }) => {
const btn = document.createElement('button');
btn.id = id;
btn.innerHTML = ICONS[icon];
btn.title = title;
grp.appendChild(btn);
});
// 回到顶部按钮
const topBtn = document.createElement('button');
topBtn.id = 'sf-top-btn';
topBtn.innerHTML = ICONS.top;
topBtn.title = '回到顶部';
grp.appendChild(topBtn);
document.body.appendChild(grp);
// 回到顶部:滚动显示/隐藏 + 点击
window.addEventListener('scroll', () => {
topBtn.classList.toggle('show', window.scrollY > 400);
}, { passive: true });
topBtn.addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' }));
// 好友面板
const panel = document.createElement('div');
panel.id = 'sf-friend-panel';
panel.innerHTML = `
`;
document.body.appendChild(panel);
// 事件委托:按钮组点击
grp.addEventListener('click', e => {
const btn = e.target.closest('button');
if (!btn) return;
e.stopPropagation();
const cfg = FILTER_BTNS.find(b => b.id === btn.id);
if (cfg?.filter) {
toggleFilter(cfg.filter);
} else if (btn.id === 'sf-friend-btn') {
if (panel.classList.toggle('open') && !panel.dataset.loaded) {
loadFriends();
panel.dataset.loaded = '1';
}
}
});
// 关闭好友面板
$('.sf-panel-close', panel).addEventListener('click', e => {
e.stopPropagation();
panel.classList.remove('open');
});
// 点击面板外关闭
document.addEventListener('click', e => {
if (!panel.contains(e.target) && !grp.contains(e.target)) {
panel.classList.remove('open');
}
});
}
/* ========== 筛选切换 ========== */
function toggleFilter(type) {
const grp = $('#sf-btn-group');
// 再次点击同一按钮 → 取消筛选,显示全部
if (activeFilter === type) {
activeFilter = null;
FILTER_BTNS.forEach(b => {
if (b.filter) $(`#${b.id}`)?.classList.remove('active');
});
grp.classList.remove('filtering');
showAllBlocks();
return;
}
// 切换筛选类型
activeFilter = type;
FILTER_BTNS.forEach(b => {
if (b.filter) $(`#${b.id}`)?.classList.toggle('active', b.filter === type);
});
grp.classList.add('filtering');
// 遍历所有动态块,匹配的显示,不匹配的隐藏
const matcher = FILTER_MATCHERS[type];
$$('.blotter_block').forEach(block => {
block.classList.toggle('sf-hidden', !matcher(block));
});
}
// 显示全部动态块
function showAllBlocks() {
$$('.blotter_block.sf-hidden').forEach(b => b.classList.remove('sf-hidden'));
}
/* ========== 加载好友列表 ========== */
function loadFriends() {
const list = $('#sf-friend-list');
const rc = $('#friendactivity_right_column');
if (!list || !rc) {
if (list) list.innerHTML = '暂无数据
';
return;
}
try {
// 自己的信息
const oa = rc.querySelector('.webchat_own_avatar');
const nl = rc.querySelector('.friendslist_entry_content .linkFriend');
const os = rc.querySelector('.friendslist_entry_content');
if (oa && nl) {
const ai = oa.querySelector('img[data-miniprofile],img[srcset]');
const fi = oa.querySelector('.profile_avatar_frame img');
const st = os?.querySelector('div')?.textContent || '';
const el = document.createElement('div');
el.className = 'sf-friend-item sf-own-item';
el.innerHTML = `
${fi ? '
' : ''}
${escapeHtml(nl.textContent)}
${escapeHtml(st)}
`;
el.addEventListener('click', () => window.open(nl.href, '_blank'));
list.appendChild(el);
}
// 聊天按钮
const cb = rc.querySelector('.webchat_launch');
if (cb) {
const el = document.createElement('div');
el.className = 'sf-action-btn';
el.innerHTML = '💬 发送聊天消息';
el.addEventListener('click', () => typeof window.LaunchWebChat === 'function' && window.LaunchWebChat());
list.appendChild(el);
}
// 好友统计
const fs = rc.querySelector('.profile_link_block_text_friends');
if (fs) {
const el = document.createElement('div');
el.className = 'sf-stats-block';
el.textContent = fs.textContent;
list.appendChild(el);
}
// 查看好友列表
const vl = rc.querySelector('.friends_link_block');
if (vl) {
const lk = vl.closest('a') || rc.querySelector('a[href*="/friends"]');
const el = document.createElement('div');
el.className = 'sf-action-btn';
el.innerHTML = '👥 查看好友列表';
el.addEventListener('click', () => window.open(lk?.href || 'https://steamcommunity.com/my/friends', '_blank'));
list.appendChild(el);
}
// 游戏中的好友
const ifs = rc.querySelectorAll('.friendBlock.in-game');
if (ifs.length) {
const tt = rc.querySelector('.profile_friends_ingame');
const title = document.createElement('div');
title.className = 'sf-section-title';
title.textContent = tt?.textContent || '游戏中的好友';
list.appendChild(title);
ifs.forEach(f => {
const av = f.querySelector('.playerAvatar img');
const lk = f.querySelector('.friendBlockLinkOverlay');
const gm = f.querySelector('.linkFriend_in-game');
// 多重兜底解析好友名
let n = f.querySelector('.friendBlockContent')?.firstChild?.textContent?.trim()
|| f.querySelector('.friendBlockContent a')?.textContent?.trim()
|| f.querySelector('.friendBlockContent')?.textContent?.split('\n')[0]?.trim()
|| lk?.getAttribute('href')?.split('/').pop()?.replace(/id\/|profiles\//, '')
|| '未知';
const g = gm?.textContent?.replace(/游戏中\n|非 Steam 游戏中\n/g, '') || '';
const el = document.createElement('div');
el.className = 'sf-friend-item';
el.innerHTML = `${escapeHtml(n)}
🎮 ${escapeHtml(g)}
`;
el.addEventListener('click', () => window.open(lk?.href || '', '_blank'));
list.appendChild(el);
});
// 查看全部
const va = rc.querySelector('.profile_friends_ingame_viewall a');
if (va) {
const el = document.createElement('div');
el.className = 'sf-view-all';
el.textContent = va.textContent;
el.addEventListener('click', () => window.open(va.href, '_blank'));
list.appendChild(el);
}
}
// 快捷菜单
const mi = rc.querySelectorAll('.profile_menu_item a');
if (mi.length) {
const title = document.createElement('div');
title.className = 'sf-section-title';
title.textContent = '快捷菜单';
list.appendChild(title);
mi.forEach(a => {
const t = a.querySelector('.profile_menu_text')?.textContent?.trim() || '';
const el = document.createElement('div');
el.className = 'sf-menu-item';
el.innerHTML = `${escapeHtml(t)}`;
el.addEventListener('click', () => window.open(a.href, '_blank'));
list.appendChild(el);
});
}
} catch (err) {
console.warn('[SF] 好友列表加载失败:', err);
list.innerHTML = '加载失败
';
}
}
/* ========== 修复宽度(移除 Steam 内联 style) ========== */
function fixWidth() {
const el = document.getElementById('blotter_content');
if (el?.style.width) el.removeAttribute('style');
// 清除截图画廊滚动区域的内联 width
$$('.highlight_strip_scroll').forEach(s => {
if (s.style.width) s.style.removeProperty('width');
});
}
/* ========== 展开游戏购买动态的图片(默认3张→8张) ========== */
const CAPSULE_URL = 'https://cdn.cloudflare.steamstatic.com/steam/apps/{appid}/capsule_231x87.jpg';
const CDN_BASE = 'https://cdn.cloudflare.steamstatic.com/steam/apps/';
const SVG_PLACEHOLDER = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='231' height='87'%3E%3Crect fill='%23333' width='231' height='87'/%3E%3Ctext x='50%25' y='50%25' text-anchor='middle' dy='.3em' fill='%23999' font-size='14'%3ENo Image%3C/text%3E%3C/svg%3E`;
// 封面图候选尺寸列表,依次尝试,全部失败则通过 API 获取
const FALLBACK_CAPSULE_PATHS = [
'capsule_231x87.jpg',
'capsule_231x87.png',
'header.jpg',
'capsule_184x69.jpg',
'capsule_467x181.jpg'
];
// API 缓存:appid -> capsule URL,避免重复请求
const _capsuleCache = new Map();
// 正在请求中的 appid 集合,避免并发重复请求
const _capsulePending = new Set();
// 等待某个 appid API 结果的回调队列
const _capsuleWaiters = new Map();
/* 通过 Steam Store API 获取真实封面图 URL */
function fetchCapsuleFromAPI(appid) {
return new Promise((resolve, reject) => {
// 命中缓存直接返回
if (_capsuleCache.has(appid)) {
resolve(_capsuleCache.get(appid));
return;
}
// 已有请求在进行,加入等待队列
if (_capsulePending.has(appid)) {
if (!_capsuleWaiters.has(appid)) _capsuleWaiters.set(appid, []);
_capsuleWaiters.get(appid).push({ resolve, reject });
return;
}
_capsulePending.add(appid);
if (!_capsuleWaiters.has(appid)) _capsuleWaiters.set(appid, []);
_capsuleWaiters.get(appid).push({ resolve, reject });
const apiUrl = `https://store.steampowered.com/api/appdetails?appids=${appid}`;
const doRequest = typeof GM_xmlhttpRequest !== 'undefined' ? GM_xmlhttpRequest : null;
if (doRequest) {
doRequest({
method: 'GET',
url: apiUrl,
onload: (resp) => {
try {
const json = JSON.parse(resp.responseText);
const data = json[appid];
if (data && data.success && data.data && data.data.capsule_image) {
const url = data.data.capsule_image;
_capsuleCache.set(appid, url);
} else {
_capsuleCache.set(appid, '');
}
} catch (e) {
_capsuleCache.set(appid, '');
}
_capsulePending.delete(appid);
const waiters = _capsuleWaiters.get(appid) || [];
_capsuleWaiters.delete(appid);
const cached = _capsuleCache.get(appid);
waiters.forEach(w => cached ? w.resolve(cached) : w.reject());
},
onerror: () => {
_capsuleCache.set(appid, '');
_capsulePending.delete(appid);
const waiters = _capsuleWaiters.get(appid) || [];
_capsuleWaiters.delete(appid);
waiters.forEach(w => w.reject());
},
ontimeout: () => {
_capsuleCache.set(appid, '');
_capsulePending.delete(appid);
const waiters = _capsuleWaiters.get(appid) || [];
_capsuleWaiters.delete(appid);
waiters.forEach(w => w.reject());
}
});
} else {
// 降级:使用 fetch(可能受 CORS 限制)
fetch(apiUrl)
.then(r => r.json())
.then(json => {
const data = json[appid];
const url = (data && data.success && data.data && data.data.capsule_image) ? data.data.capsule_image : '';
_capsuleCache.set(appid, url);
_capsulePending.delete(appid);
const waiters = _capsuleWaiters.get(appid) || [];
_capsuleWaiters.delete(appid);
waiters.forEach(w => url ? w.resolve(url) : w.reject());
})
.catch(() => {
_capsuleCache.set(appid, '');
_capsulePending.delete(appid);
const waiters = _capsuleWaiters.get(appid) || [];
_capsuleWaiters.delete(appid);
waiters.forEach(w => w.reject());
});
}
});
}
/* 为 img 元素依次尝试多个 CDN 封面地址,全部失败后通过 API 获取真实 URL */
function loadCapsuleWithFallback(img, appid) {
let idx = 0;
function tryNext() {
if (idx >= FALLBACK_CAPSULE_PATHS.length) {
// CDN 路径全部失败,通过 Steam API 获取真实封面
fetchCapsuleFromAPI(appid).then(url => {
if (url) {
img.onerror = () => { img.onerror = null; img.src = SVG_PLACEHOLDER; };
img.src = url;
} else {
img.onerror = null;
img.src = SVG_PLACEHOLDER;
}
}).catch(() => {
img.onerror = null;
img.src = SVG_PLACEHOLDER;
});
return;
}
img.onerror = () => { idx++; tryNext(); };
img.src = CDN_BASE + appid + '/' + FALLBACK_CAPSULE_PATHS[idx];
}
tryNext();
}
/* ========== 修复已有游戏购买封面的 SVG / 破损图片 ========== */
function fixGamePurchaseImages() {
$$('.blotter_gamepurchase_logo').forEach(logo => {
if (logo.dataset.sfImgFixed) return;
logo.dataset.sfImgFixed = '1';
const appid = extractAppidFromContext(logo);
if (!appid) return; // 找不到 appid,无法修复
const img = logo.querySelector('img');
const inlineSvg = logo.querySelector('svg');
if (img) {
const src = img.src || '';
const isSvgDataUri = src.startsWith('data:image/svg');
const isSvgFile = /\.svg(\?|$)/i.test(src);
const isEmpty = !src || src === 'about:blank';
if (isSvgDataUri || isSvgFile || isEmpty) {
//
存在但 src 是 SVG/空 → 用回退链替换
loadCapsuleWithFallback(img, appid);
} else {
//
看起来正常,添加 onerror 兜底(不主动覆盖原 src)
img.addEventListener('error', () => loadCapsuleWithFallback(img, appid), { once: true });
}
} else if (inlineSvg) {
// logo 内没有
,而是内联