Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/lib/components/market/item_row_wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
isCharm,
isBlueSkin,
isHighlightCharm,
isBuggedSkin,
} from '../../utils/skin';
import {gFilterService} from '../../services/filter';
import {AppId, ContextId, Currency} from '../../types/steam_constants';
Expand Down Expand Up @@ -143,6 +144,10 @@ export class ItemRowWrapper extends FloatElement {
return;
}

if (isBuggedSkin(this.asset)) {
return;
}

try {
this.itemInfo = await this.fetchFloat();
} catch (e: any) {
Expand Down Expand Up @@ -236,6 +241,10 @@ export class ItemRowWrapper extends FloatElement {
return nothing;
}

if (isBuggedSkin(this.asset)) {
return nothing;
}

if (this.itemInfo && isSkin(this.asset)) {
const fadePercentage = this.asset && getFadePercentage(this.asset, this.itemInfo)?.percentage;

Expand Down
60 changes: 59 additions & 1 deletion src/lib/components/market/utility_belt.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import {FloatElement} from '../custom';
import {CustomElement, InjectBefore, InjectionMode} from '../injectors';
import {css, html, HTMLTemplateResult} from 'lit';
import {css, html, HTMLTemplateResult, nothing} from 'lit';
import {state} from 'lit/decorators.js';
import '../common/ui/steam-button';
import './page_size';
import './sort_listings';
import '../filter/filter_container';
import {Observe} from '../../utils/observers';
import {isBuggedSkin} from '../../utils/skin';
import {AppId, ContextId} from '../../types/steam_constants';

@CustomElement()
@InjectBefore('#searchResultsRows', InjectionMode.ONCE)
export class UtilityBelt extends FloatElement {
@state()
private buggedSkinCount = 0;

get marketHashName(): string {
return (document.querySelector('.market_listing_nav a:nth-child(2)') as HTMLElement).innerText;
}
Expand All @@ -31,6 +38,16 @@ export class UtilityBelt extends FloatElement {
text-decoration: underline;
font-family: 'Motiva Sans', sans-serif;
}

.bugged-skin-warning {
padding: 8px 12px;
margin-top: 10px;
background-color: rgba(255, 152, 0, 0.15);
border: 1px solid rgba(255, 152, 0, 0.4);
border-radius: 4px;
color: #ffb74d;
font-size: 13px;
}
`,
];

Expand All @@ -45,12 +62,53 @@ export class UtilityBelt extends FloatElement {
?hidden="${!this.marketHashName}"
.key="${this.marketHashName}"
></csfloat-filter-container>
${this.renderBuggedSkinWarning()}
</div>
<csfloat-ad-banner></csfloat-ad-banner>
`;
}

private countBuggedSkins(): number {
try {
const assets = g_rgAssets?.[AppId.CSGO]?.[ContextId.PRIMARY];
if (!assets) return 0;

return Object.values(assets).filter((asset) => isBuggedSkin(asset)).length;
} catch {
return 0;
}
}

private renderBuggedSkinWarning(): HTMLTemplateResult | typeof nothing {
if (this.buggedSkinCount === 0) {
return nothing;
}

return html`
<div class="bugged-skin-warning">
<b>${this.buggedSkinCount} skin${this.buggedSkinCount > 1 ? 's' : ''}</b> on this page cannot display
float data since they're not inspectable in-game (March 4, 2026 update). Valve pls fix.
</div>
`;
}

async connectedCallback() {
super.connectedCallback();

this.buggedSkinCount = this.countBuggedSkins();

Observe(
() => {
try {
return Object.keys(g_rgAssets?.[AppId.CSGO]?.[ContextId.PRIMARY] || {}).join(',');
} catch {
return '';
}
},
() => {
this.buggedSkinCount = this.countBuggedSkins();
},
100
);
}
}
16 changes: 16 additions & 0 deletions src/lib/types/steam.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ export interface rgDescription {
}[];
}

type RequireOnlyOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> &
{
[K in Keys]-?: Required<Pick<T, K>> & Partial<Record<Exclude<Keys, K>, never>>;
}[Keys];

interface rgAssetPropertyBase {
propertyid: number;
int_value?: string;
float_value?: string;
string_value?: string;
}

// Only one of int_value, float_value, or string_value can be present
type rgAssetProperty = RequireOnlyOne<rgAssetPropertyBase, 'int_value' | 'float_value' | 'string_value'>;

// g_rgAssets
export interface rgAsset extends rgDescription {
amount: number;
Expand All @@ -86,6 +101,7 @@ export interface rgAsset extends rgDescription {
unowned_contextid: string;
unowned_id: string;
element?: HTMLElement;
asset_properties?: rgAssetProperty[];
}

export interface rgInventoryAsset {
Expand Down
20 changes: 20 additions & 0 deletions src/lib/utils/skin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,23 @@ export function floor(n: number, precision?: number) {

return Math.floor(n * p) / p;
}

/**
* As part of the March 4/2026 update, skins listed on SCM get moved into the trade protected inventory context
* so the user can still use them in-game while listed. However, Valve introduced a bug where these skins
* can't be inspected in game.
*
* Detect and skip these skins to prevent overloaded errors to the user and browser request throttling.
*/
export function isBuggedSkin(asset: rgAsset | undefined): boolean {
if (!asset || !isSkin(asset)) {
return false;
}

if (asset.unowned_contextid !== '16') {
return false;
}

const fv = (asset.asset_properties || []).find((prop) => prop.propertyid === 2);
return !fv;
}