diff options
| author | NW/RL <NWRL@dabikers.online> | 2024-03-29 16:12:28 -0500 |
|---|---|---|
| committer | NW/RL <NWRL@dabikers.online> | 2024-03-29 16:12:28 -0500 |
| commit | f6732f692d463f238fc0d645017cc5628e87a68a (patch) | |
| tree | 576823fb2434e92d5312b343e38cb7673b6f123c /steamrelationships/sr.py | |
| parent | ab4bd819ff8a918c7892b94884aabbbcd65f7a42 (diff) | |
Do some work on limiting requests
Diffstat (limited to 'steamrelationships/sr.py')
| -rw-r--r-- | steamrelationships/sr.py | 121 |
1 files changed, 109 insertions, 12 deletions
diff --git a/steamrelationships/sr.py b/steamrelationships/sr.py index a9d8119..54a6464 100644 --- a/steamrelationships/sr.py +++ b/steamrelationships/sr.py | |||
| @@ -1,19 +1,112 @@ | |||
| 1 | import requests | 1 | import requests |
| 2 | import json | 2 | import json |
| 3 | import time | ||
| 4 | |||
| 5 | from steamrelationships.constants import _Const | ||
| 6 | CONST = _Const() | ||
| 3 | 7 | ||
| 4 | class SteamRelationships: | 8 | class SteamRelationships: |
| 5 | session = requests.Session() # Session object so repeated querries to steam's api use the same TCP connection | 9 | session: object = requests.Session() # Session object so repeated querries to steam's api use the same TCP connection |
| 6 | scanlist = [] # To be populated by recurse() | 10 | scanlist: list = [] # To be populated by recurse() |
| 11 | reqjson: str = "requests.json" | ||
| 7 | 12 | ||
| 8 | def __init__(self, webapikey, timeout=30) -> None: | 13 | def __init__(self, webapikey: str, timeout: int = 30, reqdelay: float = 0.5, delayrand: float = 1.25, reqsafetybuffer: float = 0.9, reqjson: str = "requests.json") -> None: |
| 9 | ''' | 14 | f''' |
| 10 | (str/int) webapikey - Steam dev api key required to use Steam's ISteamUser/GetFriendList interface | 15 | (str/int) webapikey - Steam dev api key required to use Steam's ISteamUser/GetFriendList interface |
| 16 | |||
| 17 | Request Options: | ||
| 11 | (int/float) timeout (Default: 30) - Seconds to wait before timing out a request. | 18 | (int/float) timeout (Default: 30) - Seconds to wait before timing out a request. |
| 19 | (float) reqdelay (Default: 0.5) - Default amount of seconds to wait between sending each request to Steam | ||
| 20 | (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) | ||
| 21 | (float) reqsafetybuffer (Default: 0.9) - Highest percent of Steam's API request limit you are willing to run | ||
| 22 | > Steam has an API request limit of {CONST.STEAMAPI_MAXREQ} requests per day. | ||
| 23 | reqsafetybuffer = {reqsafetybuffer} means SteamRequests will send a max of {int(CONST.STEAMAPI_MAXREQ * reqsafetybuffer)} requests per day. | ||
| 24 | Entering a number higher than 1 will not result in sending more than {CONST.STEAMAPI_MAXREQ} requests per day | ||
| 25 | |||
| 26 | (str) reqjson (Default: "requests.json") - The name/filepath of the file to store request times in | ||
| 12 | ''' | 27 | ''' |
| 13 | self.webapikey = webapikey | 28 | self.webapikey = webapikey |
| 14 | self.timeout = timeout | 29 | self.timeout = timeout |
| 30 | self.reqdelay = reqdelay | ||
| 31 | self.delayrand = delayrand | ||
| 32 | self.reqsafetybuffer = reqsafetybuffer | ||
| 33 | self.reqjson = reqjson | ||
| 34 | |||
| 35 | def _readjsonfile(self, filepath: str = "") -> dict: | ||
| 36 | if not filepath: | ||
| 37 | filepath = self.reqjson | ||
| 38 | |||
| 39 | final: dict = {} | ||
| 40 | |||
| 41 | # Try to read the contents of the given file | ||
| 42 | try: | ||
| 43 | with open(filepath, "r+") as jsonfile: | ||
| 44 | final = json.load(jsonfile) | ||
| 45 | |||
| 46 | jsonfile.close() | ||
| 47 | |||
| 48 | # If the file does not exist, create one and slap an empty list in it | ||
| 49 | except FileNotFoundError: | ||
| 50 | print(f"File {filepath} does not exist. Generating empty json file...") | ||
| 51 | try: | ||
| 52 | with open(filepath, "w+") as newfile: | ||
| 53 | json.dump({time.time_ns(): [0, []]}, newfile, indent=4) | ||
| 54 | newfile.close() | ||
| 55 | |||
| 56 | return self._readjsonfile(filepath) | ||
| 57 | |||
| 58 | except: | ||
| 59 | print("Couldn't create new file") | ||
| 60 | return {} | ||
| 61 | |||
| 62 | except: | ||
| 63 | print("Other unknown error occured") | ||
| 64 | return {} | ||
| 65 | |||
| 66 | return final | ||
| 15 | 67 | ||
| 16 | def _getFriendsList(self, steamid64 = None) -> dict: | 68 | def _checkrequests(self, filename: str = "") -> int: |
| 69 | if not filename: | ||
| 70 | filename = self.reqjson | ||
| 71 | |||
| 72 | # Get the contents of the specified file | ||
| 73 | jsoncontents: dict = {} | ||
| 74 | try: | ||
| 75 | jsoncontents = self._readjsonfile(filename) | ||
| 76 | |||
| 77 | except: | ||
| 78 | print(f"Could not get the contents of file {filename}") | ||
| 79 | return -1 | ||
| 80 | |||
| 81 | # 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)) ] | ||
| 82 | checktime = time.time_ns() | ||
| 83 | if (checktime - int(list(jsoncontents.keys())[-1])) > (8.64 * (10 ** 13)): | ||
| 84 | jsoncontents[checktime] = [0, []] | ||
| 85 | |||
| 86 | else: | ||
| 87 | # This bullshit brought to you by: ordered dictionaries | ||
| 88 | currentreqs = jsoncontents[list(jsoncontents.keys())[-1]][0] | ||
| 89 | if currentreqs < (CONST.STEAMAPI_MAXREQ * self.reqsafetybuffer) and currentreqs < CONST.STEAMAPI_MAXREQ: | ||
| 90 | jsoncontents[list(jsoncontents.keys())[-1]][0] += 1 | ||
| 91 | jsoncontents[list(jsoncontents.keys())[-1]][1].append(checktime) | ||
| 92 | |||
| 93 | else: | ||
| 94 | print(f"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})") | ||
| 95 | return 0 | ||
| 96 | |||
| 97 | # Update the json file | ||
| 98 | try: | ||
| 99 | with open(filename, "w+t") as jsonfile: | ||
| 100 | json.dump(jsoncontents, jsonfile, indent=4) | ||
| 101 | jsonfile.close() | ||
| 102 | |||
| 103 | except: | ||
| 104 | print("Could not update json file") | ||
| 105 | return -1 | ||
| 106 | |||
| 107 | return jsoncontents[list(jsoncontents.keys())[-1]][0] | ||
| 108 | |||
| 109 | def _getFriendsList(self, steamid64: str = None) -> dict: | ||
| 17 | # example url: http://api.steampowered.com/ISteamUser/GetFriendList/v0001/?key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&steamid=76561197960435530&relationship=friend | 110 | # example url: http://api.steampowered.com/ISteamUser/GetFriendList/v0001/?key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&steamid=76561197960435530&relationship=friend |
| 18 | if not steamid64: | 111 | if not steamid64: |
| 19 | print("Requested id must not be blank") | 112 | print("Requested id must not be blank") |
| @@ -23,7 +116,11 @@ class SteamRelationships: | |||
| 23 | url = "https://api.steampowered.com/ISteamUser/GetFriendList/v0001/" | 116 | url = "https://api.steampowered.com/ISteamUser/GetFriendList/v0001/" |
| 24 | options = {"key": self.webapikey, "steamid": steamid64, "relationship": "friend"} | 117 | options = {"key": self.webapikey, "steamid": steamid64, "relationship": "friend"} |
| 25 | 118 | ||
| 26 | response = self.session.get(url, params=options, timeout=self.timeout) # GET should be as secure as POST because ssl is being used | 119 | if self._checkrequests() > 0: |
| 120 | response = self.session.get(url, params=options, timeout=self.timeout) # GET should be as secure as POST because ssl is being used | ||
| 121 | |||
| 122 | else: | ||
| 123 | return None | ||
| 27 | 124 | ||
| 28 | # TODO: Implement proper error checking so that this doesn't just break if someone has a private friends list | 125 | # TODO: Implement proper error checking so that this doesn't just break if someone has a private friends list |
| 29 | if response.status_code == requests.codes.ok: | 126 | if response.status_code == requests.codes.ok: |
| @@ -31,7 +128,7 @@ class SteamRelationships: | |||
| 31 | 128 | ||
| 32 | return None | 129 | return None |
| 33 | 130 | ||
| 34 | def parseFriendsList(self, friendslist = None) -> list: | 131 | def parseFriendsList(self, friendslist: dict = None) -> list: |
| 35 | if not friendslist: | 132 | if not friendslist: |
| 36 | return None | 133 | return None |
| 37 | 134 | ||
| @@ -41,21 +138,21 @@ class SteamRelationships: | |||
| 41 | 138 | ||
| 42 | return final | 139 | return final |
| 43 | 140 | ||
| 44 | def recursive_scan(self, startid = None, recurselevel = 2) -> list: | 141 | def basic_scan(self, startid: str = None) -> list: |
| 45 | # Scan an initial id, then populate a list with the scans of each friend | 142 | # Scan an initial id, then populate a list with the scans of each friend |
| 46 | scans = {} | 143 | scans: dict = {} |
| 47 | alreadyscanned = [] | 144 | alreadyscanned: list = [] |
| 48 | 145 | ||
| 49 | # Start the scan and collect the first user's friend list | 146 | # Start the scan and collect the first user's friend list |
| 50 | scans[startid] = self.parseFriendsList(self._getFriendsList(startid)) | 147 | scans[startid] = self.parseFriendsList(self._getFriendsList(startid)) |
| 51 | alreadyscanned.append(startid) | 148 | alreadyscanned.append(startid) |
| 52 | 149 | ||
| 150 | ''' | ||
| 53 | # Scan the current scanid's friends and append them to the list | 151 | # Scan the current scanid's friends and append them to the list |
| 54 | for friend in scans[startid]: | 152 | for friend in scans[startid]: |
| 55 | if friend not in alreadyscanned: | 153 | if friend not in alreadyscanned: |
| 56 | scans[friend] = self.parseFriendsList(self._getFriendsList(friend)) | 154 | scans[friend] = self.parseFriendsList(self._getFriendsList(friend)) |
| 57 | alreadyscanned.append(friend) | 155 | alreadyscanned.append(friend) |
| 58 | 156 | ''' | |
| 59 | #TODO: Find way to repeat this by recurse level | ||
| 60 | 157 | ||
| 61 | return scans \ No newline at end of file | 158 | return scans \ No newline at end of file |
