// ==UserScript== // @name Steam 好友动态美化 // @namespace http://tampermonkey.net/ // @version 1.3.6 // @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:#4fc3f7;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),#0d9488', shadow: '52,211,153' }, { id: 'sf-game-btn', icon: 'game', title: '只看游戏', filter: 'game', gradient: 'var(--sf-orange),#ea580c', shadow: '251,146,60' }, { id: 'sf-achievement-btn', icon: 'achievement', title: '只看成就', filter: 'achievement', gradient: '#a855f7,#7c3aed', shadow: '168,85,247' }, { id: 'sf-review-btn', icon: 'review', title: '只看评测', filter: 'review', gradient: '#f43f5e,#be123c', shadow: '244,63,94' }, { id: 'sf-friend-btn', icon: 'friend', title: '好友列表', filter: null, gradient: 'var(--sf-accent),#0284c7', shadow: '79,195,247' } ]; /* ========== 筛选匹配规则 ========== */ 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, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); /* ========== CSS 变量主题(科技感深空玻璃) ========== */ const CSS_VARS = `:root{ --sf-bg:#0d1520;--sf-bg2:#080c12; --sf-card:rgba(15,24,40,.78); --sf-border:rgba(79,195,247,.14); --sf-border-strong:rgba(79,195,247,.4); --sf-glow:rgba(79,195,247,.22); --sf-hover:rgba(79,195,247,.08); --sf-text:#c9d6e2;--sf-dim:#7d8fa1; --sf-accent:#4fc3f7; --sf-green:#34d399;--sf-orange:#fb923c; --sf-radius:12px; --sf-shadow:0 4px 24px rgba(0,0,0,.45); --sf-transition:all .3s cubic-bezier(.25,.8,.35,1)}`; /* ========== 样式表 ========== */ const CSS = `${CSS_VARS} /* 页面布局:深空渐变 + 顶部青色光晕 */ .pagecontent,.bluebg{background:radial-gradient(1100px 480px at 50% -120px,rgba(79,195,247,.09),transparent 62%),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:800!important;letter-spacing:.5px!important;background:linear-gradient(90deg,#fff 30%,#7dd3fc 100%)!important;-webkit-background-clip:text!important;background-clip:text!important;-webkit-text-fill-color:transparent!important;margin-bottom:20px!important} .friend_activity_header{padding:20px!important;background:rgba(15,24,40,.6)!important;backdrop-filter:blur(10px)!important;-webkit-backdrop-filter:blur(10px)!important;border:1px solid var(--sf-border)!important;border-radius:var(--sf-radius)!important;margin-bottom:16px!important;box-shadow:var(--sf-shadow)!important} /* 动态卡片:玻璃拟态 + 顶部光线 + 悬停辉光 */ .blotter_block{position:relative!important;background:linear-gradient(160deg,rgba(20,32,50,.82) 0%,rgba(11,18,30,.86) 100%)!important;backdrop-filter:blur(10px)!important;-webkit-backdrop-filter:blur(10px)!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),inset 0 1px 0 rgba(255,255,255,.04)!important;transition:var(--sf-transition)!important} .blotter_block::before{content:''!important;position:absolute!important;top:0!important;left:0!important;right:0!important;height:1px!important;background:linear-gradient(90deg,transparent 0%,rgba(79,195,247,.55) 50%,transparent 100%)!important;opacity:.55!important;pointer-events:none!important;z-index:1!important;transition:opacity .3s ease!important} .blotter_block:hover{transform:translateY(-3px)!important;box-shadow:0 8px 32px rgba(0,0,0,.5),0 0 26px var(--sf-glow)!important;border-color:var(--sf-border-strong)!important} .blotter_block:hover::before{opacity:1!important} .blotter_block.sf-hidden{display:none!important} /* 作者信息 */ .blotter_author_block{padding:14px 16px!important;background:rgba(0,0,0,.25)!important;border-bottom:1px solid rgba(79,195,247,.08)!important} .blotter_avatar_holder .playerAvatar{border-radius:8px!important;box-shadow:0 2px 10px rgba(0,0,0,.4),0 0 0 1px rgba(79,195,247,.2)!important;transition:box-shadow .3s ease!important} .blotter_block:hover .blotter_avatar_holder .playerAvatar{box-shadow:0 2px 10px rgba(0,0,0,.4),0 0 12px var(--sf-glow)!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;text-shadow:0 0 12px rgba(79,195,247,.35)!important;transition:color .2s ease!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 .35s cubic-bezier(.25,.8,.35,1)!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,.12)!important} .blotter_screenshot_gallery_image{border-radius:8px!important;margin:4px!important;overflow:hidden!important;transition:box-shadow .25s ease!important} .blotter_screenshot_gallery_image:hover{box-shadow:0 0 12px var(--sf-glow)!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(79,195,247,.25);border-radius:3px} .highlight_strip_scroll::-webkit-scrollbar-thumb:hover{background:rgba(79,195,247,.45)} .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,box-shadow .2s ease!important;box-shadow:0 2px 8px rgba(0,0,0,.35)!important} .blotter_gamepurchase_logo:hover{transform:scale(1.06)!important;box-shadow:0 4px 16px rgba(0,0,0,.4),0 0 14px var(--sf-glow)!important} .blotter_gamepurchase_logo img{border-radius:8px!important} .blotter_gamepurchase_logo.single_item{width:200px!important} .plus_block{background:rgba(79,195,247,.08)!important;border:1px solid var(--sf-border)!important;border-radius:8px!important;color:var(--sf-accent)!important;font-weight:600!important;font-variant-numeric:tabular-nums!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;box-shadow:0 2px 8px rgba(0,0,0,.35)!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;filter:drop-shadow(0 0 6px rgba(79,195,247,.3))!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,.18)!important;border-left:2px solid var(--sf-border-strong)!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,.32)!important;border:1px solid var(--sf-border)!important;transition:all .2s ease!important} .dynamiclink_box:hover{border-color:var(--sf-border-strong)!important;box-shadow:0 0 16px var(--sf-glow)!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,.18)!important;border-top:1px solid rgba(79,195,247,.08)!important} .btn_grey_grey,.btn_grey_white_innerfade{background:rgba(79,195,247,.08)!important;border:1px solid var(--sf-border)!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(79,195,247,.16)!important;color:#fff!important;border-color:var(--sf-border-strong)!important;box-shadow:0 0 10px var(--sf-glow)!important} .ico16.thumb_up{filter:brightness(1.2) drop-shadow(0 0 4px rgba(79,195,247,.4))!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,.12)!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(4,10,18,.6)!important;border:1px solid var(--sf-border)!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;transition:border-color .2s ease,box-shadow .2s ease!important} .commentthread_textarea:focus{border-color:var(--sf-border-strong)!important;box-shadow:0 0 0 1px rgba(79,195,247,.25),0 0 14px var(--sf-glow)!important;outline:none!important} .btn_green_white_innerfade{background:linear-gradient(135deg,#10b981 0%,#0d9488 100%)!important;border-radius:6px!important;font-weight:500!important;box-shadow:0 2px 10px rgba(16,185,129,.3)!important} /* 日期分组:左侧霓虹指示条 */ .blotter_day{margin-top:20px!important} .blotter_day_header{display:flex!important;align-items:center!important;padding:10px 16px!important;background:linear-gradient(90deg,rgba(79,195,247,.1),rgba(79,195,247,.02))!important;border-left:2px solid var(--sf-accent)!important;border-radius:8px!important;margin-bottom:12px!important} .blotter_day_header h2{font-size:14px!important;font-weight:600!important;letter-spacing:.5px!important;color:var(--sf-text)!important} /* 发帖区域 */ .blotter_status_entry{background:linear-gradient(160deg,rgba(20,32,50,.82),rgba(11,18,30,.86))!important;backdrop-filter:blur(10px)!important;-webkit-backdrop-filter:blur(10px)!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;box-shadow:var(--sf-shadow)!important} #blotter_statuspost_textarea{background:rgba(4,10,18,.6)!important;border:1px solid var(--sf-border)!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;transition:border-color .2s ease,box-shadow .2s ease!important} #blotter_statuspost_textarea:focus{border-color:var(--sf-border-strong)!important;box-shadow:0 0 0 1px rgba(79,195,247,.25),0 0 14px var(--sf-glow)!important;outline:none!important} .blotter_appselect{background:rgba(79,195,247,.06)!important;border-radius:6px!important;border:1px solid var(--sf-border)!important} #blotter_appselect_suggestions{background:rgba(10,16,26,.95)!important;border:1px solid var(--sf-border-strong)!important;border-radius:8px!important;box-shadow:0 8px 24px rgba(0,0,0,.5),0 0 20px var(--sf-glow)!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(79,195,247,.06)!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(79,195,247,.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(15,24,40,.6)!important;backdrop-filter:blur(10px)!important;-webkit-backdrop-filter:blur(10px)!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(4,8,14,.95)!important} .blotter_screenshot_fullscreen img{border-radius:8px!important;max-width:95vw!important;max-height:90vh!important;box-shadow:0 0 40px rgba(0,0,0,.6),0 0 20px var(--sf-glow)!important} /* Steam 原生弹窗美化 */ #modalContent{background:var(--sf-card)!important;backdrop-filter:blur(14px)!important;-webkit-backdrop-filter:blur(14px)!important;border-radius:var(--sf-radius)!important;border:1px solid var(--sf-border-strong)!important;box-shadow:0 8px 40px rgba(0,0,0,.55),0 0 30px var(--sf-glow)!important;overflow:hidden!important} #modalContentTitleBar{background:linear-gradient(90deg,rgba(13,21,32,.95) 0%,rgba(30,58,84,.95) 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(244,63,94,.12)!important;border:1px solid rgba(244,63,94,.3)!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(244,63,94,.3)!important;box-shadow:0 0 10px rgba(244,63,94,.35)!important} #modalContentDismiss img{display:none!important} #modalContentDismiss::after{content:'✕';color:#fda4af!important;font-size:13px!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:1px solid rgba(255,255,255,.25)!important;cursor:pointer!important;box-shadow:0 4px 20px rgba(0,0,0,.35)!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%,#0d9488 100%)!important;box-shadow:0 4px 20px rgba(52,211,153,.35)!important} #sf-game-btn{background:linear-gradient(135deg,var(--sf-orange) 0%,#ea580c 100%)!important;box-shadow:0 4px 20px rgba(251,146,60,.35)!important} #sf-achievement-btn{background:linear-gradient(135deg,#a855f7 0%,#7c3aed 100%)!important;box-shadow:0 4px 20px rgba(168,85,247,.35)!important} #sf-review-btn{background:linear-gradient(135deg,#f43f5e 0%,#be123c 100%)!important;box-shadow:0 4px 20px rgba(244,63,94,.35)!important} #sf-friend-btn{background:linear-gradient(135deg,var(--sf-accent) 0%,#0284c7 100%)!important;box-shadow:0 4px 20px rgba(79,195,247,.35)!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;border-color:rgba(255,255,255,.5)!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;filter:drop-shadow(0 0 3px rgba(255,255,255,.45))!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:.3!important;filter:grayscale(.7)!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;animation:sf-pulse 2.2s ease-in-out infinite!important} @keyframes sf-pulse{0%,100%{filter:brightness(1)}50%{filter:brightness(1.4)}} /* 每日汇总行分类样式:霓虹左缘 */ .sf-rollup-friend{box-shadow:inset 2px 0 0 #38bdf8,inset 0 0 14px rgba(56,189,248,.05)!important;background:rgba(56,189,248,.05)!important} .sf-rollup-firstplay{box-shadow:inset 2px 0 0 #34d399,inset 0 0 14px rgba(52,211,153,.05)!important;background:rgba(52,211,153,.05)!important} .sf-rollup-wishlist{box-shadow:inset 2px 0 0 #fb923c,inset 0 0 14px rgba(251,146,60,.05)!important;background:rgba(251,146,60,.05)!important} .sf-rollup-achievement{box-shadow:inset 2px 0 0 #a855f7,inset 0 0 14px rgba(168,85,247,.05)!important;background:rgba(168,85,247,.05)!important} /* 回到顶部 */ #sf-top-btn{width:${CONFIG.BTN_SIZE}px!important;height:${CONFIG.BTN_SIZE}px!important;border-radius:50%!important;border:1px solid rgba(79,195,247,.3)!important;cursor:pointer!important;box-shadow:0 4px 20px rgba(0,0,0,.35)!important;transition:var(--sf-transition)!important;display:flex!important;align-items:center!important;justify-content:center!important;background:linear-gradient(135deg,#1e3a5f 0%,#0f2038 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,#2563eb 0%,#1e40af 100%)!important;border-color:var(--sf-border-strong)!important;box-shadow:0 4px 20px rgba(0,0,0,.35),0 0 16px var(--sf-glow)!important} #sf-top-btn svg{width:28px!important;height:28px!important;fill:#fff!important;filter:drop-shadow(0 0 3px rgba(79,195,247,.5))!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(10,16,26,.92)!important;backdrop-filter:blur(16px)!important;-webkit-backdrop-filter:blur(16px)!important;border-radius:16px!important;border:1px solid var(--sf-border-strong)!important;box-shadow:0 8px 40px rgba(0,0,0,.6),0 0 30px var(--sf-glow)!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:linear-gradient(90deg,rgba(79,195,247,.1),rgba(79,195,247,.02))!important;border-bottom:1px solid var(--sf-border)!important;display:flex!important;align-items:center!important;justify-content:space-between!important;position:relative!important} .sf-panel-header::after{content:''!important;position:absolute!important;bottom:-1px!important;left:0!important;right:0!important;height:1px!important;background:linear-gradient(90deg,transparent,rgba(79,195,247,.6),transparent)!important;pointer-events:none!important} .sf-panel-title{font-size:16px!important;font-weight:700!important;letter-spacing:.5px!important;background:linear-gradient(90deg,#4fc3f7,#a5e4ff)!important;-webkit-background-clip:text!important;background-clip:text!important;-webkit-text-fill-color:transparent!important} .sf-panel-close{width:28px!important;height:28px!important;border-radius:50%!important;background:rgba(255,255,255,.08)!important;border:1px solid var(--sf-border)!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(244,63,94,.2)!important;border-color:rgba(244,63,94,.4)!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(79,195,247,.25)!important;border-radius:3px!important} .sf-panel-content::-webkit-scrollbar-thumb:hover{background:rgba(79,195,247,.45)!important} /* 好友列表项 */ .sf-friend-item{display:flex!important;align-items:center!important;gap:12px!important;padding:10px!important;border-radius:10px!important;background:rgba(79,195,247,.04)!important;border:1px solid transparent!important;margin-bottom:8px!important;transition:all .2s ease!important;cursor:pointer!important} .sf-friend-item:hover{background:var(--sf-hover)!important;border-color:var(--sf-border)!important;box-shadow:0 0 12px var(--sf-glow)!important;transform:translateX(2px)!important} .sf-friend-avatar{width:40px!important;height:40px!important;border-radius:8px!important;position:relative!important;flex-shrink:0!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;flex-shrink:0!important} .sf-dot-online{background:var(--sf-green)!important;box-shadow:0 0 8px rgba(52,211,153,.6)!important} .sf-dot-offline{background:var(--sf-dim)!important} .sf-dot-ingame{background:var(--sf-accent)!important;box-shadow:0 0 8px rgba(79,195,247,.7)!important} .sf-own-item{background:rgba(79,195,247,.08)!important;border:1px solid var(--sf-border)!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(79,195,247,.06)!important;border:1px solid var(--sf-border)!important;color:var(--sf-text)!important;font-size:14px!important;cursor:pointer!important;transition:all .2s ease!important} .sf-action-btn:hover{background:rgba(79,195,247,.14)!important;color:var(--sf-accent)!important;border-color:var(--sf-border-strong)!important;box-shadow:0 0 12px var(--sf-glow)!important} .sf-stats-block{padding:10px!important;margin:8px 0!important;background:rgba(79,195,247,.05)!important;border:1px solid var(--sf-border)!important;border-radius:8px!important;font-size:13px!important;color:var(--sf-text)!important;text-align:center!important;font-variant-numeric:tabular-nums!important} .sf-section-title{padding:12px 8px 8px!important;font-size:13px!important;font-weight:600!important;letter-spacing:.5px!important;color:var(--sf-accent)!important;text-transform:uppercase!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;transition:all .2s ease!important} .sf-view-all:hover{color:#fff!important;text-shadow:0 0 8px var(--sf-glow)!important} .sf-menu-item{display:flex!important;align-items:center!important;padding:8px 12px!important;border-radius:6px!important;background:rgba(79,195,247,.04)!important;border:1px solid transparent!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;border-color:var(--sf-border)!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(4,8,14,.88);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::before{content:'';position:absolute;inset:0;background-image:linear-gradient(rgba(79,195,247,.03) 1px,transparent 1px),linear-gradient(90deg,rgba(79,195,247,.03) 1px,transparent 1px);background-size:44px 44px;pointer-events:none} #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),0 0 30px rgba(79,195,247,.12);cursor:grab;user-select:none;-webkit-user-drag:none;position:relative} #sf-viewer img:active{cursor:grabbing} #sf-viewer-close{position:fixed;top:20px;right:20px;width:40px;height:40px;border-radius:50%;border:1px solid rgba(244,63,94,.35);background:rgba(244,63,94,.12);color:#fda4af;font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s;z-index:100000} #sf-viewer-close:hover{background:rgba(244,63,94,.3);color:#fff;box-shadow:0 0 14px rgba(244,63,94,.4);transform:rotate(90deg)} #sf-viewer-download{position:fixed;bottom:24px;right:24px;padding:10px 20px;border-radius:8px;border:1px solid var(--sf-border-strong);background:rgba(79,195,247,.12);color:var(--sf-accent);font-size:14px;font-weight:500;cursor:pointer;transition:all .2s;z-index:100000;display:flex;align-items:center;gap:8px;backdrop-filter:blur(6px)} #sf-viewer-download:hover{background:rgba(79,195,247,.25);color:#fff;box-shadow:0 0 16px var(--sf-glow)} #sf-viewer-zoom{position:fixed;bottom:24px;left:24px;padding:8px 14px;border-radius:8px;border:1px solid var(--sf-border);background:rgba(10,16,26,.7);color:var(--sf-accent);font-size:13px;font-variant-numeric:tabular-nums;z-index:100000;pointer-events:none;backdrop-filter:blur(6px)} #sf-viewer-hint{position:fixed;bottom:70px;left:50%;transform:translateX(-50%);padding:6px 16px;border-radius:6px;border:1px solid var(--sf-border);background:rgba(10,16,26,.7);color:var(--sf-dim);font-size:12px;z-index:100000;pointer-events:none;opacity:0;transition:opacity .3s;backdrop-filter:blur(6px)} #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);backdrop-filter:blur(8px)} .sf-toast--visible{opacity:1;transform:translateX(-50%) translateY(0)} .sf-toast--info{background:rgba(13,32,50,.92);color:#c9d6e2;border:1px solid var(--sf-border-strong);box-shadow:0 4px 16px rgba(0,0,0,.5),0 0 14px var(--sf-glow)} .sf-toast--warn{background:rgba(234,179,8,.92);color:#1a1a1a;border:1px solid rgba(234,179,8,.5)} /* 切图按钮 */ #sf-viewer-prev,#sf-viewer-next{position:fixed;top:50%;transform:translateY(-50%);width:44px;height:44px;border-radius:50%;border:1px solid var(--sf-border-strong);background:rgba(79,195,247,.1);color:var(--sf-accent);font-size:22px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s;z-index:100000;opacity:0;pointer-events:none;backdrop-filter:blur(6px)} #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(79,195,247,.28);color:#fff;transform:translateY(-50%) scale(1.1);box-shadow:0 0 16px var(--sf-glow)} #sf-viewer-prev svg,#sf-viewer-next svg{fill:currentColor} /* 图片计数 */ #sf-viewer-counter{position:fixed;top:20px;left:50%;transform:translateX(-50%);padding:6px 14px;border-radius:6px;border:1px solid var(--sf-border);background:rgba(10,16,26,.7);color:var(--sf-accent);font-size:13px;font-variant-numeric:tabular-nums;z-index:100000;pointer-events:none;display:none;backdrop-filter:blur(6px)} #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'); applyFilter(); } // 按当前 activeFilter 应用隐藏/显示 function applyFilter() { if (!activeFilter) return; const matcher = FILTER_MATCHERS[activeFilter]; $$('.blotter_block').forEach(block => { block.classList.toggle('sf-hidden', !matcher(block)); }); } // 显示全部动态块 function showAllBlocks() { $$('.blotter_block.sf-hidden').forEach(b => b.classList.remove('sf-hidden')); } /* ========== 好友面板元素工厂(DOM API 构建,避免 HTML 拼接注入面) ========== */ function createFriendItem({ name, status, dotCls, href, avatarSrc, frameSrc }) { const el = document.createElement('div'); el.className = 'sf-friend-item'; const avatar = document.createElement('div'); avatar.className = 'sf-friend-avatar'; const img = document.createElement('img'); img.alt = name; img.loading = 'lazy'; if (avatarSrc) img.src = avatarSrc; avatar.appendChild(img); if (frameSrc) { const frame = document.createElement('div'); frame.className = 'sf-friend-frame'; const fimg = document.createElement('img'); fimg.src = frameSrc; frame.appendChild(fimg); avatar.appendChild(frame); } const info = document.createElement('div'); info.className = 'sf-friend-info'; const nameEl = document.createElement('div'); nameEl.className = 'sf-friend-name'; nameEl.textContent = name; const statusEl = document.createElement('div'); statusEl.className = 'sf-friend-status'; statusEl.textContent = status; info.append(nameEl, statusEl); const dot = document.createElement('div'); dot.className = 'sf-status-dot ' + dotCls; el.append(avatar, info, dot); if (href) el.addEventListener('click', () => window.open(href, '_blank')); return el; } function createActionBtn(label, onClick) { const el = document.createElement('div'); el.className = 'sf-action-btn'; el.textContent = label; el.addEventListener('click', onClick); return el; } /* ========== 加载好友列表 ========== */ 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 || ''; list.appendChild(createFriendItem({ name: nl.textContent, status: st, dotCls: 'sf-dot-offline', href: nl.href, avatarSrc: ai?.srcset || ai?.src || '', frameSrc: fi?.src || '' })); list.lastElementChild.classList.add('sf-own-item'); } // 聊天按钮 if (rc.querySelector('.webchat_launch')) { list.appendChild(createActionBtn('💬 发送聊天消息', () => typeof window.LaunchWebChat === 'function' && window.LaunchWebChat())); } // 好友统计 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"]'); list.appendChild(createActionBtn('👥 查看好友列表', () => window.open(lk?.href || 'https://steamcommunity.com/my/friends', '_blank'))); } // 游戏中的好友 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'); // 多重兜底解析好友名 const 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 = createFriendItem({ name: n, status: '🎮 ' + g, dotCls: 'sf-dot-ingame', href: lk?.href || '', avatarSrc: av?.src || '' }); el.querySelector('.sf-friend-status')?.classList.add('sf-ingame'); 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'; const span = document.createElement('span'); span.textContent = t; el.appendChild(span); 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 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='%23111a26' width='231' height='87'/%3E%3Ctext x='50%25' y='50%25' text-anchor='middle' dy='.3em' fill='%234fc3f7' font-family='monospace' font-size='13'%3ELOADING...%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)) { _capsulePending.add(appid); _capsuleWaiters.set(appid, []); // 发起实际请求 requestCapsule(appid); } _capsuleWaiters.get(appid).push({ resolve, reject }); }); function requestCapsule(appid) { 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 => { let url = ''; try { const data = JSON.parse(resp.responseText)[appid]; if (data && data.success && data.data && data.data.capsule_image) url = data.data.capsule_image; } catch {} settle(appid, url); }, onerror: () => settle(appid, ''), ontimeout: () => settle(appid, '') }); } else { // 降级:使用 fetch(可能受 CORS 限制) fetch(apiUrl) .then(r => r.json()) .then(json => { const data = json[appid]; settle(appid, (data && data.success && data.data && data.data.capsule_image) ? data.data.capsule_image : ''); }) .catch(() => settle(appid, '')); } } // 统一结算:写缓存并唤醒等待队列 function settle(appid, url) { _capsuleCache.set(appid, url); _capsulePending.delete(appid); const waiters = _capsuleWaiters.get(appid) || []; _capsuleWaiters.delete(appid); waiters.forEach(w => url ? w.resolve(url) : 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 内没有 ,而是内联 → 移除 SVG,创建 加载真实封面 inlineSvg.remove(); const newImg = document.createElement('img'); newImg.alt = `Game #${appid}`; newImg.loading = 'lazy'; logo.appendChild(newImg); loadCapsuleWithFallback(newImg, appid); } }); } /* 从 logo 元素或其所在购买块的上下文中提取 appid(多重回退) */ function extractAppidFromContext(logoElement) { // 1) 自身 data-appid let m; const dataAppid = logoElement.getAttribute('data-appid'); if (dataAppid) return dataAppid; // 2) 自身 href m = (logoElement.getAttribute('href') || '').match(/\/app\/(\d+)/); if (m) return m[1]; // 3) 内部 href const innerA = logoElement.querySelector('a[href*="/app/"]'); if (innerA) { m = (innerA.getAttribute('href') || '').match(/\/app\/(\d+)/); if (m) return m[1]; } // 4) 从同购买块的 details 链接匹配(排除已被其他 logo 使用的 appid) const block = logoElement.closest('.blotter_gamepurchase'); if (!block) return null; const usedAppids = new Set(); block.querySelectorAll('.blotter_gamepurchase_logo').forEach(l => { if (l === logoElement) return; const a = l.getAttribute('data-appid'); if (a) usedAppids.add(a); const h = (l.getAttribute('href') || '').match(/\/app\/(\d+)/); if (h) usedAppids.add(h[1]); }); const detailLinks = block.querySelectorAll('.blotter_gamepurchase_details a[href*="/app/"]'); for (const link of detailLinks) { m = (link.getAttribute('href') || '').match(/\/app\/(\d+)/); if (m && !usedAppids.has(m[1])) { logoElement.setAttribute('data-appid', m[1]); return m[1]; } } return null; } function expandGameImages() { $$('.blotter_gamepurchase').forEach(block => { if (block.dataset.sfExpanded) return; block.dataset.sfExpanded = '1'; const content = block.querySelector('.blotter_gamepurchase_content'); const details = block.querySelector('.blotter_gamepurchase_details'); const plusBlock = content?.querySelector('.plus_block'); if (!content || !details || !plusBlock) return; const existingLogos = content.querySelectorAll('.blotter_gamepurchase_logo'); if (existingLogos.length >= CONFIG.MAX_GAME_SHOW) return; // 从 details 链接提取 appid const links = details.querySelectorAll('a[href*="store.steampowered.com/app/"]'); const existingAppids = new Set(); existingLogos.forEach(l => { const a = l.getAttribute('data-appid'); if (a) existingAppids.add(a); }); let added = 0; links.forEach(link => { if (existingLogos.length + added >= CONFIG.MAX_GAME_SHOW) return; const match = (link.getAttribute('href') || '').match(/\/app\/(\d+)/); if (!match || existingAppids.has(match[1])) return; const a = document.createElement('a'); a.href = link.href; a.className = 'blotter_gamepurchase_logo es_ea_checked'; a.setAttribute('data-appid', match[1]); const img = document.createElement('img'); img.loading = 'lazy'; img.alt = `Game #${match[1]}`; // 使用多格式回退链加载封面,避免直接显示 SVG 占位符 loadCapsuleWithFallback(img, match[1]); a.appendChild(img); content.insertBefore(a, plusBlock); added++; }); // 更新 +N 数字 const totalShown = existingLogos.length + added; totalShown < links.length ? (plusBlock.textContent = '+' + (links.length - totalShown)) : plusBlock.remove(); }); } /* ========== 所有游戏链接新标签打开 ========== */ function setupLinkTarget() { const blotter = document.getElementById('blotter_content'); if (!blotter) return; const setBlank = links => links.forEach(a => { const h = a.href || ''; if (a.target !== '_blank' && (h.includes('store.steampowered.com') || h.includes('steamcommunity.com'))) { a.target = '_blank'; a.rel = 'noopener'; } }); // 处理已有链接 setBlank(blotter.querySelectorAll('a')); // 监听新增节点 new MutationObserver(mutations => { for (const m of mutations) { for (const node of m.addedNodes) { if (node.nodeType !== 1) continue; if (node.tagName === 'A') setBlank([node]); if (node.querySelectorAll) setBlank(node.querySelectorAll('a')); } } }).observe(blotter, { childList: true, subtree: true }); } /* ========== 自定义截图查看器 ========== */ function setupScreenshotViewer() { // 创建查看器 DOM const viewer = document.createElement('div'); viewer.id = 'sf-viewer'; viewer.innerHTML = `
100%
滚轮缩放 · 拖拽移动 · ESC关闭
`; document.body.appendChild(viewer); const img = $('#sf-viewer-img'); const closeBtn = $('#sf-viewer-close'); const prevBtn = $('#sf-viewer-prev'); const nextBtn = $('#sf-viewer-next'); const downloadBtn = $('#sf-viewer-download'); const zoomLabel = $('#sf-viewer-zoom'); const counter = $('#sf-viewer-counter'); const hint = $('#sf-viewer-hint'); let scale = 1, offsetX = 0, offsetY = 0; let dragging = false, dragStartX, dragStartY, startOffsetX, startOffsetY; // 画廊数据:shots 数组 + 当前索引 let galleryShots = []; let galleryIndex = 0; // 当前截图的原图下载地址 let downloadUrl = ''; // 原始图片引用(用于 FLIP 关闭动画) let sourceImg = null; let isAnimating = false; // 已加载原图的 shot ID 集合(跨开闭保持) const loadedOriginals = new Set(); // 清除动画残留的 inline 样式,恢复 flex 居中布局 const IMG_RESET_PROPS = ['position', 'margin', 'maxWidth', 'maxHeight', 'width', 'height', 'left', 'top', 'borderRadius', 'transform', 'opacity']; function resetViewerImg() { IMG_RESET_PROPS.forEach(p => img.style[p] = ''); } // JS rAF 逐帧动画(IT之家风格:直接动画 width/height/left/top) function animateRect(el, from, to, duration, onDone) { const startTime = performance.now(); const props = ['width', 'height', 'left', 'top']; const doOpacity = from.opacity !== undefined && to.opacity !== undefined; function tick(now) { let t = Math.min((now - startTime) / duration, 1); props.forEach(p => { el.style[p] = (from[p] + (to[p] - from[p]) * t) + 'px'; }); if (doOpacity) el.style.opacity = from.opacity + (to.opacity - from.opacity) * t; if (t < 1) requestAnimationFrame(tick); else if (onDone) onDone(); } requestAnimationFrame(tick); } // 从 baseurl 构建原图下载 URL function getDownloadUrl(baseurl, src) { if (baseurl) return baseurl + `?imw=${CONFIG.DL_IMG_SIZE}&imh=${CONFIG.DL_IMG_SIZE}&letterbox=false`; // 降级:修改 CDN 参数获取原图 if (!src) return ''; try { const url = new URL(src); url.searchParams.set('imw', String(CONFIG.DL_IMG_SIZE)); url.searchParams.set('imh', String(CONFIG.DL_IMG_SIZE)); url.searchParams.set('letterbox', 'false'); return url.toString(); } catch { return src; } } // 获取 shot 的唯一标识(用于 loadedOriginals) function getShotKey(shot) { return shot.m_id || shot.m_baseurl || shot.m_img || ''; } // 获取显示用的图片 src:已加载原图则返回原图 URL,否则缩略图 function getDisplaySrc(shot) { if (loadedOriginals.has(getShotKey(shot))) { return getDownloadUrl(shot.m_baseurl, shot.m_img || ''); } return shot.m_img || ''; } // 更新"加载原图"按钮状态 function updateDownloadBtn() { const shot = galleryShots[galleryIndex]; if (!shot) return; downloadBtn.innerHTML = loadedOriginals.has(getShotKey(shot)) ? `${ICONS.download}已加载` : `${ICONS.download}加载原图`; } // 更新切图按钮显示 function updateNavButtons() { const hasMultiple = galleryShots.length > 1; prevBtn.classList.toggle('show', hasMultiple); nextBtn.classList.toggle('show', hasMultiple); counter.classList.toggle('show', hasMultiple); if (hasMultiple) { counter.textContent = `${galleryIndex + 1} / ${galleryShots.length}`; } } // 切换到指定截图 function navigateTo(index) { if (index < 0 || index >= galleryShots.length) return; galleryIndex = index; const shot = galleryShots[index]; const displaySrc = getDisplaySrc(shot); downloadUrl = getDownloadUrl(shot.m_baseurl, shot.m_img || ''); // 切图后恢复原图 opacity 并清除 FLIP 回移动画 if (sourceImg) { sourceImg.style.opacity = ''; sourceImg = null; } // 重置缩放和位移,清除动画残留的 inline 样式 scale = 1; offsetX = 0; offsetY = 0; resetViewerImg(); zoomLabel.textContent = '100%'; img.src = displaySrc; updateNavButtons(); updateDownloadBtn(); } // 显示查看器(JS rAF 逐帧动画,IT之家风格) function openViewer(src, galleryId, shotId, clickedEl) { // 尝试从 Steam 全局变量获取画廊数据 galleryShots = []; galleryIndex = 0; const g_BlotterGalleries = getPageVar('g_BlotterGalleries'); if (galleryId && g_BlotterGalleries && g_BlotterGalleries[galleryId]) { const gallery = g_BlotterGalleries[galleryId]; const shotKeys = Object.keys(gallery.shots || {}); if (shotKeys.length > 0) { galleryShots = shotKeys.map(k => gallery.shots[k]); if (shotId) { const idx = galleryShots.findIndex(s => s.m_id === shotId); if (idx >= 0) galleryIndex = idx; } } } // 单图降级处理 if (galleryShots.length === 0) { galleryShots = [{ m_img: src, m_baseurl: '' }]; galleryIndex = 0; } const shot = galleryShots[galleryIndex]; const displaySrc = getDisplaySrc(shot); downloadUrl = getDownloadUrl(shot.m_baseurl, shot.m_img || ''); scale = 1; offsetX = 0; offsetY = 0; // 保存原始图片引用,隐藏原图(避免视觉重复) sourceImg = clickedEl || null; if (sourceImg) sourceImg.style.opacity = '0'; const sourceRect = clickedEl?.getBoundingClientRect(); // 先隐藏 img,显示 viewer 背景 img.style.opacity = '0'; img.style.transform = ''; zoomLabel.textContent = '100%'; viewer.classList.add('open'); document.body.style.overflow = 'hidden'; // 等图片加载后再启动动画 function startOpenAnimation() { const targetRect = img.getBoundingClientRect(); if (sourceRect && targetRect.width > 0 && targetRect.height > 0) { // 切换到 fixed 定位,放在缩略图位置 img.style.position = 'fixed'; img.style.margin = '0'; img.style.maxWidth = 'none'; img.style.maxHeight = 'none'; img.style.width = sourceRect.width + 'px'; img.style.height = sourceRect.height + 'px'; img.style.left = sourceRect.left + 'px'; img.style.top = sourceRect.top + 'px'; img.style.opacity = '1'; img.style.borderRadius = '0'; isAnimating = true; // 下一帧启动逐帧动画 requestAnimationFrame(() => { animateRect(img, { width: sourceRect.width, height: sourceRect.height, left: sourceRect.left, top: sourceRect.top }, { width: targetRect.width, height: targetRect.height, left: targetRect.left, top: targetRect.top }, CONFIG.ANIM_DURATION, () => { // 动画完成,恢复 flex 居中布局 resetViewerImg(); isAnimating = false; }); }); } else { img.style.opacity = '1'; } } img.onload = () => { img.onload = null; requestAnimationFrame(startOpenAnimation); }; img.src = displaySrc; // 图片已缓存时直接触发 if (img.complete && img.naturalWidth > 0) { img.onload = null; requestAnimationFrame(startOpenAnimation); } updateNavButtons(); updateDownloadBtn(); // 简短提示 setTimeout(() => hint.classList.add('show'), 300); setTimeout(() => hint.classList.remove('show'), 3000); } // 关闭查看器(JS rAF 逐帧回移动画) function closeViewer() { const currentSource = sourceImg; const sourceRect = currentSource?.getBoundingClientRect(); if (sourceRect && !isAnimating) { const currentRect = img.getBoundingClientRect(); if (currentRect.width > 0 && currentRect.height > 0) { // 重置缩放/拖拽 scale = 1; offsetX = 0; offsetY = 0; // 切换到 fixed 定位在当前位置 img.style.position = 'fixed'; img.style.margin = '0'; img.style.maxWidth = 'none'; img.style.maxHeight = 'none'; img.style.width = currentRect.width + 'px'; img.style.height = currentRect.height + 'px'; img.style.left = currentRect.left + 'px'; img.style.top = currentRect.top + 'px'; img.style.transform = ''; img.style.borderRadius = '0'; isAnimating = true; // 背景同步淡出 viewer.classList.remove('open'); requestAnimationFrame(() => { animateRect(img, { width: currentRect.width, height: currentRect.height, left: currentRect.left, top: currentRect.top, opacity: 1 }, { width: sourceRect.width, height: sourceRect.height, left: sourceRect.left, top: sourceRect.top, opacity: 0 }, CONFIG.ANIM_DURATION, () => { cleanup(); }); }); // 兜底 setTimeout(() => cleanup(), 300); return; } } // 无动画直接关闭 cleanup(); function cleanup() { if (cleanup.done) return; cleanup.done = true; viewer.classList.remove('open'); document.body.style.overflow = ''; resetViewerImg(); img.src = ''; downloadUrl = ''; if (sourceImg) { sourceImg.style.opacity = ''; sourceImg = null; } isAnimating = false; galleryShots = []; galleryIndex = 0; prevBtn.classList.remove('show'); nextBtn.classList.remove('show'); counter.classList.remove('show'); } } function updateTransform() { img.style.transform = `scale(${scale}) translate(${offsetX}px, ${offsetY}px)`; } // 滚轮缩放 viewer.addEventListener('wheel', e => { if (!viewer.classList.contains('open')) return; e.preventDefault(); scale = Math.min(CONFIG.ZOOM_MAX, Math.max(CONFIG.ZOOM_MIN, scale + (e.deltaY > 0 ? -CONFIG.ZOOM_STEP : CONFIG.ZOOM_STEP))); updateTransform(); zoomLabel.textContent = Math.round(scale * 100) + '%'; }, { passive: false }); // 拖拽移动 img.addEventListener('mousedown', e => { if (scale <= 1) return; e.preventDefault(); dragging = true; dragStartX = e.clientX; dragStartY = e.clientY; startOffsetX = offsetX; startOffsetY = offsetY; }); document.addEventListener('mousemove', e => { if (!dragging) return; offsetX = startOffsetX + (e.clientX - dragStartX) / scale; offsetY = startOffsetY + (e.clientY - dragStartY) / scale; updateTransform(); }); document.addEventListener('mouseup', () => { dragging = false; }); // 关闭按钮 closeBtn.addEventListener('click', closeViewer); // 点击背景关闭(排除按钮和图片) viewer.addEventListener('click', e => { if (e.target === viewer) closeViewer(); }); // ESC 关闭 + 左右箭头切图 document.addEventListener('keydown', e => { if (!viewer.classList.contains('open')) return; if (e.key === 'Escape') closeViewer(); else if (e.key === 'ArrowLeft') { if (galleryIndex <= 0) { showToast('已经是第一张了', 'warn'); return; } navigateTo(galleryIndex - 1); } else if (e.key === 'ArrowRight') { if (galleryIndex >= galleryShots.length - 1) { showToast('已经是最后一张了', 'warn'); return; } navigateTo(galleryIndex + 1); } }); // Toast 通知(参考许可分类器) let toastTimer = null; let currentToast = null; function showToast(msg, type = 'info', ms = 1500) { // 先移除上一个 toast if (currentToast) { currentToast.remove(); currentToast = null; } clearTimeout(toastTimer); const el = document.createElement('div'); el.className = `sf-toast sf-toast--${type}`; el.textContent = msg; document.body.appendChild(el); currentToast = el; requestAnimationFrame(() => requestAnimationFrame(() => el.classList.add('sf-toast--visible'))); toastTimer = setTimeout(() => { el.classList.remove('sf-toast--visible'); setTimeout(() => { el.remove(); if (currentToast === el) currentToast = null; }, 300); }, ms); } // 切图按钮 prevBtn.addEventListener('click', e => { e.stopPropagation(); if (galleryIndex <= 0) { showToast('已经是第一张了', 'warn'); return; } navigateTo(galleryIndex - 1); }); nextBtn.addEventListener('click', e => { e.stopPropagation(); if (galleryIndex >= galleryShots.length - 1) { showToast('已经是最后一张了', 'warn'); return; } navigateTo(galleryIndex + 1); }); // 加载原图(替换当前缩略图为高清原图) downloadBtn.addEventListener('click', () => { const shot = galleryShots[galleryIndex]; if (!shot) return; const shotKey = getShotKey(shot); if (loadedOriginals.has(shotKey)) return; // 已加载,忽略 const url = downloadUrl || img.src; if (!url) return; downloadBtn.style.opacity = '0.6'; downloadBtn.textContent = '加载中...'; const hdImg = new Image(); hdImg.onload = () => { loadedOriginals.add(shotKey); img.src = url; downloadBtn.style.opacity = '1'; downloadBtn.innerHTML = `${ICONS.download}已加载`; }; hdImg.onerror = () => { downloadBtn.style.opacity = '1'; downloadBtn.innerHTML = `${ICONS.download}加载原图`; }; hdImg.src = url; }); // 拦截截图点击,使用事件委托(捕获阶段优先于 Steam 的处理) const blotter = document.getElementById('blotter_content'); if (!blotter) return; blotter.addEventListener('click', e => { const screenshotLink = e.target.closest('.blotter_screenshot_image a, .blotter_screenshot_gallery_image a, .blotter_screenshot a'); if (!screenshotLink) return; const clickedImg = screenshotLink.querySelector('img') || e.target.closest('img'); if (!clickedImg) return; // 阻止 Steam 默认弹窗 e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); const src = clickedImg.srcset?.split(' ')[0] || clickedImg.src; // 获取画廊 ID const screenshotBlock = clickedImg.closest('.blotter_screenshot'); const galleryId = screenshotBlock?.querySelector('.blotter_screenshot_image')?.id || screenshotBlock?.querySelector('.highlight_strip_scroll')?.dataset.galleryid || ''; // 获取当前截图 ID // 1) 优先从 Steam 全局变量获取当前激活的截图 // 2) 其次从缩略图 id 提取(格式:gallery_XXX_shotId) let shotId = ''; const g_BlotterGalleries = getPageVar('g_BlotterGalleries'); if (galleryId && g_BlotterGalleries && g_BlotterGalleries[galleryId]) { shotId = g_BlotterGalleries[galleryId].m_screenshotActive || ''; } if (!shotId && clickedImg.id) { const parts = clickedImg.id.split('_'); if (parts.length >= 2) shotId = parts[parts.length - 1]; } if (src) openViewer(src, galleryId, shotId, clickedImg); }, true); } /* ========== 等待元素出现 ========== */ function wait(sel, cb) { const el = $(sel); if (el) { cb(el); return; } const obs = new MutationObserver(() => { const found = $(sel); if (found) { obs.disconnect(); cb(found); } }); obs.observe(document.body, { childList: true, subtree: true }); setTimeout(() => { obs.disconnect(); createUI(); fixWidth(); }, CONFIG.WAIT_TIMEOUT); } /* ========== 初始化 ========== */ wait('#friendactivity_right_column', () => { createUI(); fixWidth(); expandGameImages(); fixGamePurchaseImages(); setupLinkTarget(); setupScreenshotViewer(); injectRollupStyles(); // 监听异步加载的新动态,自动展开游戏封面 + 重新筛选 + 注入分类标签 const blotterContent = document.getElementById('blotter_content'); if (blotterContent) { new MutationObserver(mutations => { let needExpand = false, needFilter = false, needBadge = false; for (const m of mutations) { for (const node of m.addedNodes) { if (node.nodeType !== 1) continue; if (node.querySelector?.('.blotter_gamepurchase') || node.classList?.contains('blotter_gamepurchase')) needExpand = true; if (node.querySelector?.('.blotter_block') || node.classList?.contains('blotter_block')) needFilter = true; if (node.querySelector?.('.blotter_daily_rollup') || node.classList?.contains('blotter_daily_rollup')) needBadge = true; } } if (needExpand) { expandGameImages(); fixGamePurchaseImages(); } if (needBadge) injectRollupStyles(); if (needFilter) applyFilter(); }).observe(blotterContent, { childList: true, subtree: true }); } }); // 观察 #blotter_content 的 style 属性变化,自动修复宽度 const blotterEl = document.getElementById('blotter_content'); if (blotterEl) { new MutationObserver(fixWidth).observe(blotterEl, { attributes: true, attributeFilter: ['style'] }); } else { wait('#blotter_content', el => { new MutationObserver(fixWidth).observe(el, { attributes: true, attributeFilter: ['style'] }); }); } })();