steam-theming-utils 2.0.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,23 +1,51 @@
1
1
  # steam-theming-utils
2
2
 
3
- A collection of scripts for easier (and futureproof) Steam theming.
3
+ A collection of scripts for easier (and future-proof) Steam theming.
4
4
 
5
5
  ## Usage
6
6
 
7
7
  ```sh
8
8
  $ npm i steam-theming-utils
9
- $ npx steam-theming-utils <script>
9
+ $ npx steam-theming-utils <script> <page>
10
10
  ```
11
11
 
12
12
  Note that running any script requires Steam running with `-cef-enable-debugging`.
13
13
 
14
- ## Script list
14
+ ## Scripts
15
15
 
16
- | Name | Description |
17
- | --------------------- | ----------------------------------------------------------------------------------------------- |
18
- | build_class_modules | Generates a `class_map.json` file for usage with other scripts. |
19
- | build_theme | Builds the theme. Requires a [specific directory structure][template]. |
20
- | make_readable_classes | Adds readable versions of classes to the focused window. ![Preview](./img/readable_classes.png) |
21
- | replace_old_classes | Replaces old classes with new ones for themes not using the [template][template]. |
16
+ | Name | Description |
17
+ | --------------------- | ----------------------------------------------------------------------------------------- |
18
+ | build_class_modules | Generates a class map file for usage with other scripts. |
19
+ | make_readable_classes | Adds readable versions of classes to the focused window/page. ![Preview][classes-preview] |
20
+ | replace_old_classes | Replaces old classes with new ones for themes not using the [template][template]. |
22
21
 
22
+ ## Pages
23
+
24
+ | Name | Description |
25
+ | ------------------ | ---------------------------------------------------------------------------- |
26
+ | apppage | Related items & controller info in https://store.steampowered.com/app/666220 |
27
+ | accountpreferences | https://store.steampowered.com/account |
28
+ | client | The Steam client. The default. |
29
+ | gameslist | https://steamcommunity.com/my/games |
30
+ | notificationspage | https://steamcommunity.com/my/notifications |
31
+ | profileedit | https://steamcommunity.com/my/edit |
32
+ | shoppingcart | https://store.steampowered.com/cart |
33
+
34
+ ## Errors
35
+
36
+ | Name | Description |
37
+ | ------------------------- | ------------------------------------------------------------------------------------------------- |
38
+ | #ClassName is undefined | Typo or that class either got renamed or removed, and so you will have to update it yourself. |
39
+ | [mod_name] no such module | Typo or that module either got renamed or removed, see diffs [here][diffs] depending on the page. |
40
+ | [map_name] no such map | Use `npx steam-theming-utils build_class_modules map_name` to create it. |
41
+
42
+ ## Config
43
+
44
+ Configured through a `steam-theming-utils.config.js` (or the one from [here][config-files]) file that must `export default` an [object][config-docs]. It's optional and has defaults listed [here][config-defaults].
45
+
46
+ [classes-preview]: ./img/readable_classes.png
47
+ [config-defaults]: https://github.com/ricewind012/steam-theming-utils/blob/master/src/constants.js#L7-L11
48
+ [config-docs]: https://github.com/ricewind012/steam-theming-utils/blob/master/src/api.d.ts#L4-L16
49
+ [config-files]: https://github.com/cosmiconfig/cosmiconfig#usage-for-end-users
50
+ [diffs]: https://github.com/ricewind012/steam-theming-utils/tree/master/cdp/db
23
51
  [template]: https://github.com/ricewind012/more-advanced-theme-template
