-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathsvr_lib.py
371 lines (296 loc) · 12.4 KB
/
svr_lib.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
#!/usr/bin/env python
'''
------------------------------------------------------------------------------------------------
Author: @Isaac
Last Updated: 22 Nov 2020
Contact: Message @Isaac at https://forum.c1games.com/
Copyright: CC0 - completely open to edit, share, etc
Short Description:
This is a python library to provide some basic interactions with C1's terminal-api.
------------------------------------------------------------------------------------------------
Every function is self documented and I have provided an example script.
If you still have any questions or suggestions just let me know on the forums - @Isaac
'''
import multiprocessing as mp
import requests
import time
import json
import sys
SEASON = '7'
API_LINK = 'http://terminal.c1games.com/api'
def clean_content(content):
'''
Formats string from an HTML webpage from requests api.
Args:
* content: A string from requests.get(URL).content
Returns:
A string that can be parsed using JSON
'''
return str(content)[2:-1].replace("\\'",'').replace('\\\\','\\').replace('\\"','')
def get_page(url):
'''
Get a web page using the requests library.
Args:
* url: A webpage url to get
Returns:
A requests response object
'''
return requests.get(url)
def get_page_content(path, url=API_LINK):
'''
Get the content from a webpage, for the terminal api this is a JSON string.
Args:
* path: A path relative to terminal's api link
* url: The base url, default is terminal's api link
Returns:
A string that can be parsed using json.
'''
return clean_content(get_page(url+path).content)
def get_leaderboard_metrics():
'''
Get the leaderboard metrics that are shown on the terminal leaderboard page.
Returns:
A dictionary containing the current leaderboard metrics.
'''
contents = get_page_content('/game/leaderboard/metrics')
return json.loads(contents)['data']
def get_leaderboard_metric(key, season=SEASON):
'''
Get a specific leaderboard metric from the terminal leaderboard page.
Args:
* key: The leaderboard metric you'd like to retrieve
* season: The season of metrics to get (as a string, eg '7')
Returns:
The number associated with the leaderboard metric.
'''
data = get_leaderboard_metrics()
try: return data[season][key]
except KeyError:
raise Exception('No leaderboard metric with key: {}'.format(key))
def get_leaderboard_algos(i):
'''
Get the algos that are on the i_th page of the leaderboard.
Args:
* i: The leaderboard page to retrieve (starts at 1, not 0)
Returns:
A list of dictionaries, where each dictionary contains an algo and it's stats.
'''
if i < 1: raise KeyError('leaderboard page must be larger than 0, got {}'.format(i))
contents = get_page_content('/game/leaderboard?page={}'.format(i))
return json.loads(contents)['data']['algos']
def get_num_players(season=SEASON):
'''
Get the total number of terminal players.
Args:
* season: The season of metrics to get (as a string, eg '7')
Returns:
The total number of terminal players.
'''
return get_leaderboard_metric('Players', season=season)
def get_num_matches(season=SEASON):
'''
Get the total number of terminal matches played for a season (default is current).
Args:
* season: The season of metrics to get (as a string, eg '7')
Returns:
The total number of terminal matches played in a season.
'''
return get_leaderboard_metric('Matches', season=season)
def get_num_algos(season=SEASON):
'''
Get the total number of terminal algos uploaded.
Args:
* season: The season of metrics to get (as a string, eg '7')
Returns:
The total number of terminal algos uploaded.
'''
return get_leaderboard_metric('Algos', season=season)
def get_algos_matches(ID):
'''
Get the last matches that an algo has played.
Args:
* ID: The id of the algo
Returns:
A list of dictionaries, where each dictionary contains a match and it's stats.
'''
try:
contents = get_page_content('/game/algo/{}/matches'.format(ID))
return json.loads(contents)['data']['matches']
except json.decoder.JSONDecodeError: raise KeyError('"{}" is not a valid ID, must be an integer'.format(ID))
def search_for_id(algo_name, num_processes=20, verbose=False):
'''
Searches for an algo's id by checking it's name. It loops through every single id and it's matches until it finds the associated name.
If you make a typo, it will take a very long time, be sure :).
It searches (generally) based on the algo's last uploaded,
so if you just uploaded the algo, you can make this like 5,
but you'll want it to be larger if you uploaded the algo a long time ago.
Args:
* algo_name: The name of the algo to find
* num_processes: The number of subprocesses to start, make this larger if your algo is really old
* verbose: Whether or not to print as it searches
Returns:
An id associated with the algo_name you passed.
If there are duplicate names then it returns the first that it finds.
This will most likely be the most recent algo uploaded, but this IS NOT guaranteed.
'''
offset = 507
num_algos = sum([get_num_algos(season=str(i)) for i in range(1, int(SEASON) + 1)])
start = num_algos + offset
manager = mp.Manager()
rtn_dict = manager.dict()
next_id = manager.dict()
next_id[0] = start
ps = {}
for i in range(num_processes+1, 0, -1):
ps[i] = mp.Process(target=search_for_algo, args=(algo_name, next_id, rtn_dict, verbose))
ps[i].start()
while len(rtn_dict) == 0:
time.sleep(.1)
for p in ps.values():
p.terminate()
if verbose: print ('\n\nName: {}\t\tID: {}'.format(algo_name,rtn_dict.values()[0]))
return rtn_dict.values()[0]
def search_for_algo(algo_name, next_id, rtn_dict, verbose):
'''
Helper function that is used by search_for_id, continually checks the next_algo for algo_name.
Args:
* algo_name: The name of the algo to find
* next_id: The id of the algo it should check next
* rtn_dict: A dictionary to store whether or not the name has been found, necessary for the mp module
* verbose: Whether or not to print as it searches
Returns:
An id associated with the algo_name passed, -1 if not found.
'''
next_id[0] -= 1
ID = check_id_for_algo(algo_name, next_id[0], rtn_dict, verbose)
if ID != -1: return ID
while ID == -1 :
next_id[0] -= 1
ID = check_id_for_algo(algo_name, next_id[0], rtn_dict, verbose)
if ID != -1:
return ID
return -1
def check_id_for_algo(algo_name, ID, rtn_dict, verbose=False):
'''
Helper function that is used by search_for_algo, checks every match played by an algo to see if the algo_name is found.
Args:
* algo_name: The name of the algo to find
* ID: The id of the algo to check the matches played
* rtn_dict: A dictionary to store whether or not the name has been found, necessary for the mp module
* verbose: Whether or not to print as it searches
Returns:
An id associated with the algo_name passed, -1 if not found.
'''
try:
if verbose: print ('checking id {}\t\t\t\r'.format(ID), end='')
for match in get_algos_matches(ID):
w_algo = match['winning_algo']
l_algo = match['losing_algo']
w_name = w_algo['name']
l_name = l_algo['name']
w_id = w_algo['id']
l_id = l_algo['id']
if l_name.upper() == algo_name.upper():
rtn_dict[0] = l_id
return l_id
if w_name.upper() == algo_name.upper():
rtn_dict[0] = w_id
return w_id
except Exception as e:
print (e)
return -1
def search_leaderboard_for_id(algo_name, r=104, verbose=False):
'''
Searches for an algo's id by checking it's name. It loops through every leaderboard page and checks every algo until it finds the associated name.
This serves the same function as search_for_id, but is much much faster if you know that the algo is currently listed on the leaderboard.
Args:
* algo_name: The name of the algo to find
* r: The max number of pages to check (104 is the current max - will check every page)
* verbose: Whether or not to print as it searches
Returns:
An id associated with the algo_name you passed.
If there are duplicate names then it returns the first that it finds (highest elo).
'''
for i in range(1, r+1):
try:
if verbose: print ('checking leaderboard page {}\t\t\r'.format(i), end='')
for algo in get_leaderboard_algos(i):
name = algo['name']
ID = algo['id']
if name.upper() == algo_name.upper():
if verbose: print ('\n\nName: {}\t\tID: {}'.format(algo_name,ID))
return ID
except Exception as e:
if verbose: print ()
print (e)
break
if verbose: print ()
return -1
def get_leaderboard_ids(pages=[1], limit=(-sys.maxsize - 1)):
'''
Gets every single algo's id on leaderboard's pages.
Args:
* pages: A list of leaderboard pages to check (index number), or a single page to check
* limit: You can specify an elo limit, where it will not return any algo's below that limit
Returns:
A dictionary where each key is an algo name and it's value is it's id.
'''
if type(pages) == int: pages = [pages]
algos = {}
for i in pages:
try:
for algo in get_leaderboard_algos(i):
name = algo['name']
ID = algo['id']
elo = algo['rating']
if elo < limit: break
algos[name] = ID
except Exception as e:
print (e)
return algos
def get_match_ids(algo, in_leaderboard=False, verbose=False):
'''
Gets the ids of the matches an algo has played.
Args:
* algo: The algo you want the matches from, it can be:
- The algo's ID
- The algo's name (you should then specify whether or not it is in the leaderboard to find it faster)
* in_leaderboard: Whether or not the algo is on the leaderboard - used to make searching for an algo's id much faster
* verbose: Whether or not to print as it searches
Returns:
A list of match ids associated with that algo.
'''
if type(algo) == str:
if in_leaderboard:
ID = search_leaderboard_for_id(algo, verbose=verbose)
else:
ID = search_for_id(algo, verbose=verbose)
elif type(algo) == int:
ID = algo
if ID == -1: return []
return [match['id'] for match in get_algos_matches(ID)]
def get_match_str(mID):
'''
Function to get a simple formatted string to watch a match.
Args:
* mID: The id number of the match
Returns:
A string you can copy and paste into a browser to watch that game.
'''
return 'https://terminal.c1games.com/watch/{}'.format(mID)
def get_matches_str(algo, in_leaderboard=False, verbose=False):
'''
Function to get all of an algo's matches in formatted strings.
Args:
* algo: The algo you want the matches from, it can be:
- The algo's ID
- The algo's name (you should then specify whether or not it is in the leaderboard to find it faster)
* in_leaderboard: Whether or not the algo is on the leaderboard - used to make searching for an algo's id much faster
* verbose: Whether or not to print as it searches
Returns:
A string you can copy and paste into a browser to watch that game.
'''
matchIDs = get_match_ids(algo, in_leaderboard=in_leaderboard, verbose=verbose)
return [get_match_str(x) for x in matchIDs]
if __name__ == '__main__':
pass