diff options
Diffstat (limited to 'steamrelationships/sr.py')
| -rw-r--r-- | steamrelationships/sr.py | 90 |
1 files changed, 45 insertions, 45 deletions
diff --git a/steamrelationships/sr.py b/steamrelationships/sr.py index 28b6b16..63122b4 100644 --- a/steamrelationships/sr.py +++ b/steamrelationships/sr.py | |||
| @@ -8,14 +8,14 @@ CONST = _Const() | |||
| 8 | 8 | ||
| 9 | class SteamRelationships: | 9 | class SteamRelationships: |
| 10 | """A class that handles the querring of Steam's web api to request a user's friends list""" | 10 | """A class that handles the querring of Steam's web api to request a user's friends list""" |
| 11 | 11 | ||
| 12 | 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 |
| 13 | scanlist: dict = {} # To be populated by scan functions | 13 | scanlist: dict = {} # To be populated by scan functions |
| 14 | 14 | ||
| 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: | 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: |
| 16 | """ | 16 | """ |
| 17 | (str) webapikey - A Steam "web api key" for grabbing friends lists (get one at https://steamcommunity.com/dev/apikey) | 17 | (str) webapikey - A Steam "web api key" for grabbing friends lists (get one at https://steamcommunity.com/dev/apikey) |
| 18 | 18 | ||
| 19 | (int) timeout (Default: 30) - The time in seconds to wait for a response from Steam before timing out | 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 | 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) | 21 | (float) reqdelay (Default: 0.5) - The base amount of seconds to wait after each scan (to lessen the load on Steam's servers) |
| @@ -40,14 +40,14 @@ class SteamRelationships: | |||
| 40 | def __str__(self) -> str: | 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}" | 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}" |
| 42 | 42 | ||
| 43 | def _readjsonfile(self, filepath: str = "") -> dict: | 43 | def __readjsonfile(self, filepath: str = "") -> dict: |
| 44 | """ | 44 | """ |
| 45 | _readjsonfile(self, filepath: str = "") -> dict: Read the specified json file for previous requests | 45 | __readjsonfile(self, filepath: str = "") -> dict: Read the specified json file for previous requests |
| 46 | filepath: Path to json file | 46 | filepath: Path to json file |
| 47 | 47 | ||
| 48 | dict (return): The contents of the json file. Empty on error | 48 | dict (return): The contents of the json file. Empty on error |
| 49 | 49 | ||
| 50 | _readjsonfile will create an "empty" json file if the specified file at the filepath doesn't exist | 50 | __readjsonfile will create an "empty" json file if the specified file at the filepath doesn't exist |
| 51 | """ | 51 | """ |
| 52 | if not filepath: | 52 | if not filepath: |
| 53 | filepath = self.reqjson | 53 | filepath = self.reqjson |
| @@ -63,31 +63,31 @@ class SteamRelationships: | |||
| 63 | 63 | ||
| 64 | # If the file does not exist, create one and slap an empty list in it | 64 | # If the file does not exist, create one and slap an empty list in it |
| 65 | except FileNotFoundError: | 65 | except FileNotFoundError: |
| 66 | print(f"[_readjsonfile] File {filepath} does not exist. Generating empty json file...") | 66 | print(f"[__readjsonfile] File {filepath} does not exist. Generating empty json file...") |
| 67 | try: | 67 | try: |
| 68 | with open(filepath, "w+") as newfile: | 68 | with open(filepath, "w+") as newfile: |
| 69 | json.dump({time.time_ns(): [0, []]}, newfile, indent=CONST.JSON_INDENT) | 69 | json.dump({time.time_ns(): [0, []]}, newfile, indent=CONST.JSON_INDENT) |
| 70 | newfile.close() | 70 | newfile.close() |
| 71 | 71 | ||
| 72 | return self._readjsonfile(filepath) | 72 | return self.__readjsonfile(filepath) |
| 73 | 73 | ||
| 74 | except Exception as e: | 74 | except Exception as e: |
| 75 | print(f"[_readjsonfile] Couldn't create new file ({e})") | 75 | print(f"[__readjsonfile] Couldn't create new file ({e})") |
| 76 | return {} | 76 | return {} |
| 77 | 77 | ||
| 78 | except Exception as e: | 78 | except Exception as e: |
| 79 | print(f"[_readjsonfile] Other unknown error occured ({e})") | 79 | print(f"[__readjsonfile] Other unknown error occured ({e})") |
| 80 | return {} | 80 | return {} |
| 81 | 81 | ||
| 82 | return final | 82 | return final |
| 83 | 83 | ||
| 84 | def _checkrequests(self, filename: str = "") -> int: | 84 | def __checkrequests(self, filename: str = "") -> int: |
| 85 | """ | 85 | """ |
| 86 | _checkrequests(self, filename: str = "") -> int: Check the requests log to make sure Steam's request limit hasn't been passed | 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 | 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 | 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 | 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 | 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 | """ | 91 | """ |
| 92 | if not filename: | 92 | if not filename: |
| 93 | filename = self.reqjson | 93 | filename = self.reqjson |
| @@ -95,10 +95,10 @@ class SteamRelationships: | |||
| 95 | # Get the contents of the specified file | 95 | # Get the contents of the specified file |
| 96 | jsoncontents: dict = {} | 96 | jsoncontents: dict = {} |
| 97 | try: | 97 | try: |
| 98 | jsoncontents = self._readjsonfile(filename) | 98 | jsoncontents = self.__readjsonfile(filename) |
| 99 | 99 | ||
| 100 | except Exception as e: | 100 | except Exception as e: |
| 101 | print(f"[_checkrequests] Could not get the contents of file \"{filename}\" ({e})") | 101 | print(f"[__checkrequests] Could not get the contents of file \"{filename}\" ({e})") |
| 102 | return -1 | 102 | return -1 |
| 103 | 103 | ||
| 104 | # Check the current date. If over 1 day since last entry, add a new entry. Otherwise, edit the current day's entry [note, 1 day in nanoseconds = (8.64 * (10 ** 13)) ] | 104 | # Check the current date. If over 1 day since last entry, add a new entry. Otherwise, edit the current day's entry [note, 1 day in nanoseconds = (8.64 * (10 ** 13)) ] |
| @@ -114,7 +114,7 @@ class SteamRelationships: | |||
| 114 | jsoncontents[list(jsoncontents.keys())[-1]][1].append(checktime) | 114 | jsoncontents[list(jsoncontents.keys())[-1]][1].append(checktime) |
| 115 | 115 | ||
| 116 | else: | 116 | else: |
| 117 | print(f"[_checkrequests] Daily request limit reached ({currentreqs}/{CONST.STEAMAPI_MAXREQ * self.reqsafetybuffer}). Please try again tomorrow, or increase \"reqsafetybuffer\" (currently: {self.reqsafetybuffer})") | 117 | print(f"[__checkrequests] Daily request limit reached ({currentreqs}/{CONST.STEAMAPI_MAXREQ * self.reqsafetybuffer}). Please try again tomorrow, or increase \"reqsafetybuffer\" (currently: {self.reqsafetybuffer})") |
| 118 | return 0 | 118 | return 0 |
| 119 | 119 | ||
| 120 | # Update the json file | 120 | # Update the json file |
| @@ -124,28 +124,28 @@ class SteamRelationships: | |||
| 124 | jsonfile.close() | 124 | jsonfile.close() |
| 125 | 125 | ||
| 126 | except Exception as e: | 126 | except Exception as e: |
| 127 | print(f"[_checkrequests] Could not update json file ({e})") | 127 | print(f"[__checkrequests] Could not update json file ({e})") |
| 128 | return -1 | 128 | return -1 |
| 129 | 129 | ||
| 130 | return jsoncontents[list(jsoncontents.keys())[-1]][0] | 130 | return jsoncontents[list(jsoncontents.keys())[-1]][0] |
| 131 | 131 | ||
| 132 | 132 | ||
| 133 | 133 | ||
| 134 | def _getFriendsList(self, steamid64: str, _retries: int = 0) -> dict: | 134 | def __getFriendsList(self, steamid64: str, _retries: int = 0) -> dict: |
| 135 | """ | 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 | 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 | 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 | 138 | _retries: An internal value used to limit the number of retries after a timeout. Increments by one automatically on every timeout |
| 139 | 139 | ||
| 140 | dict (return): The json representation of Steam's response. Empty on error | 140 | dict (return): The json representation of Steam's response. Empty on error |
| 141 | """ | 141 | """ |
| 142 | if not steamid64: | 142 | if not steamid64: |
| 143 | print("[_getFriendsList] No steamid64 given") | 143 | print("[__getFriendsList] No steamid64 given") |
| 144 | return {} | 144 | return {} |
| 145 | 145 | ||
| 146 | # Make sure we haven't gone over steam's daily max | 146 | # Make sure we haven't gone over steam's daily max |
| 147 | if self._checkrequests() == 0: | 147 | if self.__checkrequests() == 0: |
| 148 | print("[_getFriendsList] Max requests reached, refusing to contact steam") | 148 | print("[__getFriendsList] Max requests reached, refusing to contact steam") |
| 149 | return {} | 149 | return {} |
| 150 | 150 | ||
| 151 | url: str = "https://api.steampowered.com/ISteamUser/GetFriendList/v0001/" | 151 | url: str = "https://api.steampowered.com/ISteamUser/GetFriendList/v0001/" |
| @@ -157,24 +157,24 @@ class SteamRelationships: | |||
| 157 | result = self.session.get(url, params=options, timeout=self.timeout) | 157 | result = self.session.get(url, params=options, timeout=self.timeout) |
| 158 | 158 | ||
| 159 | except requests.exceptions.Timeout: | 159 | except requests.exceptions.Timeout: |
| 160 | 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)") |
| 161 | if _retries <= self.timeout_retries: | 161 | if _retries <= self.timeout_retries: |
| 162 | print(f"[_getFriendsList] Retrying request... (Attempt {_retries}/{self.timeout_retries})") | 162 | print(f"[__getFriendsList] Retrying request... (Attempt {_retries}/{self.timeout_retries})") |
| 163 | return self._getFriendsList(steamid64, _retries + 1) | 163 | return self.__getFriendsList(steamid64, _retries + 1) |
| 164 | 164 | ||
| 165 | print("[_getFriendsList] Retry limit reached") | 165 | print("[__getFriendsList] Retry limit reached") |
| 166 | return {} | 166 | return {} |
| 167 | 167 | ||
| 168 | except Exception as e: | 168 | except Exception as e: |
| 169 | print(f"[_getFriendsList] Other error in contacting steam ({e})") | 169 | print(f"[__getFriendsList] Other error in contacting steam ({e})") |
| 170 | return {} | 170 | return {} |
| 171 | 171 | ||
| 172 | # Error out on request error | 172 | # Error out on request error |
| 173 | if result.status_code != requests.codes.ok: | 173 | if result.status_code != requests.codes.ok: |
| 174 | print(f"[_getFriendsList] Got bad status code (Requested id: {steamid64}, status: {result.status_code})", end="") | 174 | print(f"[__getFriendsList] Got bad status code (Requested id: {steamid64}, status: {result.status_code})", end="") |
| 175 | if result.status_code == 401: # Steam returns a 401 on profiles with private friends lists | 175 | if result.status_code == 401: # Steam returns a 401 on profiles with private friends lists |
| 176 | print(f". It is likely that the requested id has a private profile / private friends list", end="") | 176 | print(f". It is likely that the requested id has a private profile / private friends list", end="") |
| 177 | 177 | ||
| 178 | print("\n", end="") | 178 | print("\n", end="") |
| 179 | return {} | 179 | return {} |
| 180 | 180 | ||
| @@ -184,25 +184,25 @@ class SteamRelationships: | |||
| 184 | resultjson = result.json() | 184 | resultjson = result.json() |
| 185 | 185 | ||
| 186 | except requests.exceptions.JSONDecodeError: | 186 | except requests.exceptions.JSONDecodeError: |
| 187 | print("[_getFriendsList] Could not decode json response for some reason") | 187 | print("[__getFriendsList] Could not decode json response for some reason") |
| 188 | return {} | 188 | return {} |
| 189 | 189 | ||
| 190 | except Exception as e: | 190 | except Exception as e: |
| 191 | print(f"[_getFriendsList] Unknown error in getting json response ({e})") | 191 | print(f"[__getFriendsList] Unknown error in getting json response ({e})") |
| 192 | return {} | 192 | return {} |
| 193 | 193 | ||
| 194 | return resultjson | 194 | return resultjson |
| 195 | 195 | ||
| 196 | def _parseFriendsList(self, friendsdict: dict) -> list: | 196 | def __parseFriendsList(self, friendsdict: dict) -> list: |
| 197 | """ | 197 | """ |
| 198 | _parseFriendsList(self, friendsdict: dict) -> list: Parse a response from Steam and extract a user's friend's Steam IDs | 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 | 199 | friendsdict: The return value of __getFriendsList |
| 200 | 200 | ||
| 201 | list (return): The steamid64's of a user's friends. Empty on error | 201 | list (return): The steamid64's of a user's friends. Empty on error |
| 202 | """ | 202 | """ |
| 203 | 203 | ||
| 204 | if not friendsdict: | 204 | if not friendsdict: |
| 205 | print("[_parseFriendsList] Empty friends dict given") | 205 | print("[__parseFriendsList] Empty friends dict given") |
| 206 | return [] | 206 | return [] |
| 207 | 207 | ||
| 208 | 208 | ||
| @@ -212,7 +212,7 @@ class SteamRelationships: | |||
| 212 | people.append(f'{friend["steamid"]}') | 212 | people.append(f'{friend["steamid"]}') |
| 213 | 213 | ||
| 214 | except Exception as e: | 214 | except Exception as e: |
| 215 | print("[_parseFriendsList] Error parsing friendsdict ({e})") | 215 | print("[__parseFriendsList] Error parsing friendsdict ({e})") |
| 216 | people.clear() | 216 | people.clear() |
| 217 | 217 | ||
| 218 | return people | 218 | return people |
| @@ -229,7 +229,7 @@ class SteamRelationships: | |||
| 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 | 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 |
| 230 | 230 | ||
| 231 | ''' | 231 | ''' |
| 232 | self.scanlist = {steamid64: self._parseFriendsList(self._getFriendsList(steamid64))} | 232 | self.scanlist = {steamid64: self.__parseFriendsList(self.__getFriendsList(steamid64))} |
| 233 | return self.scanlist | 233 | return self.scanlist |
| 234 | 234 | ||
| 235 | def recursivescan(self, steamid64: str, recurselevel: int = 2) -> dict: | 235 | def recursivescan(self, steamid64: str, recurselevel: int = 2) -> dict: |
| @@ -244,12 +244,12 @@ class SteamRelationships: | |||
| 244 | RETURN VALUES: | 244 | RETURN VALUES: |
| 245 | (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 |
| 246 | (dict) { | 246 | (dict) { |
| 247 | steamid64: | 247 | steamid64: |
| 248 | [friend1id, friend2id, friend3id, ...], | 248 | [friend1id, friend2id, friend3id, ...], |
| 249 | friend1id: | 249 | friend1id: |
| 250 | [other_friend, other_friend, ...], | 250 | [other_friend, other_friend, ...], |
| 251 | friend2id: | 251 | friend2id: |
| 252 | [other_friend, other_friend, ...], | 252 | [other_friend, other_friend, ...], |
| 253 | friend3id: | 253 | friend3id: |
| 254 | [other_friend, other_friend, ...], | 254 | [other_friend, other_friend, ...], |
| 255 | ... | 255 | ... |
| @@ -276,7 +276,7 @@ class SteamRelationships: | |||
| 276 | 276 | ||
| 277 | sleepytime: float = abs(random.randrange(100 - self.delayrand, 100 + self.delayrand) / 100 * self.reqdelay) | 277 | sleepytime: float = abs(random.randrange(100 - self.delayrand, 100 + self.delayrand) / 100 * self.reqdelay) |
| 278 | time.sleep(sleepytime) | 278 | time.sleep(sleepytime) |
| 279 | 279 | ||
| 280 | testlist.update(tempdict) | 280 | testlist.update(tempdict) |
| 281 | tempdict.clear() | 281 | tempdict.clear() |
| 282 | 282 | ||
