Skip to content

Commit 4b2f460

Browse files
committed
Add community session file helpers and group membership state
1 parent 3e28af6 commit 4b2f460

4 files changed

Lines changed: 237 additions & 0 deletions

File tree

examples/smoke_test.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import argparse
44
import sys
5+
import tempfile
56
import time
67
from pathlib import Path
78

@@ -860,6 +861,10 @@ def run_community_suite(client: SteamClient, args) -> None:
860861
"Get Group Member Summaries Map",
861862
lambda: _format_group_member_summaries_map(client.get_group_member_summaries_map(args.group_url, limit=5)),
862863
)
864+
run_check(
865+
"Get Group Membership State",
866+
lambda: _format_group_membership_state(client.get_group_membership_state(args.group_url)),
867+
)
863868
if client.api_key:
864869
run_check(
865870
"Get Friend Bans",
@@ -875,6 +880,10 @@ def run_community_suite(client: SteamClient, args) -> None:
875880
"Community Cookie Mapping Roundtrip",
876881
lambda: _format_cookie_mapping_roundtrip(client, args),
877882
)
883+
run_check(
884+
"Community Cookie File Roundtrip",
885+
lambda: _format_cookie_file_roundtrip(client, args),
886+
)
878887
run_check(
879888
"Community Session Bundle Roundtrip",
880889
lambda: _format_bundle_roundtrip(client, args),
@@ -883,6 +892,10 @@ def run_community_suite(client: SteamClient, args) -> None:
883892
"Community Session Bundle JSON Roundtrip",
884893
lambda: _format_bundle_json_roundtrip(client, args),
885894
)
895+
run_check(
896+
"Community Session Bundle File Roundtrip",
897+
lambda: _format_bundle_file_roundtrip(client, args),
898+
)
886899
run_check(
887900
"Export Community Refresh Token",
888901
lambda: _format_refresh_token_export(client),
@@ -1249,6 +1262,15 @@ def _format_group_member_summaries_map(payload: dict) -> str:
12491262
)
12501263

12511264

