-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmatch-result-posting.py
262 lines (230 loc) · 13.6 KB
/
match-result-posting.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
import re
import discord
from discord.ext import commands
from discord.ext.commands.converter import MessageConverter
from discord.ext.commands.errors import MessageNotFound
from gspread.exceptions import APIError
from utility_funcs import get_setting, is_channel, request, res_cog, url_to_id
class MatchResultPostingCog(commands.Cog):
delete_delay = 10
match_id_format = '[0-9]+'
trigger = re.compile(f'^!{match_id_format}$', re.IGNORECASE)
userID_username_cache = {}
bmapID_json_cache = {}
username_flag_cache = {}
def __init__(self, bot):
self.bot = bot
@commands.Cog.listener()
async def on_message(self, message):
if message.author == self.bot.user:
return
# if message.channel.name in ['bot']:
if message.channel.name in ['match-results', 'referee']:
if re.match(self.trigger, message.content):
async with message.channel.typing():
await message.delete()
await self.post_result(message, message.content.lstrip('!').upper())
@commands.command(aliases=['del', 'undo'])
@is_channel('match-results')
async def delete(self, ctx):
await ctx.message.delete()
async for message in ctx.history():
if message.author == self.bot.user:
await message.delete()
break
@commands.command()
@is_channel('referee', 'match-results')
async def edit(self, ctx, match_id, oldmessage):
await ctx.message.delete()
# Find the message to edit
try:
oldmessage = await MessageConverter().convert(ctx, oldmessage)
except MessageNotFound:
await ctx.send("Couldn't find a message with what you provided.", delete_after=self.delete_delay)
return
# Check if the bot can edit the message
if oldmessage.author != self.bot.user:
await ctx.send(f'{ctx.author.mention} I didn\'t post that message. I can\'t edit it.', delete_after=self.delete_delay)
return
# Check if match_id is valid
if not re.match(self.match_id_format, match_id):
await ctx.send(f'{ctx.author.mention} That isn\'t a valid match id.', delete_after=self.delete_delay)
return
await self.post_result(ctx.message, match_id.upper(), oldmessage)
async def post_result(self, message, match_id, oldmessage=None):
async with message.channel.typing():
setts = get_setting('match-result-posting')
referees = setts['referees']
setts = setts['exposed_settings']
apiKey = get_setting('osu', 'apikey')
# Get spreadsheet from google sheets
agc = await res_cog(self.bot).agc()
try:
sh = await agc.open_by_url(setts["sheet_url"])
except APIError as e:
if e.args[0]['status'] == 'PERMISSION_DENIED':
await message.channel.send(f'{message.author.mention} I don\'t have permission to view that sheet. Share it with `[email protected]` to give me access.', delete_after=self.delete_delay*2)
return
ws = await sh.worksheet(match_id)
# Get lobby id from sheet
try:
urlcell = await ws.acell('G4')
lobby_id = url_to_id(urlcell.value)
except (SyntaxError, IndexError):
await message.channel.send(f'{message.author.mention} Couldn\'t find a valid mp link on the sheet for match: {match_id}', delete_after=self.delete_delay)
return
# Get lobby info with osu api
lobbyjson = await request(f'https://osu.ppy.sh/api/get_match?k={apiKey}&mp={lobby_id}', self.bot)
if lobbyjson['match'] == 0:
await message.channel.send(f'{message.author.mention} Mp link (https://osu.ppy.sh/mp/{lobby_id}) returned no results for match: {match_id}', delete_after=self.delete_delay)
return
# elif lobbyjson['match']['end_time'] is None:
# await message.channel.send(f'{message.author.mention} Mp link (https://osu.ppy.sh/mp/{lobby_id}) looks to be incomplete. Use !mp close', delete_after=self.delete_delay)
# return
# Get the values of all exposed settings except the first 4 which aren't sheet references. Refer to settings_template.json.
sheet_references = list(setts.values())[4:]
data = await ws.batch_get(sheet_references)
# Unwrap the double nested list that is returned but keep empty cells.
data = [i[0][0] if i != [] else i for i in data]
# , 'ban2': data[4][0:3], 'roll': data[6]
# , 'ban2': data[10][0:3], 'roll': data[12]
p1 = {'username': data[1], 'score': data[2], 'ban1': data[3][0:3], 'ban2': data[4][0:3], 'protect1': data[6][0:3]}
p2 = {'username': data[8], 'score': data[9], 'ban1': data[10][0:3], 'ban2': data[11][0:3], 'protect1': data[13][0:3]}
if [] in p1.values() or [] in p2.values():
await message.channel.send(f'{message.author.mention} Sheet is missing one or all of player\'s '
f'usernames, scores, bans or rolls.', delete_after=self.delete_delay)
return
# Used to line up the scores horizontally by left justifying the username to this amount
longest_name_len = len(max([p1['username'], p2['username']], key=len))
# Highlight who the winner was using bold and an emoji
if p1['score'] > p2['score']:
p1['score'] = f'**{p1["score"]}** :trophy:'
elif p1['score'] < p2['score']:
p2['score'] = f'**{p2["score"]}** :trophy:'
# Retreive player's flags from api or cache
# Duplicate code, I know
if p1['username'] not in self.username_flag_cache.keys():
json = await request(f'https://osu.ppy.sh/api/get_user?k={apiKey}&u={p1["username"]}&m=0&type=string', self.bot)
flag = json[0]['country'].lower()
self.username_flag_cache[p1['username']] = flag
else:
flag = self.username_flag_cache[p1['username']]
p1['flag'] = flag
if p2['username'] not in self.username_flag_cache.keys():
json = await request(f'https://osu.ppy.sh/api/get_user?k={apiKey}&u={p2["username"]}&m=0&type=string', self.bot)
flag = json[0]['country'].lower()
self.username_flag_cache[p2['username']] = flag
else:
flag = self.username_flag_cache[p2['username']]
p2['flag'] = flag
# Get TB bans, but only if they are present
p1_tb_ban = data[5][0:3]
p2_tb_ban = data[11][0:3]
# Check if either cell is empty. Represented by either "TB0" or [].
if any(x in [p1_tb_ban, p2_tb_ban] for x in ['TB0', []]):
p1_tb_ban = p2_tb_ban = ''
else:
p1_tb_ban = ', ' + p1_tb_ban
p2_tb_ban = ', ' + p2_tb_ban
# Construct the embed
description = (f':flag_{p1["flag"]}: `{p1["username"].ljust(longest_name_len)} -` {p1["score"]}\n'
f'Bans: {p1["ban1"]}, {p1["ban2"]} - Protects: {p1["protect1"]}\n'
f':flag_{p2["flag"]}: `{p2["username"].ljust(longest_name_len)} -` {p2["score"]}\n'
f'Bans: {p2["ban1"]}, {p2["ban2"]} - Protects: {p2["protect1"]}')
embed = discord.Embed(title=f'Match ID: {match_id}', description=description, color=0xe47607)
embed.set_author(name=f'{setts["tourney_round"]}: ({p1["username"]}) vs ({p2["username"]})',
url=f'https://osu.ppy.sh/mp/{lobby_id}')
# Add streamer and referee to footer
ws = await sh.worksheet(setts["sheet_tab_name"])
schedule_batch = (await ws.batch_get(['B5:I500']))[0]
referee = ''
streamer = ''
reporter = message.author.display_name
for row in schedule_batch:
if row[0] == match_id:
try:
referee = row[6]
streamer = row[7]
except IndexError:
pass
break
footer = f'Refereed by {referee}' if referee else f'Reported by {reporter}'
footer += f' - Streamed by {streamer}' if streamer else ''
embed.set_footer(text=footer)
# Construct the fields within the embed, displaying each pick and score differences
firstpick = data[0]
if firstpick not in [p1['username'], p2['username']]:
await message.channel.send(f'{message.author.mention} Failed to find who picked first by looking at the'
f'sheet for match: {match_id}', delete_after=self.delete_delay)
return
orange = ':small_orange_diamond:'
blue = ':small_blue_diamond:'
tiebreaker = ':diamond_shape_with_a_dot_inside:'
# Get the mappool
ws = await sh.worksheet('Mappool')
poolRound = setts['pool_round']
cells = f'D{3+25*poolRound}:F{2+25*(poolRound+1)}'
poolbatch = (await ws.batch_get([cells]))[0]
pool = {}
for row in poolbatch:
pool[int(row[2])] = row[0]
# Only look at games that used a beatmap from the mappool and were not aborted
filteredgames = [game for game in lobbyjson['games'] if game['end_time'] is not None and int(game['beatmap_id']) in pool]
for i, game in enumerate(filteredgames):
emote = orange if i % 2 == 0 else blue
# Alternate players starting from whoever the sheet says had first pick
picker = p1['username'] if (i % 2 == 0 if firstpick == p1['username'] else i % 2 != 0) else p2['username']
bmapID = int(game['beatmap_id'])
# Retreive beatmap information from osu api or cache
if bmapID not in self.bmapID_json_cache.keys():
bmapJson = await request(f'https://osu.ppy.sh/api/get_beatmaps?k={apiKey}&b={bmapID}', self.bot)
bmapJson = bmapJson[0]
self.bmapID_json_cache[bmapID] = bmapJson
else:
bmapJson = self.bmapID_json_cache[bmapID]
bmapFormatted = f"{bmapJson['artist']} - {bmapJson['title']} [{bmapJson['version']}]"
# Filter out scores made by referees
scores = [score for score in game['scores'] if int(score['user_id']) not in referees]
scores.sort(key=lambda score: int(score['score']), reverse=True)
# Retreive winner's username from osu api or cache
if scores[0]['user_id'] not in self.userID_username_cache.keys():
winnerjson = await request(f'https://osu.ppy.sh/api/get_user?k={apiKey}&u={scores[0]["user_id"]}', self.bot)
winner = winnerjson[0]['username']
self.userID_username_cache[scores[0]['user_id']] = winner
else:
winner = self.userID_username_cache[scores[0]['user_id']]
# Check if map was tiebreaker
if pool[bmapID].startswith('TB'):
firstline = f'{tiebreaker} **Tiebreaker** [{pool[bmapID]}]'
else:
firstline = f'{emote}Pick #{i+1} by __{picker}__ [{pool[bmapID]}]'
# One or both players didn't play a map
if len(scores) < 2:
# await message.channel.send(f'{message.author.mention} It looks like a score is missing in the {infeng.ordinal(i+1)} mappool map for match: {match_id}', delete_after=self.delete_delay)
embed.add_field(name=firstline,
value=f'[{bmapFormatted}](https://osu.ppy.sh/b/{bmapID})\n'
f'__{winner} ({int(scores[0]["score"]):,})__ wins. Other score missing.', inline=False)
else:
embed.add_field(name=firstline,
value=f'[{bmapFormatted}](https://osu.ppy.sh/b/{bmapID})\n'
f'__{winner} ({int(scores[0]["score"]):,})__ wins by **({int(scores[0]["score"])-int(scores[1]["score"]):,})**', inline=False)
# Try to find result channel for this server
resultchannels = [c for c in message.guild.channels if c.name == 'match-results']
if len(resultchannels) < 1:
await message.channel.send(f'{message.author.mention} Couldn\'t find a channel named `match-results` in this server to post result to', delete_after=self.delete_delay)
return
for channel in resultchannels:
try:
if oldmessage is None:
await channel.send(embed=embed)
else:
await oldmessage.edit(content='', embed=embed)
if message.channel.name != 'match-results':
await message.channel.send(f'{message.author.mention} Done', delete_after=self.delete_delay)
return
except Exception:
continue
else:
await message.channel.send(f'{message.author.mention} Couldn\'t post to the results channel. ping diony', delete_after=self.delete_delay)
async def setup(bot):
await bot.add_cog(MatchResultPostingCog(bot))