@@ -1,8 +1,8 @@
1
1
  classModules = {
2
- ...specialModules,
3
- ...parsedModules
4
- .map((e) => ({ [e[0]]: e[1] }))
5
- .reduce((a, b) => Object.assign(a, b)),
2
+ ...(window.specialModules || {}),
3
+ ...(window.parsedModules
4
+ ?.map((e) => ({ [e[0]]: e[1] }))
5
+ .reduce((a, b) => Object.assign(a, b)) || {}),
6
6
  ...exportedModules
7
7
  .flatMap((a) => {
8
8
  const mod = findFirstModule(a[1], a[0]);
@@ -1,6 +1,7 @@
1
1
  let initReq;
2
2
  const webpackCache = {};
3
- window.webpackChunksteamui.push([
3
+ const webpackGlobal = Object.keys(window).find((e) => e.startsWith("webpack"));
4
+ window[webpackGlobal].push([
4
5
  [Math.random()],
5
6
  {},
6
7
  (r) => {
@@ -0,0 +1,11 @@
1
+ // Note that every module is only available on the "family management" page.
2
+ exportedModules = [
3
+ ["authorizeddevices", (e) => e.AuthorizedDeviceGroup],
4
+ ["cookies", (e) => e.CookieSection],
5
+ ["familymanagement", (e) => e.FamilySettingsContainer],
6
+ ["familymanagementtabs", (e) => e.GraphicalAssetsTabs],
7
+ // TODO: shared with steam settings notif page
8
+ ["notifications", (e) => e.NotificationSection],
9
+ // TODO: gamepaddialog
10
+ ["toggle", (e) => e.Field],
11
+ ];
@@ -0,0 +1,12 @@
1
+ exportedModules = [
2
+ ["storeitemscarousel", (e) => e.StoreItemsCarousel],
3
+ ["capsule", (e) => e.MainCapsuleImageContainer],
4
+ ["capsulecontainer", (e) => e.ItemCount1],
5
+ ["carousel", (e) => e.pipScrollerContainer],
6
+ ["creatorhome", (e) => e.FollowBtnText],
7
+ ["creatorhomecarousel", (e) => e.CreatorHomeWithItems],
8
+ ["focusring", (e) => e.FocusRingRoot],
9
+ ["gamehover", (e) => e.GameHoverCapsuleCtn],
10
+ ["scrollsnapcarousel", (e) => e.ScrollSnapCarousel],
11
+ ["sidebarcontrollerinfo", (e) => e.StoreSidebarContainer],
12
+ ];
@@ -44,14 +44,9 @@ specialModules = {
44
44
  window.parsedModules = [
45
45
  ...(
46
46
  await Promise.all(
47
- [
48
- "awardicon",
49
- "broadcast",
50
- "chunk~1a96cdf59", // Also broadcast
51
- "gamenotes",
52
- "gamerecording",
53
- ].map(async (e) =>
54
- (await fetch(`https://steamloopback.host/${e}.js`)).text(),
47
+ ["awardicon", "broadcast", "gamenotes", "gamerecording"].map(
48
+ async (e) =>
49
+ (await fetch(`https://steamloopback.host/${e}.js`)).text(),
55
50
  ),
56
51
  )
57
52
  )
@@ -67,8 +62,6 @@ specialModules = {
67
62
  return "awardicon";
68
63
  case exists("PopOutVideoTitleBar"):
69
64
  return "broadcastembeddable";
70
- case exists("BroadcastPlayerLite"):
71
- return "broadcastplayer";
72
65
  case exists("StoreSaleImage_mini"):
73
66
  return "broadcastwidgets";
74
67
  case exists("ClipUploadStatus"):
@@ -78,7 +71,7 @@ specialModules = {
78
71
  case exists("GameNotesPopup"):
79
72
  return "gamenotespopups";
80
73
  case exists("ClipSavedHint"):
81
- return "gamerecording";
74
+ return "gamerecordingclip";
82
75
  case exists("LinkHelp"):
83
76
  return "pmhover";
84
77
  case exists("CommandButton"):
@@ -124,13 +117,16 @@ exportedModules = [
124
117
  ["appdetailsfriendssection", (e) => e.FriendsOverflow],
125
118
  ["appdetailsgameinfocontainer", (e) => e.GameInfoShadow],
126
119
  ["appdetailsgameinfopanel", (e) => e.GameDescription],
127
- ["appdetailsheader", (e) => e.AppDetailsHeader],
120
+ ["appdetailsheader", (e) => e.AppDetailsOverviewPanel],
121
+ ["appdetailsheaderinfo", (e) => e.HeaderFriendsInGameBadge],
128
122
  ["appdetailshover", (e) => e.AppDetailsHover],
123
+ ["appdetailsinfoicon", (e) => e.MoreInfoIcon],
129
124
  ["appdetailsinvalidostype", (e) => e.InvalidOSTypeBody],
130
125
  ["appdetailsmastersubincluded", (e) => e.IncludedBanner],
131
126
  ["appdetailsnotessection", (e) => e.NoteLink],
132
127
  ["appdetailsoverview", (e) => e.SeekTarget],
133
128
  ["appdetailsplaysection", (e) => e.PermanentlyUnavailable],
129
+ ["appdetailsposttextentry", (e) => e.PostTextEntryArea],
134
130
  ["appdetailsprimarylinkssection", (e) => e.NavButton],
135
131
  ["appdetailsreviewsection", (e) => e.EditMyReview],
136
132
  ["appdetailsscreenshotssection", (e) => e.ScreenshotsSection],
@@ -175,10 +171,13 @@ exportedModules = [
175
171
  ["bluetoothsettings", (e) => e.NotConnectedLabel && e.Header],
176
172
  ["borrowgamedialog", (e) => e.BorrowGameDialog],
177
173
  ["bottombar", (e) => e.BottomBarContainer],
174
+ ["bottombarprogressbar", (e) => e.loadingBarAnim],
178
175
  ["boxcarousel", (e) => e.BoxCarousel],
176
+ ["bpmfriendsuicontainer", (e) => e.FriendsChatsContainer],
179
177
  ["broadcastchat", (e) => e.BroadcastChat],
180
178
  ["broadcastchatannouncement", (e) => e.GiveawayWinnerBox],
181
179
  ["broadcastfirsttime", (e) => e.BroadcastFirstTimeDialog],
180
+ ["broadcastplayer", (e) => e.BroadcastPlayerLite],
182
181
  ["broadcastsettings", (e) => e.ConfigureMic],
183
182
  ["broadcaststatus", (e) => e.BroadcastStatusBody],
184
183
  ["browserviewfindinpage", (e) => e.ControlButton],
@@ -199,39 +198,38 @@ exportedModules = [
199
198
  ["clanimagechooser", (e) => e.ImagesOuterContainer],
200
199
  ["clanimagepickandresize", (e) => e.Image && Object.keys(e).length === 1],
201
200
  ["clipdecorator", (e) => e.ClipDecorator],
202
- ["clipgolive", (e) => e.GoLiveButton],
203
201
  ["clipmanager", (e) => e.ClipActions],
204
202
  ["clipplayback", (e) => e.PlaybackControlsCtn],
205
- ["clipplayheadghost", (e) => e.GhostPlayheadCtn],
203
+ ["clipplaybackscrollbar", (e) => e.ScrollBarCtn],
204
+ ["clipplayheadseekscrubber", (e) => e.GhostPlayheadCtn],
206
205
  ["clipplaybackskip", (e) => e.SkipperCtn],
207
- ["clipplaybacktimeline", (e) => e.ScrollBarCtn],
208
206
  ["clipplayhead", (e) => e.PlayHeadContainer && !e.FullMask],
209
- ["clippostgame", (e) => e.PostGameSummaryClip],
207
+ ["clippostgame", (e) => e.PostGameSummaryHighlightGroup],
210
208
  ["clipsbutton", (e) => e.ClipsButtonContainer],
211
209
  ["cliptimeline", (e) => e.GamepadTimelineContainer],
212
- ["cliptimelinebuffer", (e) => e.LiveRecordingBuffer],
210
+ ["cliptimelinebackgroundticks", (e) => e.TimeTick],
213
211
  ["cliptimelinecontextmenu", (e) => e.TimelineContextMenuItem],
214
- ["cliptimelinegamemode", (e) => e.GameModeMarker],
212
+ ["cliptimelinedatedecorator", (e) => e.TimelineRelativeDate],
213
+ ["cliptimelinegamemodes", (e) => e.GameModeMarker],
215
214
  ["cliptimelinemarker", (e) => e.MarkerBacking],
216
215
  ["cliptimelinemarkercontainer", (e) => e.TimelineMarkerCtn],
217
- ["cliptimelinerelativedate", (e) => e.TimelineRelativeDate],
218
- ["cliptimelinetick", (e) => e.TimeTick],
216
+ ["cliptimelinerangeselector", (e) => e.TrackRangeControls && !e.ActiveCtn],
217
+ ["cliptimelinerecordingdecorator", (e) => e.LiveRecordingBuffer],
219
218
  ["cloudconflict", (e) => e.ConflictChoiceText],
220
219
  ["cloudfileuploadprogress", (e) => e.UploadPreviewContainer],
221
- ["collapseicon", (e) => e.CollapseIconParent],
222
220
  ["collectionbanner", (e) => e.CollectionShelfBanner],
223
221
  ["collectionview", (e) => e.DynamicCollectionLabelAndButton],
224
222
  ["colorsettings", (e) => e.FloatingControls],
225
- ["comment_thread", (e) => e.ActivityCommentThread],
223
+ ["commentthread", (e) => e.ActivityCommentThread],
226
224
  ["console", (e) => e.Console],
227
225
  ["contentmanagement", (e) => e.ContentManagement],
228
- ["contextmenu", (e) => e.ContextMenuFocusContainer],
226
+ ["contextmenu", (e) => e.ContextMenuMouseOverlay],
229
227
  ["controllerconfigurator", (e) => e.ControllerConfiguratorActionSetSelector],
230
228
  [
231
- "controllerconfigurator_interstitial",
229
+ "controllerconfiguratorinterstitial",
232
230
  (e) => e.ConfiguratorInterstitialContainer,
233
231
  ],
234
- ["controllerconfigurator_mouseposition", (e) => e.BackgroundScreenshot],
232
+ ["controllerconfiguratormouseposition", (e) => e.BackgroundScreenshot],
235
233
  [
236
234
  "controllerconfiguratoractionsetselector",
237
235
  (e) => e.ActionSetNameOverIndicators,
@@ -243,11 +241,11 @@ exportedModules = [
243
241
  ["controllerconfiguratorsummary", (e) => e.StandardControl],
244
242
  ["controllerconfiguratorvirtualmenus", (e) => e.VirtualMenus],
245
243
  [
246
- "controllerconfiguratorvisualizer_deadzones",
244
+ "controllerconfiguratorvisualizerdeadzones",
247
245
  (e) => e.VisualizerCenterXOffset,
248
246
  ],
249
247
  ["controllersettings", (e) => e.ControllerName],
250
- ["controllersettings_devicecalibration", (e) => e.CalibrationButton],
248
+ ["controllersettingsdevicecalibration", (e) => e.CalibrationButton],
251
249
  ["creatorhomeembed", (e) => e.DevSummaryCtn],
252
250
  ["cropmodal", (e) => e.CropImage],
253
251
  ["cssgrid", (e) => e.CSSGrid],
@@ -263,12 +261,12 @@ exportedModules = [
263
261
  ["desktopsecuritysettings", (e) => e.SteamGuardIcon],
264
262
  ["desktoptoasts", (e) => e.DesktopToastPopup],
265
263
  ["dialogs", (e) => e.DialogTitle && e.DialogContent],
266
- ["discoveryqueuewidget", (e) => e.DiscoveryQueueCarousel],
264
+ ["discoveryqueuewidget", (e) => e.DiscoveryQueueApp],
267
265
  ["discoveryqueuewizard", (e) => e.DeckVerifiedLogo],
268
266
  ["discussionwidget", (e) => e.DiscussContainer],
269
267
  ["displayscaledialog", (e) => e.YouCanChangeThisLater],
270
268
  ["displaysettings", (e) => e.TimeRangeControls && !e.BandwidthInput],
271
- ["downloadgraph", (e) => e.DownloadGraph],
269
+ ["downloadgraph", (e) => e.DownloadGraphStats],
272
270
  ["downloads", (e) => e.InProgress],
273
271
  ["downloadsettings", (e) => e.BandwidthLimit],
274
272
  ["draganddrop", (e) => e.GhostContainer],
@@ -294,7 +292,7 @@ exportedModules = [
294
292
  ["familysettings", (e) => e.AuthorizeUserField],
295
293
  ["familysharedcomponents", (e) => e.FamilyMemberRowTop],
296
294
  ["fastscrolloverlay", (e) => e.FastScrollOverlay],
297
- ["focusring", (e) => e.FocusRing],
295
+ ["focusring", (e) => e.FocusRingRoot],
298
296
  ["footer", (e) => e.BasicFooter],
299
297
  ["footericons", (e) => e.Knockout],
300
298
  ["friendinvites", (e) => e.IncomingInvites],
@@ -308,6 +306,7 @@ exportedModules = [
308
306
  ["gamelaunchingdialog", (e) => e.GameLaunchingDialog],
309
307
  ["gamelist", (e) => e.NoSearchResultsContainer],
310
308
  ["gamelistbar", (e) => e.GameListHomeAndSearch],
309
+ ["gamelistcollapseicon", (e) => e.CollapseIconParent],
311
310
  ["gamelistcollectionmenu", (e) => e.CollectionDeleteContainer],
312
311
  ["gamelistdropdown", (e) => e.ScrollToTop],
313
312
  ["gamelistentry", (e) => e.GameListEntryName],
@@ -332,17 +331,19 @@ exportedModules = [
332
331
  ["gamepadtabbedpage", (e) => e.CanBeHeaderBackground],
333
332
  ["gamepadtoasts", (e) => e.GamepadToastPlaceholder],
334
333
  ["gamepadui", (e) => e.GamepadUIPopupWindowBody],
335
- ["gamepadui_svg_library", (e) => e.WifiBar1],
334
+ ["gamepaduisvglibrary", (e) => e.WifiBar1],
336
335
  ["gamepaduiappoverlay", (e) => e.OverlayPosition],
337
336
  ["gamepaduiappoverlayvirtualmenucontainer", (e) => e.VirtualMenuContainer],
338
337
  ["gamerecordingaudiosessionfield", (e) => e.VU],
338
+ ["gamerecordingbitrate", (e) => e.BitrateButton],
339
339
  ["gamerecordingdesktopdialog", (e) => e.GameRecordingDesktopDialog],
340
340
  ["gamerecordingplayer", (e) => e.GameRecordingPlayer],
341
- ["gamerecordingplayermouselistener", (e) => e.MouseListenerContainer],
342
341
  ["gamerecordingquickaccess", (e) => e.BackgroundRecordingQuickSettingRow],
343
342
  ["gamerecordingsettings", (e) => e.RecordingMode],
344
343
  ["gamerecordingshare", (e) => e.ExportProgressContainer],
345
344
  ["gamerecordingtour", (e) => e.TourBox],
345
+ ["gamerecordingwarning", (e) => e.WarningBox && !e.VU],
346
+ ["golivebutton", (e) => e.GoLiveButton],
346
347
  ["guidedtour", (e) => e.PageIndicator],
347
348
  ["gyroscopenoisebar", (e) => e.RotateChilden],
348
349
  ["hardwaresurveydialog", (e) => e.HardwareSurveySections],
@@ -353,7 +354,6 @@ exportedModules = [
353
354
  ["hoverposition", (e) => e.HoverPosition],
354
355
  ["htmlpopupdialog", (e) => e.HTMLPopupDialog],
355
356
  ["index", (e) => e.ThreeDots],
356
- ["infoicon", (e) => e.MoreInfoIcon],
357
357
  ["insetshadow", (e) => e.FriendsListInsetShadowCtn],
358
358
  ["installdialog", (e) => e.DownloadItem],
359
359
  ["installrequest", (e) => e.AppSizeRequired],
@@ -397,7 +397,9 @@ exportedModules = [
397
397
  ["messages", (e) => e.MsgWithAddons],
398
398
  ["miniprofile", (e) => e.playerContent],
399
399
  ["modals", (e) => e.BodyNoScroll],
400
+ ["mouselistenercontainer", (e) => e.MouseListenerContainer],
400
401
  ["moveappsdialog", (e) => e.MoveAppsDialog],
402
+ ["multiselectactions", (e) => e.MultiSelectActionDialogContainer],
401
403
  ["mustupdateclientdialog", (e) => e.MustUpdateClientModalContent],
402
404
  ["networkconnectiondialog", (e) => e.ConnectionStatus],
403
405
  ["networkdiagnosticsdialog", (e) => e.ColumnDisplayName],
@@ -419,7 +421,8 @@ exportedModules = [
419
421
  ["overlaydialogs", (e) => e.Invited],
420
422
  ["overlayguides", (e) => e.GuidesHomeHeaderDesc],
421
423
  ["overlaytimeline", (e) => e.KeyboardCapture],
422
- ["overlaytimelinecontainer", (e) => e.OverlayPopup && !e.BackgroundRecording],
424
+ ["overlaytimelinecontainer", (e) => e.OverlayPopup && e.BackgroundRecording],
425
+ ["overlaytimelinepreview", (e) => e.ThumbnailHitBoxPadding],
423
426
  ["overlaytimer", (e) => e.OverlayClock],
424
427
  ["pageablecontainer", (e) => e.HeaderPageControls],
425
428
  ["pagedcontent", (e) => e.NavTitle],
@@ -442,7 +445,6 @@ exportedModules = [
442
445
  ["personastatusicons", (e) => e.PersonaStatusIcon],
443
446
  ["pininput", (e) => e.DigitInputField],
444
447
  ["playersdialog", (e) => e.PlayersDialog],
445
- ["posttextentry", (e) => e.PostTextEntryArea],
446
448
  ["powermenu", (e) => e.SuspendDialog],
447
449
  ["presenterpopup", (e) => e.Speaker],
448
450
  ["progressbar", (e) => e.ProgressBarFieldStatus],
@@ -462,11 +464,11 @@ exportedModules = [
462
464
  ["reactions", (e) => e.ReactorName],
463
465
  ["recentchatssteamdeck", (e) => e.RecentChatsList],
464
466
  ["recentlycompleted", (e) => e.RecentlyCompleted],
465
- ["recordingicon", (e) => e.RecordingIconContainer],
466
467
  ["reloadingdialog", (e) => e.Popup && Object.keys(e).length === 1],
467
468
  ["remainderlist", (e) => e.ItemWrapper],
468
469
  ["remoteplay", (e) => e.ContentForm],
469
470
  ["remoteplaydialog", (e) => e.SegmentedInput && Object.keys(e).length === 3],
471
+ ["remoteplayexplainerdialog", (e) => e.AppStoreContainer],
470
472
  ["remoteplaysettings", (e) => e.SubSetting],
471
473
  ["removefreeappdialog", (e) => e.RemovingText],
472
474
  ["removegamehover", (e) => e.RemoveBoxTransition],
@@ -493,8 +495,9 @@ exportedModules = [
493
495
  ["segmentedinputs", (e) => e.SegmentedCharacterInput && !e.Text],
494
496
  ["serverbrowserdialog", (e) => e.ServerBrowserDialog],
495
497
  ["settings", (e) => e.SettingsDialogSubHeader],
496
- ["shared_common", (e) => e.v6 && e.AvatarImage],
497
- ["shared_svg_library", (e) => e.SteamDeckCompatLogo],
498
+ ["sharedcommon", (e) => e.v6 && e.AvatarImage],
499
+ ["sharedsvggamerecordings", (e) => e.RecordingIconContainer],
500
+ ["sharedsvglibrary", (e) => e.SteamDeckCompatLogo],
498
501
  ["sharedappdetailsheader", (e) => e.BoxSizerDelete],
499
502
  ["sharedialog", (e) => e.ShareButton && e.ShareIcon],
500
503
  ["sharescreenshotupload", (e) => e.ShareScreenshotDialog],
@@ -0,0 +1,10 @@
1
+ exportedModules = [
2
+ // shared
3
+ ["gamehover", (e) => e.ItemHoverSource],
4
+ ["gameslist", (e) => e.Gameslistapp],
5
+ ["entry", (e) => e.GamesListItemContainer],
6
+ ["search", (e) => e.SearchIcon],
7
+ ["rareachievementglow", (e) => e.RareAchievementIconGlowContainerRoot],
8
+ ["remote", (e) => e.RemoteDownloadHeader],
9
+ ["tabs", (e) => e.TabBaseline],
10
+ ];
@@ -0,0 +1,12 @@
1
+ exportedModules = [
2
+ ["notificationspage", (e) => e.NotificationPageCtn],
3
+ [
4
+ "notification",
5
+ (e) =>
6
+ e.StandardTemplateContainer &&
7
+ !e.AllNotificationsCommentPlus &&
8
+ !e.ShortTemplate,
9
+ ],
10
+ // TODO: has DesktopToastTemplate, BottomBar, etc., wtf?
11
+ ["notificationcontainer", (e) => e.SteamNotificationWrapper],
12
+ ];
@@ -0,0 +1,21 @@
1
+ exportedModules = [
2
+ ["avatar", (e) => e.AvatarDialogUploadArea],
3
+ ["avatarcollection", (e) => e.AvatarCollection],
4
+ ["avatarcrop", (e) => e.AvatarCrop],
5
+ ["body", (e) => e.ProfileEditRoot],
6
+ ["favoritebadge", (e) => e.Badge],
7
+ ["favoritegroup", (e) => e.FavoriteGroup],
8
+ ["friendsnooze", (e) => e.SnoozeContainer],
9
+ ["miniprofile", (e) => e.miniProfile],
10
+ ["miniprofilepreview", (e) => e.MiniProfilePreview],
11
+ ["personastatusicons", (e) => e.PersonaStatusIcon],
12
+ ["profilebackground", (e) => e.ProfileBackgroundEquipOption],
13
+ ["profileedit", (e) => e.ProfileEditRoot],
14
+ ["profilemodifier", (e) => e.ProfileModifierPreview],
15
+ ["profilepreview", (e) => e.ProfilePreviewCtn],
16
+ ["profiletheme", (e) => e.ProfileThemePicker],
17
+ ["profilethemecolors", (e) => e.SteamDeckTheme],
18
+ ["shell", (e) => e.ProfileEditStoreLink],
19
+ ["steamavatar", (e) => e.avatarFrame],
20
+ ["summary", (e) => e.summaryTextArea],
21
+ ];
@@ -0,0 +1,9 @@
1
+ exportedModules = [
2
+ ["shoppingcart", (e) => e.ShoppingCartModal],
3
+ ["shoppingcartcount", (e) => e.ShoppingCartCountCtn],
4
+ ["shoppingcartitem", (e) => e.DropDownThin],
5
+ ["giftrecipient", (e) => e.GiftRecipientPickerFormCtn],
6
+ ["storesalewidget", (e) => e.StoreSalePriceWidget],
7
+ ["tradingcart", (e) => e.TradingCardContainer],
8
+ ["upsell", (e) => e.CartUpsellArea],
9
+ ];
@@ -1,3 +1,7 @@
1
+ /**
2
+ * @param {string} className
3
+ * @returns the readable class name.
4
+ */
1
5
  function getNormalClass(className) {
2
6
  for (const key of Object.keys(classModules)) {
3
7
  const mod = classModules[key];
@@ -9,8 +13,15 @@ function getNormalClass(className) {
9
13
 
10
14
  return [key, name].join("_");
11
15
  }
16
+
17
+ notInDb.push(className);
12
18
  }
13
19
 
20
+ /**
21
+ * Sets readable classes to a `data-readable-class` attribute.
22
+ *
23
+ * @param {HTMLElement} el
24
+ */
14
25
  function normalizeElement(el) {
15
26
  const readableClasses = [...el.classList]
16
27
  .map(getNormalClass)
@@ -21,21 +32,41 @@ function normalizeElement(el) {
21
32
  return;
22
33
  }
23
34
 
24
- el.setAttribute("data-readableclass", `\n${readableClasses}\n`);
35
+ el.dataset.readableClass = `\n${readableClasses}\n`;
25
36
  }
26
37
 
27
- function onFocus({ target }) {
38
+ /**
39
+ * @param {FocusEvent}
40
+ */
41
+ function main({ target }) {
28
42
  const elements = target.document.querySelectorAll("[class]");
29
43
  for (const el of elements) {
30
44
  normalizeElement(el);
31
45
  }
32
46
 
33
- for (const popup of popups) {
34
- popup.removeEventListener("focus", onFocus);
47
+ if (inClient) {
48
+ for (const popup of popups) {
49
+ popup.removeEventListener("focus", main);
50
+ }
51
+ }
52
+
53
+ if (notInDb.length > 0) {
54
+ const classes = [...new Set(notInDb)];
55
+ console.error(
56
+ "%s classes are not in the classes db: %o",
57
+ classes.length,
58
+ classes.sort(),
59
+ );
35
60
  }
36
61
  }
37
62
 
38
- popups = [...g_PopupManager.GetPopups()].map((e) => e.m_popup);
39
- for (const popup of popups) {
40
- popup.addEventListener("focus", onFocus);
63
+ inClient = !!SteamClient.User;
64
+ notInDb = [];
65
+ if (inClient) {
66
+ window.popups = [...g_PopupManager.GetPopups()].map((e) => e.m_popup);
67
+ for (const popup of popups) {
68
+ popup.addEventListener("focus", main);
69
+ }
70
+ } else {
71
+ main({ target: window });
41
72
  }
@@ -0,0 +1,6 @@
1
+ const elements = document.getElementsByClassName(
2
+ classModules.profileedit.NavLink,
3
+ );
4
+ for (const btn in elements) {
5
+ btn.click();
6
+ }
@@ -1,26 +1,126 @@
1
1
  import fs from "node:fs";
2
- import cp from "node:child_process";
3
2
  import path from "node:path";
4
- import { runCdpFile, runWithResult, sleep } from "../src/api.js";
5
- import { CLASS_MAP_FILE } from "../src/constants.js";
3
+ import prettier from "prettier";
4
+ import {
5
+ connection,
6
+ config,
7
+ run,
8
+ runCdpFile,
9
+ runWithResult,
10
+ sleep,
11
+ } from "../src/api.js";
12
+ import { createWebConnection, getPageUrl } from "../src/shared.js";
6
13
 
7
- export async function execute() {
8
- const webpackRan = await runWithResult("!!webpackCache");
9
- const forceWebpackRerun = await runWithResult("forceDflRerun");
14
+ /**
15
+ * BrowserView event to listen for on page load.
16
+ */
17
+ const BROWSER_EVENT = "finished-request";
18
+
19
+ /**
20
+ * @type {Record<import("../src/api").Page, string>}
21
+ */
22
+ const SELECTORS = {
23
+ accountpreferences: "[data-featuretarget]",
24
+ gameslist: "[data-featuretarget='gameslist-root']",
25
+ notificationspage: "#react_root",
26
+ profileedit: "#react_root",
27
+ shoppingcart: "[data-featuretarget='react-root']",
28
+ storeitemscarousel: "[data-featuretarget$='-carousel']",
29
+ };
30
+
31
+ async function sleepUntilResult(...args) {
32
+ while (!(await runWithResult(...args))) {
33
+ await sleep(10);
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Gets a CDP web connection, accounting for the needed React part to load.
39
+ *
40
+ * @param {import("../src/api").Page | "client"} page
41
+ */
42
+ async function getWebConn(page) {
43
+ if (page === "client") {
44
+ return null;
45
+ }
46
+
47
+ const openedConn = await createWebConnection(page).catch(() => {});
48
+ if (openedConn) {
49
+ return openedConn;
50
+ }
51
+
52
+ const { url } = await getPageUrl(page);
53
+ await run(`
54
+ function onFinishedRequest() {
55
+ window._finished = true;
56
+ browser.off("${BROWSER_EVENT}", onFinishedRequest);
57
+ }
58
+
59
+ browser = SteamClient.BrowserView.Create();
60
+ browser.LoadURL("${url}");
61
+ browser.on("${BROWSER_EVENT}", onFinishedRequest);
62
+ `);
63
+
64
+ console.log("Waiting for page load...");
65
+ await sleepUntilResult("window._finished");
66
+ const conn = await createWebConnection(page).catch((e) => {
67
+ console.log("%s\nNo page whose URL is %o has been found.", e.message, url);
68
+ process.exit(1);
69
+ });
70
+
71
+ const selector = `${SELECTORS[page]}:not(:empty)`;
72
+ const expression = `!!document.querySelector("${selector}")`;
73
+ console.log("Waiting for %o selector...", selector);
74
+ await sleepUntilResult(expression, conn);
75
+
76
+ return conn;
77
+ }
78
+
79
+ /**
80
+ * @param {import("../src/api").Page} page
81
+ * @param {import("chrome-remote-interface").Client} conn
82
+ */
83
+ async function doTheThing(page, conn) {
84
+ const webpackRan = await runWithResult("!!webpackCache", conn);
85
+ const forceWebpackRerun = await runWithResult("forceWebpackRerun", conn);
86
+ const preloadFile = path.join("preload", `${page}.js`);
10
87
  if (!webpackRan || forceWebpackRerun) {
11
- await runCdpFile("class_modules_webpack.js");
88
+ await runCdpFile("class_modules_webpack.js", conn);
89
+ }
90
+ if (fs.existsSync(preloadFile)) {
91
+ await runCdpFile(preloadFile, conn);
12
92
  }
13
- await runCdpFile("class_modules_db.js");
93
+ await runCdpFile(path.join("db", `${page}.js`), conn);
94
+
95
+ const dirPath = path.join(path.basename(process.cwd()), config.classMaps);
96
+ const filePath = path.join(dirPath, `${page}.json`);
14
97
 
15
- const filePath = path.join(process.cwd(), CLASS_MAP_FILE);
16
- // Sleep for a bit to not error on first launch.
17
- await sleep(10);
18
- const output = await runCdpFile("class_modules.js");
98
+ const output = await runCdpFile("class_modules.js", conn);
19
99
  const [classModules, allModules] = await runWithResult(
20
100
  "[Object.keys(classModules).length, allModules.length]",
101
+ conn,
21
102
  );
22
103
 
23
- fs.writeFileSync(filePath, JSON.stringify(output));
24
- cp.spawnSync("npx", ["@biomejs/biome", "format", "--write", filePath]);
104
+ const content = await prettier.format(JSON.stringify(output), {
105
+ parser: "json-stringify",
106
+ });
107
+ fs.mkdirSync(dirPath, { recursive: true });
108
+ fs.writeFileSync(filePath, content);
25
109
  console.log("Wrote %s/%s modules to %o", classModules, allModules, filePath);
26
110
  }
111
+
112
+ export async function execute(page = "client") {
113
+ const isClient = page === "client";
114
+ const webConn = await getWebConn(page);
115
+
116
+ const conn = isClient ? connection : webConn;
117
+ await doTheThing(page, conn);
118
+
119
+ webConn?.close();
120
+ if (!isClient) {
121
+ await run(`
122
+ window._finished = false;
123
+ SteamClient.BrowserView.Destroy(browser);
124
+ `);
125
+ }
126
+ }
@@ -1,9 +1,16 @@
1
- import { readScript, runCdpFile } from "../src/api.js";
1
+ import { connection, readScript, runCdpFile } from "../src/api.js";
2
+ import { createWebConnection } from "../src/shared.js";
3
+
4
+ export async function execute(page = "client") {
5
+ const isClient = page === "client";
6
+ const webConn = isClient ? null : await createWebConnection(page);
7
+ const conn = isClient ? connection : webConn;
2
8
 
3
- export async function execute() {
4
9
  const script = await readScript("build_class_modules");
5
- await script.execute();
10
+ await script.execute(page);
6
11
 
7
- console.log("Waiting for focus...");
8
- await runCdpFile("make_readable_classes.js");
12
+ if (isClient) {
13
+ console.log("Waiting for focus...");
14
+ }
15
+ await runCdpFile("make_readable_classes.js", conn);
9
16
  }
@@ -1,9 +1,12 @@
1
1
  import fs from "node:fs";
2
+ import path from "node:path";
2
3
  import postcss from "postcss";
3
- import { CLASS_MAP_FILE } from "../src/constants.js";
4
- import { readFile, selectorReplacerPlugin } from "../src/shared.js";
4
+ import { config } from "../src/api.js";
5
+ import { readFile } from "../src/shared.js";
5
6
 
6
- const OLD_CLASS_MAP_FILE = `old_${CLASS_MAP_FILE}`;
7
+ const CLASS_MAP_FILE = path.join(config.classMaps, "client.json");
8
+ const OLD_CLASS_MAP_FILE = path.join(config.classMaps, "client_old.json");
9
+ const SELECTOR = /\.([\w-]+)/g;
7
10
 
8
11
  if ([CLASS_MAP_FILE, OLD_CLASS_MAP_FILE].some((e) => !fs.existsSync(e))) {
9
12
  console.log("Usage:");
@@ -16,6 +19,13 @@ if ([CLASS_MAP_FILE, OLD_CLASS_MAP_FILE].some((e) => !fs.existsSync(e))) {
16
19
  process.exit(1);
17
20
  }
18
21
 
22
+ const selectorReplacerPlugin = (opts) => (css) => {
23
+ css.walkRules((rule) => {
24
+ rule.selector = rule.selector.replace(opts.match, opts.replace);
25
+ });
26
+ };
27
+ selectorReplacerPlugin.postcss = true;
28
+
19
29
  const cwd = process.cwd();
20
30
  const oldClasses = JSON.parse(fs.readFileSync(OLD_CLASS_MAP_FILE));
21
31
  const newClasses = JSON.parse(fs.readFileSync(CLASS_MAP_FILE));
@@ -43,24 +53,22 @@ function findNewClassFromOld(oldName) {
43
53
  }
44
54
 
45
55
  export async function execute() {
46
- const parsedCss = fs
56
+ const files = fs
47
57
  .readdirSync(cwd, { recursive: true })
48
- .filter((e) => e.endsWith(".css"))
49
- .map((e) => [
50
- e,
51
- postcss([
52
- selectorReplacerPlugin({
53
- match: /\.([\w-]+)/g,
54
- replace: (_, s) => findNewClassFromOld(s),
55
- }),
56
- ]).process(readFile(e), {
57
- from: e,
58
+ .filter((e) => e.endsWith(".css"));
59
+ for (const file of files) {
60
+ postcss([
61
+ selectorReplacerPlugin({
62
+ match: SELECTOR,
63
+ replace: (_, s) => findNewClassFromOld(s),
58
64
  }),
59
- ]);
60
-
61
- for (const [file, result] of parsedCss) {
62
- fs.writeFileSync(file, (await result).css);
63
-
64
- console.log("[%s] done", file);
65
+ ])
66
+ .process(readFile(file), {
67
+ from: file,
68
+ })
69
+ .then(({ css }) => {
70
+ fs.writeFileSync(file, css);
71
+ console.log("[%s] done", file);
72
+ });
65
73
  }
66
74
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "steam-theming-utils",
3
- "version": "2.0.2",
3
+ "version": "3.1.0",
4
4
  "description": "A collection of scripts for easier Steam theming",
5
5
  "repository": {
6
6
  "type": "git",
@@ -12,12 +12,13 @@
12
12
  },
13
13
  "types": "./src/api.d.ts",
14
14
  "exports": {
15
- ".": "./src/api.js"
15
+ ".": "./src/api.js",
16
+ "./postcss-plugin": "./src/postcss_plugin.js"
16
17
  },
17
18
  "dependencies": {
18
- "@biomejs/biome": "^1.9.0",
19
19
  "chrome-remote-interface": "^0.33.2",
20
- "postcss": "^8.4.39",
21
- "postcss-load-config": "^6.0.1"
20
+ "lilconfig": "^3.1.2",
21
+ "postcss": "^8.5.1",
22
+ "prettier": "^3.4.1"
22
23
  }
23
24
  }
package/src/api.d.ts CHANGED
@@ -1,7 +1,23 @@
1
+ import type CDP from "chrome-remote-interface";
1
2
  import type Protocol from "devtools-protocol";
2
3
 
4
+ export interface Config {
5
+ /**
6
+ * Path of built class maps.
7
+ */
8
+ classMaps: string;
9
+
10
+ /**
11
+ * Directories for the postcss plugin to ignore.
12
+ *
13
+ * For example: `["client/shared", "web/vars"]` will ignore
14
+ * `src/client/shared` and `src/web/vars`, assuming the base dir is `src`.
15
+ */
16
+ ignore: string[];
17
+ }
18
+
3
19
  interface Script {
4
- execute(): Promise<void>;
20
+ execute(arg?: string): Promise<void>;
5
21
  }
6
22
 
7
23
  /**
@@ -9,10 +25,23 @@ interface Script {
9
25
  */
10
26
  type ScriptFile =
11
27
  | "build_class_modules"
12
- | "build_theme"
13
28
  | "make_readable_classes"
14
29
  | "replace_old_classes";
15
30
 
31
+ /**
32
+ * Pages that have an existing class map, excluding `client`.
33
+ */
34
+ export type Page =
35
+ | "accountpreferences"
36
+ | "apppage"
37
+ | "gameslist"
38
+ | "notificationspage"
39
+ | "profileedit"
40
+ | "shoppingcart";
41
+
42
+ export declare const connection: CDP.Client;
43
+ export declare const config: Config;
44
+
16
45
  /**
17
46
  * @param file The script to read.
18
47
  */
@@ -23,6 +52,7 @@ export function readScript(name: ScriptFile): Promise<Script>;
23
52
  */
24
53
  export function run(
25
54
  expression: string,
55
+ conn?: CDP.Client,
26
56
  ): Promise<Protocol.Runtime.EvaluateResponse>;
27
57
 
28
58
  /**
@@ -30,6 +60,7 @@ export function run(
30
60
  */
31
61
  export function runCdpFile(
32
62
  file: ScriptFile,
63
+ conn?: CDP.Client,
33
64
  ): Promise<Protocol.Runtime.EvaluateResponse>;
34
65
 
35
66
  /**
@@ -38,6 +69,9 @@ export function runCdpFile(
38
69
  *
39
70
  * @param expression JS to run.
40
71
  */
41
- export function runWithResult(expression: string): Promise<any>;
72
+ export function runWithResult(
73
+ expression: string,
74
+ conn?: CDP.Client,
75
+ ): Promise<any>;
42
76
 
43
77
  export function sleep(ms: number): Promise<void>;
package/src/api.js CHANGED
@@ -1,33 +1,39 @@
1
- import cdp from "chrome-remote-interface";
2
1
  import path from "node:path";
3
- import { CDP_FILES_PATH, SCRIPT_PATH } from "./constants.js";
4
- import { readFile } from "./shared.js";
2
+ import { lilconfig } from "lilconfig";
3
+ import { CDP_FILES_PATH, DEFAULT_CONFIG, SCRIPT_PATH } from "./constants.js";
4
+ import { createConnection, readFile } from "./shared.js";
5
5
 
6
- export const connection = await cdp({
7
- host: "127.0.0.1",
8
- port: 8080,
9
- target: (e) => e.find((e) => e.title === "SharedJSContext"),
10
- }).catch((e) => {
11
- console.log(
12
- "%s\n%s %o",
13
- e.message,
14
- "Try running Steam with",
15
- "-cef-enable-debugging",
16
- );
17
- process.exit(1);
18
- });
6
+ // postcss-cli hangs because of cdp
7
+ export const connection =
8
+ path.basename(process.argv[1]) !== "postcss" &&
9
+ (await createConnection((e) =>
10
+ e.find((e) => e.title === "SharedJSContext"),
11
+ ).catch((e) => {
12
+ console.log(
13
+ "%s\nTry running Steam with %o",
14
+ e.message,
15
+ "-cef-enable-debugging",
16
+ );
17
+ process.exit(1);
18
+ }));
19
19
 
20
- export const readScript = async (name) =>
21
- await import(`file://${path.join(SCRIPT_PATH, `${name}.js`)}`);
22
- export const run = async (expression) =>
23
- await connection.Runtime.evaluate({
20
+ export const config = Object.assign(
21
+ DEFAULT_CONFIG,
22
+ (await lilconfig("steam-theming-utils").search())?.config || {},
23
+ );
24
+
25
+ export const readScript = (name) =>
26
+ import(`file://${path.join(SCRIPT_PATH, `${name}.js`)}`);
27
+
28
+ export const run = (expression, conn = connection) =>
29
+ conn.Runtime.evaluate({
24
30
  expression,
25
31
  awaitPromise: true,
26
32
  returnByValue: true,
27
33
  });
28
- export const runCdpFile = async (file) =>
29
- await runWithResult(readFile(path.join(CDP_FILES_PATH, file)));
30
- export const runWithResult = async (expression) =>
31
- (await run(expression)).result.value;
34
+ export const runCdpFile = (file, conn = connection) =>
35
+ runWithResult(readFile(path.join(CDP_FILES_PATH, file)), conn);
36
+ export const runWithResult = async (expression, conn = connection) =>
37
+ (await run(expression, conn)).result.value;
32
38
 
33
39
  export const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
package/src/constants.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import path from "node:path";
2
- import { fileURLToPath } from "node:url";
3
-
4
- const packagePath = path
5
- .dirname(fileURLToPath(import.meta.url))
6
- .split(path.sep)
7
- .slice(0, -1)
8
- .join(path.sep);
2
+ import { packagePath } from "./shared.js";
9
3
 
10
4
  export const CDP_FILES_PATH = path.join(packagePath, "cdp");
11
- export const CLASS_MAP_FILE = "class_map.json";
12
5
  export const SCRIPT_PATH = path.join(packagePath, "lib");
6
+
7
+ /** @type {import("./api").Config} */
8
+ export const DEFAULT_CONFIG = {
9
+ classMaps: "class_maps",
10
+ ignore: [],
11
+ };
12
+
13
+ export const STORE_BASE_URL = "https://store.steampowered.com";
package/src/index.js CHANGED
@@ -13,15 +13,6 @@ if (!files.some((e) => process.argv[2] === e)) {
13
13
  process.exit(2);
14
14
  }
15
15
 
16
- await connection.Runtime.enable();
17
- connection.Runtime.on("consoleAPICalled", (ev) => {
18
- if (ev.type !== "error") {
19
- return;
20
- }
21
-
22
- console.error(...ev.args.map((e) => e.description || e.value));
23
- });
24
-
25
16
  const script = await readScript(process.argv[2]);
26
- await script.execute();
17
+ await script.execute(process.argv[3]);
27
18
  connection.close();
@@ -0,0 +1,78 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ // Not a dependency because postcss already depends on it.
4
+ import yargs from "yargs";
5
+ import { config } from "./api.js";
6
+
7
+ const PAGES = [
8
+ "accountpreferences",
9
+ "apppage",
10
+ "client",
11
+ "gameslist",
12
+ "notificationspage",
13
+ "profileedit",
14
+ "shoppingcart",
15
+ ];
16
+ const SELECTOR = /#(\w+)/g;
17
+
18
+ const { argv } = yargs(process.argv);
19
+ const cwd = process.cwd();
20
+ const classMap = {};
21
+
22
+ /**
23
+ * Gets a class map on demand rather than reading all files at once.
24
+ *
25
+ * @param {string} page
26
+ */
27
+ function getClassMap(page) {
28
+ if (classMap[page]) {
29
+ return classMap[page];
30
+ }
31
+
32
+ const pagePath = path.join(config.classMaps, `${page}.json`);
33
+ if (!fs.existsSync(pagePath)) {
34
+ return;
35
+ }
36
+
37
+ classMap[page] = JSON.parse(fs.readFileSync(pagePath));
38
+ return classMap[page];
39
+ }
40
+
41
+ export const selectorReplacerPlugin = () => (css) => {
42
+ const { file } = css.source.input;
43
+ const name = path.basename(file, ".scss");
44
+
45
+ const splitPath = file.split(path.sep);
46
+ const page = PAGES.find((e) => splitPath.includes(e));
47
+ if (!page) {
48
+ return;
49
+ }
50
+
51
+ const map = getClassMap(page);
52
+ if (!map) {
53
+ console.error("[%s] no such map", page);
54
+ return;
55
+ }
56
+
57
+ const mod = map?.[name];
58
+ const ignoredPaths = config.ignore || [];
59
+ const src = path.join(cwd, argv.base);
60
+ const skipFile = ignoredPaths.some((e) => file.startsWith(path.join(src, e)));
61
+ if (!mod && !skipFile) {
62
+ console.error("[%s] no such module", name);
63
+ return;
64
+ }
65
+
66
+ css.walkRules((rule) => {
67
+ rule.selector = rule.selector.replace(SELECTOR, (_, s) => {
68
+ const id = mod[s];
69
+ if (!id) {
70
+ console.error("[%s] %o is undefined", name, `#${s}`);
71
+ return;
72
+ }
73
+
74
+ return `.${id}`;
75
+ });
76
+ });
77
+ };
78
+ selectorReplacerPlugin.postcss = true;
package/src/shared.js CHANGED
@@ -1,10 +1,98 @@
1
1
  import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import cdp from "chrome-remote-interface";
5
+ import { runWithResult } from "./api.js";
6
+ import { STORE_BASE_URL } from "./constants.js";
7
+
8
+ export const packagePath = path
9
+ .dirname(fileURLToPath(import.meta.url))
10
+ .split(path.sep)
11
+ .slice(0, -1)
12
+ .join(path.sep);
2
13
 
3
14
  export const readFile = (file) => fs.readFileSync(file).toString();
4
15
 
5
- export const selectorReplacerPlugin = (opts) => (css) => {
6
- css.walkRules((rule) => {
7
- rule.selector = rule.selector.replace(opts.match, opts.replace);
16
+ /**
17
+ * @typedef {object} SteamPage
18
+ *
19
+ * @property {string} url
20
+ * The URL to open if not open already.
21
+ * @property {RegExp} match
22
+ * URL regex to match if there is an open page.
23
+ */
24
+
25
+ /**
26
+ * Gets a page URL for a given page name.
27
+ *
28
+ * @param {import("./api").Page} page
29
+ * @returns {Promise<SteamPage>}
30
+ */
31
+ export async function getPageUrl(page) {
32
+ const resolve = (name) => runWithResult(`urlStore.ResolveURL("${name}")`);
33
+ /** @returns {SteamPage} */
34
+ const pageObj = (url) => ({
35
+ url,
36
+ match: new RegExp(`^${url.replace(/\/+$/, "")}`),
37
+ });
38
+
39
+ const profileUrl = await resolve("SteamIDMyProfile");
40
+ switch (page) {
41
+ case "accountpreferences":
42
+ return {
43
+ url: await resolve("FamilyManagement"),
44
+ match: new RegExp(`^${STORE_BASE_URL}/account`),
45
+ };
46
+ case "apppage":
47
+ return {
48
+ url: `${STORE_BASE_URL}/app/666220`,
49
+ match: new RegExp(`^${STORE_BASE_URL}/app/\\d+`),
50
+ };
51
+ case "gameslist":
52
+ return pageObj(`${profileUrl}games`);
53
+ case "notificationspage":
54
+ return pageObj(`${profileUrl}notifications`);
55
+ case "profileedit":
56
+ return pageObj(await resolve("SteamIDEditPage"));
57
+ case "shoppingcart":
58
+ return pageObj(await resolve("StoreCart"));
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Creates a CDP connection for a given target.
64
+ *
65
+ * @param {(targets: cdp.Target[]) => cdp.Target} target
66
+ */
67
+ export async function createConnection(target) {
68
+ const connection = await cdp({
69
+ host: "127.0.0.1",
70
+ port: 8080,
71
+ target,
8
72
  });
9
- };
10
- selectorReplacerPlugin.postcss = true;
73
+
74
+ await connection.Runtime.enable();
75
+ connection.Runtime.on("consoleAPICalled", (ev) => {
76
+ if (ev.type !== "error") {
77
+ return;
78
+ }
79
+
80
+ console.error(...ev.args.map((e) => e.description || e.value));
81
+ });
82
+
83
+ return connection;
84
+ }
85
+
86
+ /**
87
+ * Creates a CDP connection for a given page name.
88
+ *
89
+ * @param {import("./api").Page} page
90
+ */
91
+ export async function createWebConnection(page) {
92
+ const { match } = await getPageUrl(page);
93
+ const connection = await createConnection((e) =>
94
+ e.find((e) => e.url.match(match)),
95
+ );
96
+
97
+ return connection;
98
+ }
@@ -1,69 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import postcss from "postcss";
4
- import postcssrc from "postcss-load-config";
5
- import { readScript } from "../src/api.js";
6
- import { CLASS_MAP_FILE } from "../src/constants.js";
7
- import { readFile, selectorReplacerPlugin } from "../src/shared.js";
8
-
9
- const cwd = process.cwd();
10
- const DIST_DIR = path.join(cwd, "dist");
11
- const EXTENSION = ".css";
12
- const SRC_DIR = path.join(cwd, "src");
13
-
14
- export async function execute() {
15
- if (!fs.existsSync(path.join(cwd, CLASS_MAP_FILE))) {
16
- const script = await readScript("build_class_modules");
17
- await script.execute();
18
- }
19
-
20
- const classes = JSON.parse(fs.readFileSync(CLASS_MAP_FILE));
21
- // TODO:
22
- // Can't use SASS here - screams at me with "Please check the validity
23
- // of the block starting from line #1" with a completely valid SCSS syntax.
24
- const { plugins, options } = await postcssrc();
25
- const parsedCss = fs
26
- .readdirSync(SRC_DIR, { recursive: true })
27
- .filter((e) => e.endsWith(EXTENSION))
28
- .map((e) => [e, path.basename(e, EXTENSION)])
29
- .map(([e, name]) => [
30
- e,
31
- postcss([
32
- selectorReplacerPlugin({
33
- match: /#(\w+)/g,
34
- replace: (_, s) => {
35
- const mod = classes[name];
36
- if (!mod) {
37
- console.log("[%s] no such module", name);
38
- return;
39
- }
40
-
41
- const id = mod[s];
42
- if (!id) {
43
- console.log("[%s] %o is undefined", name, `#${s}`);
44
- return;
45
- }
46
-
47
- return `.${id}`;
48
- },
49
- }),
50
- ...plugins,
51
- ]).process(readFile(path.join(SRC_DIR, e)), {
52
- ...options,
53
- from: path.join(SRC_DIR, e),
54
- to: path.join(DIST_DIR, e),
55
- }),
56
- ]);
57
-
58
- fs.rmSync(DIST_DIR, { force: true, recursive: true });
59
- for (const [file, result] of parsedCss) {
60
- const { css, map } = await result;
61
- const distFile = path.join(DIST_DIR, file);
62
-
63
- fs.mkdirSync(path.dirname(distFile), { recursive: true });
64
- fs.writeFileSync(distFile, css);
65
- if (map) {
66
- fs.writeFileSync(`${distFile}.map`, map.toString());
67
- }
68
- }
69
- }