summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md16
-rw-r--r--poetry.lock6
-rw-r--r--pyproject.toml2
-rw-r--r--steamrelationships/__init__.py2
-rw-r--r--steamrelationships/sr.py73
5 files changed, 79 insertions, 20 deletions
diff --git a/README.md b/README.md
index 3a8ba06..9cb5619 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,18 @@ Steam Relationships querries the Steam WebAPI to get public friends lists, and t
8 8
9## REQUIREMENTS 9## REQUIREMENTS
10 10
111. A [Steam DevAPI key](https://steamcommunity.com/dev/apikey) 111. A [Steam Developer API key](https://steamcommunity.com/dev/apikey)
122. [Poetry](https://python-poetry.org/)
12 13
13## USAGE 14## INSTALLATION
15
161. Clone the repo
172. [Install poetry](https://python-poetry.org/docs/#installing-with-the-official-installer) (if poetry isn't already installed)
183. Open a `poetry shell`
194. Run `poetry install`
20
21```bash
22git clone https://git.dabikers.online/SteamRelationships && cd SteamRelationships
23
24poetry shell && poetry install
25```
diff --git a/poetry.lock b/poetry.lock
index 161e2c5..695827a 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -112,13 +112,13 @@ files = [
112 112
113[[package]] 113[[package]]
114name = "idna" 114name = "idna"
115version = "3.6" 115version = "3.7"
116description = "Internationalized Domain Names in Applications (IDNA)" 116description = "Internationalized Domain Names in Applications (IDNA)"
117optional = false 117optional = false
118python-versions = ">=3.5" 118python-versions = ">=3.5"
119files = [ 119files = [
120 {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, 120 {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
121 {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, 121 {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
122] 122]
123 123
124[[package]] 124[[package]]
diff --git a/pyproject.toml b/pyproject.toml
index 42672e0..d608faa 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ version = "0.1.0"
4description = "A tool to discover friend relationships between steam profiles" 4description = "A tool to discover friend relationships between steam profiles"
5license = "LGPL-3.0-or-later" 5license = "LGPL-3.0-or-later"
6authors = [ 6authors = [
7 "NW/RL <nwrl@dabikers.online>", 7 "NWRL <nwrl@dabikers.online>",
8] 8]
9readme = "README.md" 9readme = "README.md"
10 10
diff --git a/steamrelationships/__init__.py b/steamrelationships/__init__.py
index 8c1715a..7862897 100644
--- a/steamrelationships/__init__.py
+++ b/steamrelationships/__init__.py
@@ -1,3 +1,5 @@
1__all__ = ["sr", "constants"]
2
1from steamrelationships.sr import SteamRelationships 3from steamrelationships.sr import SteamRelationships
2from steamrelationships.constants import _Const 4from steamrelationships.constants import _Const
3CONST = _Const() \ No newline at end of file 5CONST = _Const() \ No newline at end of file
diff --git a/steamrelationships/sr.py b/steamrelationships/sr.py
index 1cf9c2e..28b6b16 100644
--- a/steamrelationships/sr.py
+++ b/steamrelationships/sr.py
@@ -7,12 +7,26 @@ from steamrelationships.constants import _Const
7CONST = _Const() 7CONST = _Const()
8 8
9class SteamRelationships: 9class SteamRelationships:
10 """A class that handles the querring of Steam's web api to request a user's friends list"""
11
10 session: object = requests.Session() # Session object so repeated querries to steam's api use the same TCP connection 12 session: object = requests.Session() # Session object so repeated querries to steam's api use the same TCP connection
11 scanlist: dict = {} # To be populated by recursivescan() 13 scanlist: dict = {} # To be populated by scan functions
12 14
13 def __init__(self, webapikey: str, timeout: int = 30, timeout_retries: int = 5, reqdelay: float = 0.5, delayrand: int = 25, reqsafetybuffer: float = 0.9, reqjson: str = "requests.json") -> None: 15 def __init__(self, webapikey: str, timeout: int = 30, timeout_retries: int = 5, reqdelay: float = 0.5, delayrand: int = 25, reqsafetybuffer: float = 0.9, reqjson: str = "requests.json") -> None:
14 """ 16 """
15 """ # I can't make this a formatted string :sob: 17 (str) webapikey - A Steam "web api key" for grabbing friends lists (get one at https://steamcommunity.com/dev/apikey)
18
19 (int) timeout (Default: 30) - The time in seconds to wait for a response from Steam before timing out
20 (int) timeout_retries (Default: 5) - The number of times to retry a scan after a timeout
21 (float) reqdelay (Default: 0.5) - The base amount of seconds to wait after each scan (to lessen the load on Steam's servers)
22 (int) delayrand (Default: 25) - The maximum percent up/down to add/subtract to the request delay
23 (float) reqsafetybuffer (Default: 0.9) - A multiplier applied to Steam's max API request limit of 100,000. At 0.9, the new limit is 90,000
24 (str) reqjson (Default: "requests.json") - The path to a .json file that stores request numbers
25
26 If a value is entered and is of the incorrect type / can't be automatically converted, SteamRelationships will raise a type error
27 All numerical values should be positive, and will be made positive automatically if otherwise
28 delayrand will be truncated to between [0, 99] (inclusive) if over 100
29 """
16 30
17 # This used to be in a try-except block, but I decided that it should error out on a proper error 31 # This used to be in a try-except block, but I decided that it should error out on a proper error
18 self.webapikey: str = str(webapikey) 32 self.webapikey: str = str(webapikey)
@@ -23,8 +37,18 @@ class SteamRelationships:
23 self.reqsafetybuffer: float = float(abs(reqsafetybuffer)) 37 self.reqsafetybuffer: float = float(abs(reqsafetybuffer))
24 self.reqjson: str = str(reqjson) 38 self.reqjson: str = str(reqjson)
25 39
40 def __str__(self) -> str:
41 return f"Vals:\n\twebapikey: {len(self.webapikey) > 0} (Actual value ommitted for security)\n\n\tTimeout: {self.timeout}\n\tTimeout Retries: {self.timeout_retries}\n\tRequest Delay: {self.reqdelay}\n\tRequest Delay Randomness Factor: +/- {self.delayrand}%\n\tRequest Safety Buffer: {self.reqsafetybuffer} ({self.reqsafetybuffer * CONST.STEAMAPI_MAXREQ} out of {CONST.STEAMAPI_MAXREQ} max requests)\n\tRequests Log Filepath: \"{self.reqjson}\"\n\n\tMost Recent Scan: {self.scanlist}"
26 42
27 def _readjsonfile(self, filepath: str = "") -> dict: 43 def _readjsonfile(self, filepath: str = "") -> dict:
44 """
45 _readjsonfile(self, filepath: str = "") -> dict: Read the specified json file for previous requests
46 filepath: Path to json file
47
48 dict (return): The contents of the json file. Empty on error
49
50 _readjsonfile will create an "empty" json file if the specified file at the filepath doesn't exist
51 """
28 if not filepath: 52 if not filepath:
29 filepath = self.reqjson 53 filepath = self.reqjson
30 54
@@ -58,6 +82,13 @@ class SteamRelationships:
58 return final 82 return final
59 83
60 def _checkrequests(self, filename: str = "") -> int: 84 def _checkrequests(self, filename: str = "") -> int:
85 """
86 _checkrequests(self, filename: str = "") -> int: Check the requests log to make sure Steam's request limit hasn't been passed
87 filename: filepath to requests log
88 int (return): The number of requests in the last 24 hours. -1 on error, 0 on too many requests, and >1 if a valid number of requests
89
90 _checkrequests will create a file at filepath if it doesn't exist. It will also never go over 100,000 requests, regardless of what reqsafetybuffer is
91 """
61 if not filename: 92 if not filename:
62 filename = self.reqjson 93 filename = self.reqjson
63 94
@@ -100,7 +131,14 @@ class SteamRelationships:
100 131
101 132
102 133
103 def _getFriendsList(self, steamid64: str, retrys: int = 0) -> dict: 134 def _getFriendsList(self, steamid64: str, _retries: int = 0) -> dict:
135 """
136 _getFriendsList(self, steamid64: str, _retries: int = 0) -> dict: Send a request to the Steam Web API to get a user's friends list
137 steamid64: A Steam User's id, in the steamid64 format
138 _retries: An internal value used to limit the number of retries after a timeout. Increments by one automatically on every timeout
139
140 dict (return): The json representation of Steam's response. Empty on error
141 """
104 if not steamid64: 142 if not steamid64:
105 print("[_getFriendsList] No steamid64 given") 143 print("[_getFriendsList] No steamid64 given")
106 return {} 144 return {}
@@ -120,9 +158,9 @@ class SteamRelationships:
120 158
121 except requests.exceptions.Timeout: 159 except requests.exceptions.Timeout:
122 print(f"[_getFriendsList] Request timed out (No response for {self.timeout} seconds)") 160 print(f"[_getFriendsList] Request timed out (No response for {self.timeout} seconds)")
123 if retrys <= self.timeout_retries: 161 if _retries <= self.timeout_retries:
124 print(f"[_getFriendsList] Retrying request... (Attempt {retrys}/{self.timeout_retries})") 162 print(f"[_getFriendsList] Retrying request... (Attempt {_retries}/{self.timeout_retries})")
125 return self._getFriendsList(steamid64, retrys + 1) 163 return self._getFriendsList(steamid64, _retries + 1)
126 164
127 print("[_getFriendsList] Retry limit reached") 165 print("[_getFriendsList] Retry limit reached")
128 return {} 166 return {}
@@ -156,6 +194,13 @@ class SteamRelationships:
156 return resultjson 194 return resultjson
157 195
158 def _parseFriendsList(self, friendsdict: dict) -> list: 196 def _parseFriendsList(self, friendsdict: dict) -> list:
197 """
198 _parseFriendsList(self, friendsdict: dict) -> list: Parse a response from Steam and extract a user's friend's Steam IDs
199 friendsdict: The return value of _getFriendsList
200
201 list (return): The steamid64's of a user's friends. Empty on error
202 """
203
159 if not friendsdict: 204 if not friendsdict:
160 print("[_parseFriendsList] Empty friends dict given") 205 print("[_parseFriendsList] Empty friends dict given")
161 return [] 206 return []
@@ -184,7 +229,8 @@ class SteamRelationships:
184 (dict) {steamid64: [friendID1, friendID2, ...]} - A dict with a single key, that of the scanned user, which maps to a list of the users's friends 229 (dict) {steamid64: [friendID1, friendID2, ...]} - A dict with a single key, that of the scanned user, which maps to a list of the users's friends
185 230
186 ''' 231 '''
187 return {steamid64: self._parseFriendsList(self._getFriendsList(steamid64))} 232 self.scanlist = {steamid64: self._parseFriendsList(self._getFriendsList(steamid64))}
233 return self.scanlist
188 234
189 def recursivescan(self, steamid64: str, recurselevel: int = 2) -> dict: 235 def recursivescan(self, steamid64: str, recurselevel: int = 2) -> dict:
190 ''' 236 '''
@@ -193,6 +239,7 @@ class SteamRelationships:
193 PARAMS: 239 PARAMS:
194 (str) steamid64 - The starting user to scan 240 (str) steamid64 - The starting user to scan
195 (int) recurselevel - The number of recursive scans to complete 241 (int) recurselevel - The number of recursive scans to complete
242 > Note: 0 is equivalent to a basic scan; 1 scans the specified user, then the user's friends; etc.
196 243
197 RETURN VALUES: 244 RETURN VALUES:
198 (dict) EMPTY - Some catastrophic error has occured and no scan could be started 245 (dict) EMPTY - Some catastrophic error has occured and no scan could be started
@@ -203,17 +250,15 @@ class SteamRelationships:
203 [other_friend, other_friend, ...], 250 [other_friend, other_friend, ...],
204 friend2id: 251 friend2id:
205 [other_friend, other_friend, ...], 252 [other_friend, other_friend, ...],
253 friend3id:
254 [other_friend, other_friend, ...],
206 ... 255 ...
207 } 256 }
208 257
209 - A dict containing the starting steamid, then the friends contained in the original scan with the results of their scan 258 - A dict containing the starting steamid, then the friends contained in the original scan with the results of their scan
210 259
211 NOTE: 260 NOTE:
212 Please do not use a value greater than 3. While theoretically any value works, due to the exponential 261 Please do not use a value greater than 3. While theoretically any value works, due to the exponential nature of friendship relations, you will very quickly spam Steam with tens of thousands of requests. If this concept is unfamiliar to you, please take a quick glance at the Wikipedia page for "six degress of separation": https://en.wikipedia.org/wiki/Six_degrees_of_separation
213 nature of friendship relations, you will very quickly spam Steam with tens of thousands of requests.
214 If this concept is unfamiliar to you, please take a quick glance at the Wikipedia page for
215 "six degress of separation": https://en.wikipedia.org/wiki/Six_degrees_of_separation
216
217 262
218 TLDR: recursivescan is exponential and you will reach 100,000 requests very quickly if you recurse greater than 3 263 TLDR: recursivescan is exponential and you will reach 100,000 requests very quickly if you recurse greater than 3
219 ''' 264 '''
@@ -235,5 +280,5 @@ class SteamRelationships:
235 testlist.update(tempdict) 280 testlist.update(tempdict)
236 tempdict.clear() 281 tempdict.clear()
237 282
238 self.scanlist.update(testlist) 283 self.scanlist = testlist
239 return testlist \ No newline at end of file 284 return self.scanlist \ No newline at end of file