1265+
def _format_group_membership_state(payload: dict) -> str:
1266+
return "state={0} can_join={1} can_leave={2} group_id={3}".format(
1267+
payload.get("membership_state"),
1268+
payload.get("can_join"),
1269+
payload.get("can_leave"),
1270+
payload.get("group_id", "<none>"),
1271+
)
1272+
1273+
12521274
def _format_group_details(payload: dict) -> str:
12531275
return "group={0} members={1} online={2}".format(
12541276
_safe_console_text(payload.get("group_name") or "<none>"),
@@ -1636,6 +1658,23 @@ def _format_cookie_mapping_roundtrip(client: SteamClient, args) -> str:
16361658
roundtrip_client.close()
16371659

16381660

1661+
def _format_cookie_file_roundtrip(client: SteamClient, args) -> str:
1662+
with tempfile.TemporaryDirectory(prefix="sckit-cookie-") as temp_dir:
1663+
cookie_path = Path(temp_dir) / "community_cookie.txt"
1664+
client.save_community_cookie_string(cookie_path)
1665+
roundtrip_client = build_client(args)
1666+
try:
1667+
roundtrip_client.load_community_cookie_string(cookie_path)
1668+
state = roundtrip_client.get_community_session_state()
1669+
return "steamid={0} logged_in={1} path={2}".format(
1670+
state.get("steam_id", "<unknown>"),
1671+
state.get("logged_in", False),
1672+
cookie_path.name,
1673+
)
1674+
finally:
1675+
roundtrip_client.close()
1676+
1677+
16391678
def _format_bundle_roundtrip(client: SteamClient, args) -> str:
16401679
bundle = client.export_community_session_bundle()
16411680
roundtrip_client = build_client(args)
@@ -1651,6 +1690,23 @@ def _format_bundle_roundtrip(client: SteamClient, args) -> str:
16511690
roundtrip_client.close()
16521691

16531692

1693+
def _format_bundle_file_roundtrip(client: SteamClient, args) -> str:
1694+
with tempfile.TemporaryDirectory(prefix="sckit-bundle-") as temp_dir:
1695+
bundle_path = Path(temp_dir) / "community_bundle.json"
1696+
client.save_community_session_bundle(bundle_path)
1697+
roundtrip_client = build_client(args)
1698+
try:
1699+
roundtrip_client.load_community_session_bundle(bundle_path)
1700+
state = roundtrip_client.get_community_session_state()
1701+
return "steamid={0} logged_in={1} path={2}".format(
1702+
state.get("steam_id", "<unknown>"),
1703+
state.get("logged_in", False),
1704+
bundle_path.name,
1705+
)
1706+
finally:
1707+
roundtrip_client.close()
1708+
1709+
16541710
def _format_bundle_json_roundtrip(client: SteamClient, args) -> str:
16551711
bundle_json = client.export_community_session_bundle_json()
16561712
roundtrip_client = build_client(args)

src/steamcommunitykit/client.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@ def get_group_id64(self, group_url: str) -> str:
238238
def get_group_id(self, group_url: str) -> str:
239239
return self.groups.fetch_group_id(group_url)
240240

241+
def get_group_membership_state(self, group_url: str) -> dict:
242+
return self.groups.get_group_membership_state(group_url)
243+
241244
def check_group_name_availability(self, name: str):
242245
return self.groups.check_name_availability(name)
243246

@@ -1782,6 +1785,10 @@ def set_community_credentials_from_cookie_mapping(self, cookies: dict) -> Commun
17821785
self.set_community_credentials(credentials)
17831786
return credentials
17841787

1788+
def load_community_cookie_string(self, path: Union[str, Path]) -> CommunityCredentials:
1789+
cookie_string = Path(path).read_text(encoding="utf-8")
1790+
return self.set_community_credentials_from_cookie_string(cookie_string)
1791+
17851792
def set_community_credentials_from_bundle(self, bundle: dict) -> CommunityCredentials:
17861793
credentials = self.auth.community_credentials_from_bundle(bundle)
17871794
self.set_community_credentials(credentials)
@@ -1792,6 +1799,10 @@ def set_community_credentials_from_bundle_json(self, bundle_json: str) -> Commun
17921799
self.set_community_credentials(credentials)
17931800
return credentials
17941801

1802+
def load_community_session_bundle(self, path: Union[str, Path]) -> CommunityCredentials:
1803+
bundle_json = Path(path).read_text(encoding="utf-8")
1804+
return self.set_community_credentials_from_bundle_json(bundle_json)
1805+
17951806
def login_to_community_with_refresh_token(self, refresh_token: str) -> CommunityCredentials:
17961807
credentials = self.auth.community_credentials_from_refresh_token(refresh_token)
17971808
self.set_community_credentials(credentials)
@@ -1803,6 +1814,12 @@ def export_community_cookie_string(self) -> str:
18031814
def export_community_cookie_mapping(self) -> dict:
18041815
return self.auth.export_cookie_mapping(self._transport.require_community_credentials())
18051816

1817+
def save_community_cookie_string(self, path: Union[str, Path]) -> Path:
1818+
resolved_path = Path(path)
1819+
resolved_path.parent.mkdir(parents=True, exist_ok=True)
1820+
resolved_path.write_text(self.export_community_cookie_string(), encoding="utf-8")
1821+
return resolved_path
1822+
18061823
def export_community_session_bundle(self) -> dict:
18071824
return self.auth.export_credentials_bundle(self._transport.require_community_credentials())
18081825

@@ -1812,6 +1829,15 @@ def export_community_session_bundle_json(self, *, indent: Optional[int] = None)
18121829
indent=indent,
18131830
)
18141831

1832+
def save_community_session_bundle(self, path: Union[str, Path], *, indent: Optional[int] = 2) -> Path:
1833+
resolved_path = Path(path)
1834+
resolved_path.parent.mkdir(parents=True, exist_ok=True)
1835+
resolved_path.write_text(
1836+
self.export_community_session_bundle_json(indent=indent),
1837+
encoding="utf-8",
1838+
)
1839+
return resolved_path
1840+
18151841
def export_community_refresh_token(self) -> str:
18161842
credentials = self._transport.require_community_credentials()
18171843
if not credentials.refresh_token:

