summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNW/RL <NWRL@dabikers.online>2024-04-02 14:31:33 -0500
committerNW/RL <NWRL@dabikers.online>2024-04-02 14:31:33 -0500
commitd6832a62fe0272ba725b3d5ceab64b5fb3a07276 (patch)
tree7697835c9dd71b908c5167458f80e1a0df670a2d
parent56bcf6615a51989dfbe4c7dbbb2d03b4af5f32fb (diff)
Implement request throttling
-rw-r--r--steamrelationships/sr.py51
1 files changed, 24 insertions, 27 deletions
diff --git a/steamrelationships/sr.py b/steamrelationships/sr.py
index 4bad716..1cf9c2e 100644
--- a/steamrelationships/sr.py
+++ b/steamrelationships/sr.py
@@ -1,6 +1,7 @@
1import requests 1import requests
2import json 2import json
3import time 3import time
4import random
4 5
5from steamrelationships.constants import _Const 6from steamrelationships.constants import _Const
6CONST = _Const() 7CONST = _Const()
@@ -8,33 +9,19 @@ CONST = _Const()
8class SteamRelationships: 9class SteamRelationships:
9 session: object = requests.Session() # Session object so repeated querries to steam's api use the same TCP connection 10 session: object = requests.Session() # Session object so repeated querries to steam's api use the same TCP connection
10 scanlist: dict = {} # To be populated by recursivescan() 11 scanlist: dict = {} # To be populated by recursivescan()
11 reqjson: str = "requests.json" # Path to the JSON file that handles requests
12 12
13 def __init__(self, webapikey: str, timeout: int = 30, timeout_retries: int = 5, reqdelay: float = 0.5, delayrand: float = 1.25, reqsafetybuffer: float = 0.9, reqjson: str = "requests.json") -> None: 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:
14 """ 14 """
15 (str/int) webapikey - Steam dev api key required to use Steam's ISteamUser/GetFriendList interface
16
17 Request Options:
18 (int) timeout (Default: 30) - Seconds to wait before timing out a request.
19 (int) timeout_retries (Default: 5) Number of times to retry a request after timing out
20 (float) reqdelay (Default: 0.5) - Default amount of seconds to wait between sending each request to Steam
21 (float) delayrand (Default: 1.25) - The max percentage of extra delay to randomly add to each request (1 = no randomness, <1 = randomly shorter, >1 = randomly longer)
22 (float) reqsafetybuffer (Default: 0.9) - Highest percent of Steam's API request limit you are willing to run
23 > Steam has an API request limit of 100,000 requests per day.
24 reqsafetybuffer = 0.9 means SteamRequests will send a max of 90,000 requests per day.
25 Entering a number higher than 1 will not result in sending more than 100,000 requests per day
26
27 (str) reqjson (Default: "requests.json") - The name/filepath of the file to store request times in
28 """ # I can't make this a formatted string :sob: 15 """ # I can't make this a formatted string :sob:
29 16
30 self.webapikey = webapikey 17 # This used to be in a try-except block, but I decided that it should error out on a proper error
31 self.timeout = timeout 18 self.webapikey: str = str(webapikey)
32 self.timeout_retries = timeout_retries 19 self.timeout: int = int(abs(timeout))
33 self.reqdelay = reqdelay 20 self.timeout_retries: int = int(abs(timeout_retries))
34 self.delayrand = delayrand 21 self.reqdelay: float = float(abs(reqdelay))
35 self.reqsafetybuffer = reqsafetybuffer 22 self.delayrand: int = int(abs(delayrand) % 100)
36 self.reqjson = reqjson 23 self.reqsafetybuffer: float = float(abs(reqsafetybuffer))
37 24 self.reqjson: str = str(reqjson)
38 25
39 26
40 def _readjsonfile(self, filepath: str = "") -> dict: 27 def _readjsonfile(self, filepath: str = "") -> dict:
@@ -80,7 +67,7 @@ class SteamRelationships:
80 jsoncontents = self._readjsonfile(filename) 67 jsoncontents = self._readjsonfile(filename)
81 68
82 except Exception as e: 69 except Exception as e:
83 print(f"[_checkrequests] Could not get the contents of file {filename} ({e})") 70 print(f"[_checkrequests] Could not get the contents of file \"{filename}\" ({e})")
84 return -1 71 return -1
85 72
86 # 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)) ] 73 # 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)) ]
@@ -96,7 +83,7 @@ class SteamRelationships:
96 jsoncontents[list(jsoncontents.keys())[-1]][1].append(checktime) 83 jsoncontents[list(jsoncontents.keys())[-1]][1].append(checktime)
97 84
98 else: 85 else:
99 print(f"[_checkrequests] Daily request limit reached ({jsoncontents[list(jsoncontents.keys())[-1]][0]}/{CONST.STEAMAPI_MAXREQ * self.reqsafetybuffer}). Please try again tomorrow, or increase \"reqsafetybuffer\" (currently: {self.reqsafetybuffer})") 86 print(f"[_checkrequests] Daily request limit reached ({currentreqs}/{CONST.STEAMAPI_MAXREQ * self.reqsafetybuffer}). Please try again tomorrow, or increase \"reqsafetybuffer\" (currently: {self.reqsafetybuffer})")
100 return 0 87 return 0
101 88
102 # Update the json file 89 # Update the json file
@@ -106,7 +93,7 @@ class SteamRelationships:
106 jsonfile.close() 93 jsonfile.close()
107 94
108 except Exception as e: 95 except Exception as e:
109 print("[_checkrequests] Could not update json file ({e})") 96 print(f"[_checkrequests] Could not update json file ({e})")
110 return -1 97 return -1
111 98
112 return jsoncontents[list(jsoncontents.keys())[-1]][0] 99 return jsoncontents[list(jsoncontents.keys())[-1]][0]
@@ -146,7 +133,11 @@ class SteamRelationships:
146 133
147 # Error out on request error 134 # Error out on request error
148 if result.status_code != requests.codes.ok: 135 if result.status_code != requests.codes.ok:
149 print(f"[_getFriendsList] Got bad status code (Requested id: {steamid64}, status: {result.status_code})") 136 print(f"[_getFriendsList] Got bad status code (Requested id: {steamid64}, status: {result.status_code})", end="")
137 if result.status_code == 401: # Steam returns a 401 on profiles with private friends lists
138 print(f". It is likely that the requested id has a private profile / private friends list", end="")
139
140 print("\n", end="")
150 return {} 141 return {}
151 142
152 # Get the json contents from the response 143 # Get the json contents from the response
@@ -222,6 +213,9 @@ class SteamRelationships:
222 nature of friendship relations, you will very quickly spam Steam with tens of thousands of requests. 213 nature of friendship relations, you will very quickly spam Steam with tens of thousands of requests.
223 If this concept is unfamiliar to you, please take a quick glance at the Wikipedia page for 214 If this concept is unfamiliar to you, please take a quick glance at the Wikipedia page for
224 "six degress of separation": https://en.wikipedia.org/wiki/Six_degrees_of_separation 215 "six degress of separation": https://en.wikipedia.org/wiki/Six_degrees_of_separation
216
217
218 TLDR: recursivescan is exponential and you will reach 100,000 requests very quickly if you recurse greater than 3
225 ''' 219 '''
226 220
227 testlist: dict = self.basicscan(steamid64) 221 testlist: dict = self.basicscan(steamid64)
@@ -234,6 +228,9 @@ class SteamRelationships:
234 if friend not in alreadyscanned: 228 if friend not in alreadyscanned:
235 tempdict.update(self.basicscan(friend)) 229 tempdict.update(self.basicscan(friend))
236 alreadyscanned.append(friend) 230 alreadyscanned.append(friend)
231
232 sleepytime: float = abs(random.randrange(100 - self.delayrand, 100 + self.delayrand) / 100 * self.reqdelay)
233 time.sleep(sleepytime)
237 234
238 testlist.update(tempdict) 235 testlist.update(tempdict)
239 tempdict.clear() 236 tempdict.clear()