summaryrefslogtreecommitdiff
path: root/steamrelationships
diff options
context:
space:
mode:
Diffstat (limited to 'steamrelationships')
-rw-r--r--steamrelationships/__init__.py3
-rw-r--r--steamrelationships/constants.py12
-rw-r--r--steamrelationships/sr.py121
3 files changed, 124 insertions, 12 deletions
diff --git a/steamrelationships/__init__.py b/steamrelationships/__init__.py
index e69de29..b4b2480 100644
--- a/steamrelationships/__init__.py
+++ b/steamrelationships/__init__.py
@@ -0,0 +1,3 @@
1from steamrelationships.sr import SteamRelationships as sr
2from steamrelationships.constants import _Const
3CONST = _Const() \ No newline at end of file
diff --git a/steamrelationships/constants.py b/steamrelationships/constants.py
new file mode 100644
index 0000000..af35733
--- /dev/null
+++ b/steamrelationships/constants.py
@@ -0,0 +1,12 @@
1# God I fucking love python and the shit workarounds it requires for something as simple as a constant variable
2def constant(f):
3 def fset(self, value):
4 raise TypeError
5 def fget(self):
6 return f()
7 return property(fget, fset)
8
9class _Const(object):
10 @constant
11 def STEAMAPI_MAXREQ() -> int:
12 return 100000 \ No newline at end of file
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 @@
1import requests 1import requests
2import json 2import json
3import time
4
5from steamrelationships.constants import _Const
6CONST = _Const()
3 7
4class SteamRelationships: 8class 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