src/steamcommunitykit/services/groups.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,62 @@ def fetch_group_id(self, group_url: str) -> str:
143143
group_id = tail.split("<div class=\"formRowFields\">", 1)[1].split("</div>", 1)[0]
144144
return group_id.strip()
145145

146+
def get_group_membership_state(self, group_url: str) -> dict:
147+
normalized_group_url = self._normalize_group_url_value(group_url)
148+
response_text = self.transport.request(
149+
"GET",
150+
f"{COMMUNITY_BASE_URL}/groups/{normalized_group_url}",
151+
cookies=self._community_cookies(),
152+
expected="text",
153+
)
154+
state = self._parse_group_membership_state(response_text)
155+
state["group_url"] = normalized_group_url
156+
state["group_link"] = f"{COMMUNITY_BASE_URL}/groups/{normalized_group_url}/"
157+
return state
158+
159+
@staticmethod
160+
def _parse_group_membership_state(html_text: str) -> dict:
161+
join_form_match = re.search(
162+
r'<form name="join_group_form" id="join_group_form" method="POST" action="([^"]+)">.*?'
163+
r'name="action" value="join".*?name="sessionID" value="([^"]+)"',
164+
html_text,
165+
re.S | re.I,
166+
)
167+
leave_form_match = re.search(
168+
r'<form method="POST" id="leave_group_form" action="([^"]+)">.*?'
169+
r'name="sessionID" value="([^"]+)".*?name="action" value="leaveGroup".*?'
170+
r'name="groupId" value="([^"]+)"',
171+
html_text,
172+
re.S | re.I,
173+
)
174+
can_join = bool(re.search(r"<span>\s*Join Group\s*</span>", html_text, re.I))
175+
can_leave = leave_form_match is not None
176+
if can_leave and not can_join:
177+
membership_state = "member"
178+
elif can_join:
179+
membership_state = "not_member"
180+
else:
181+
membership_state = "unknown"
182+
183+
payload = {
184+
"membership_state": membership_state,
185+
"is_member": membership_state == "member",
186+
"can_join": can_join,
187+
"can_leave": can_leave,
188+
"join_action_url": None,
189+
"leave_action_url": None,
190+
"session_id": None,
191+
"group_id": None,
192+
}
193+
if join_form_match:
194+
payload["join_action_url"] = html.unescape(join_form_match.group(1))
195+
payload["session_id"] = join_form_match.group(2)
196+
if leave_form_match:
197+
payload["leave_action_url"] = html.unescape(leave_form_match.group(1))
198+
payload["session_id"] = leave_form_match.group(2)
199+
payload["group_id"] = leave_form_match.group(3)
200+
return payload
201+
146202
def get_group_details(self, group_url: str, page: int = 1) -> dict:
147203
normalized_group_url = self._normalize_group_url_value(group_url)
148204
response_text = self.transport.request(

tests/test_client.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1766,6 +1766,27 @@ def test_client_can_export_community_cookie_mapping() -> None:
17661766
client.close()
17671767

17681768

1769+
def test_client_can_save_and_load_community_cookie_file(tmp_path) -> None:
1770+
client = SteamClient(api_key="test")
1771+
client.set_community_credentials(
1772+
CommunityCredentials(
1773+
steam_id="76561197960435530",
1774+
session_id="session123",
1775+
steam_login_secure="76561197960435530%7C%7Ctoken123",
1776+
)
1777+
)
1778+
1779+
path = client.save_community_cookie_string(tmp_path / "session" / "community_cookie.txt")
1780+
1781+
restored_client = SteamClient(api_key="test")
1782+
restored = restored_client.load_community_cookie_string(path)
1783+
1784+
assert path.read_text(encoding="utf-8") == "sessionid=session123; steamLoginSecure=76561197960435530%7C%7Ctoken123"
1785+
assert restored.steam_id == "76561197960435530"
1786+
client.close()
1787+
restored_client.close()
1788+
1789+
17691790
def test_client_can_export_and_restore_community_session_bundle() -> None:
17701791
client = SteamClient(api_key="test")
17711792
client.set_community_credentials(
@@ -1791,6 +1812,29 @@ def test_client_can_export_and_restore_community_session_bundle() -> None:
17911812
restored_client.close()
17921813

17931814

1815+
def test_client_can_save_and_load_community_session_bundle_file(tmp_path) -> None:
1816+
client = SteamClient(api_key="test")
1817+
client.set_community_credentials(
1818+
CommunityCredentials(
1819+
steam_id="76561197960435530",
1820+
session_id="session123",
1821+
access_token="access123",
1822+
refresh_token="refresh123",
1823+
steam_login_secure="76561197960435530%7C%7Ctoken123",
1824+
)
1825+
)
1826+
1827+
path = client.save_community_session_bundle(tmp_path / "session" / "community_bundle.json")
1828+
1829+
restored_client = SteamClient(api_key="test")
1830+
restored = restored_client.load_community_session_bundle(path)
1831+
1832+
assert '"steam_id": "76561197960435530"' in path.read_text(encoding="utf-8")
1833+
assert restored.refresh_token == "refresh123"
1834+
client.close()
1835+
restored_client.close()
1836+
1837+
17941838
def test_client_can_export_and_restore_community_session_bundle_json() -> None:
17951839
client = SteamClient(api_key="test")
17961840
client.set_community_credentials(
@@ -1977,6 +2021,61 @@ def test_players_get_recently_played_games_summary_normalizes_games() -> None:
19772021
client.close()
19782022

19792023

2024+
def test_groups_membership_state_parses_join_form() -> None:
2025+
html = """
2026+
<form name="join_group_form" id="join_group_form" method="POST" action="https://steamcommunity.com/groups/SteamDB">
2027+
<input type="hidden" name="action" value="join">
2028+
<input type="hidden" name="sessionID" value="session123">
2029+
</form>
2030+
<div class="grouppage_join_area">
2031+
<span>Join Group</span>
2032+
</div>
2033+
"""
2034+
client = SteamClient(session=RecordingSession(DummyResponse(text=html)))
2035+
client.set_community_credentials(
2036+
CommunityCredentials(
2037+
steam_id="76561197960435530",
2038+
session_id="session123",
2039+
steam_login_secure="76561197960435530%7C%7Ctoken123",
2040+
)
2041+
)
2042+
2043+
result = client.get_group_membership_state("SteamDB")
2044+
2045+
assert result["membership_state"] == "not_member"
2046+
assert result["can_join"] is True
2047+
assert result["can_leave"] is False
2048+
assert result["join_action_url"] == "https://steamcommunity.com/groups/SteamDB"
2049+
client.close()
2050+
2051+
2052+
def test_groups_membership_state_parses_leave_form() -> None:
2053+
html = """
2054+
<form method="POST" id="leave_group_form" action="https://steamcommunity.com/profiles/76561197960435530/home_process">
2055+
<input type="hidden" name="sessionID" value="session123">
2056+
<input type="hidden" name="action" value="leaveGroup">
2057+
<input type="hidden" name="groupId" value="103582791429521412">
2058+
</form>
2059+
"""
2060+
client = SteamClient(session=RecordingSession(DummyResponse(text=html)))
2061+
client.set_community_credentials(
2062+
CommunityCredentials(
2063+
steam_id="76561197960435530",
2064+
session_id="session123",
2065+
steam_login_secure="76561197960435530%7C%7Ctoken123",
2066+
)
2067+
)
2068+
2069+
result = client.get_group_membership_state("Valve")
2070+
2071+
assert result["membership_state"] == "member"
2072+
assert result["is_member"] is True
2073+
assert result["can_join"] is False
2074+
assert result["can_leave"] is True
2075+
assert result["group_id"] == "103582791429521412"
2076+
client.close()
2077+
2078+
19802079
def test_players_find_owned_game_filters_by_app_id() -> None:
19812080
session = RecordingSession(
19822081
DummyResponse(

0 commit comments

Comments
 (0)