44 "context"
55 "fmt"
66 "net/url"
7+ "strconv"
78
89 sdk "github.com/GoCodeAlone/workflow/plugin/external/sdk"
910)
@@ -70,6 +71,7 @@ func (s *steamPresenceSetStep) Execute(_ context.Context, _ map[string]any,
7071// apiKey string (required)
7172// steamId string (required)
7273// relationship string — "friend" (default), "all"
74+ // appId string — optional; when set, only friends who own this app are returned
7375// baseUrl string — override Steam API base URL (for testing)
7476//
7577// Outputs: friends ([]map), count (int)
@@ -97,6 +99,7 @@ func (s *steamFriendsListStep) Execute(_ context.Context, _ map[string]any,
9799 if relationship == "" {
98100 relationship = "friend"
99101 }
102+ appId , _ := merged ["appId" ].(string )
100103 baseURL , _ := merged ["baseUrl" ].(string )
101104
102105 client := newSteamClient (baseURL )
@@ -120,12 +123,60 @@ func (s *steamFriendsListStep) Execute(_ context.Context, _ map[string]any,
120123 }
121124 }
122125
126+ // If appId is provided, filter friends to those who own the specified app.
127+ if appId != "" {
128+ friends = filterFriendsByOwnership (client , apiKey , appId , friends )
129+ }
130+
123131 return & sdk.StepResult {Output : map [string ]any {
124132 "friends" : friends ,
125133 "count" : len (friends ),
126134 }}, nil
127135}
128136
137+ // filterFriendsByOwnership returns only the friends who own the given appId.
138+ // Calls IPlayerService/GetOwnedGames/v1/ per friend with appids_filter to minimise
139+ // the data returned; friends with a non-empty game list own the app.
140+ func filterFriendsByOwnership (client * steamClient , apiKey , appId string , friends []any ) []any {
141+ appIdInt , err := strconv .Atoi (appId )
142+ if err != nil {
143+ // Non-numeric appId — cannot filter, return all.
144+ return friends
145+ }
146+
147+ filtered := make ([]any , 0 , len (friends ))
148+ for _ , f := range friends {
149+ fm , ok := f .(map [string ]any )
150+ if ! ok {
151+ continue
152+ }
153+ fid , _ := fm ["steamid" ].(string )
154+ if fid == "" {
155+ continue
156+ }
157+ params := url.Values {
158+ "key" : {apiKey },
159+ "steamid" : {fid },
160+ "appids_filter[0]" : {strconv .Itoa (appIdInt )},
161+ "include_appinfo" : {"0" },
162+ }
163+ resp , err := client .get ("/IPlayerService/GetOwnedGames/v1/" , params )
164+ if err != nil {
165+ // On error (e.g. private profile), skip this friend.
166+ continue
167+ }
168+ response , _ := resp ["response" ].(map [string ]any )
169+ if response == nil {
170+ continue
171+ }
172+ games , _ := response ["games" ].([]any )
173+ if len (games ) > 0 {
174+ filtered = append (filtered , f )
175+ }
176+ }
177+ return filtered
178+ }
179+
129180// step.steam_invite_send — generates a Steam join-game invite for a lobby.
130181//
131182// Steam Web API does not expose a server-side invite endpoint; this step
@@ -134,6 +185,7 @@ func (s *steamFriendsListStep) Execute(_ context.Context, _ map[string]any,
134185//
135186// Inputs (current or config):
136187//
188+ // appId string (required) — Steam application ID
137189// steamId string (required) — inviting player's Steam ID
138190// lobbyId string (required) — Steam lobby ID to join
139191// message string — optional custom message
@@ -151,6 +203,10 @@ func (s *steamInviteSendStep) Execute(_ context.Context, _ map[string]any,
151203
152204 merged := mergeConfigs (current , config )
153205
206+ appId , _ := merged ["appId" ].(string )
207+ if appId == "" {
208+ return nil , fmt .Errorf ("step %s: appId is required" , s .name )
209+ }
154210 steamId , _ := merged ["steamId" ].(string )
155211 if steamId == "" {
156212 return nil , fmt .Errorf ("step %s: steamId is required" , s .name )
@@ -161,12 +217,8 @@ func (s *steamInviteSendStep) Execute(_ context.Context, _ map[string]any,
161217 }
162218 message , _ := merged ["message" ].(string )
163219
164- // steam:// invite URL format for joining a Steam lobby.
165- inviteURL := fmt .Sprintf ("steam://joinlobby/%s/%s/%s" ,
166- merged ["appId" ], lobbyId , steamId )
167- if appId , _ := merged ["appId" ].(string ); appId == "" {
168- inviteURL = fmt .Sprintf ("steam://joinlobby/%s/%s" , lobbyId , steamId )
169- }
220+ // steam:// invite URL format: steam://joinlobby/{appId}/{lobbyId}/{steamId}
221+ inviteURL := fmt .Sprintf ("steam://joinlobby/%s/%s/%s" , appId , lobbyId , steamId )
170222
171223 return & sdk.StepResult {Output : map [string ]any {
172224 "inviteUrl" : inviteURL ,
0 commit comments