MystBin
/e3db03909851726a2d Created 8 months ago...
Raw
main.py Hide Copy Raw
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
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
import os import discord from discord import ButtonStyle, app_commands, ui from discord.ext import tasks, commands from discord.utils import get from discord.app_commands import AppCommandError, Group import aiosqlite import time from datetime import datetime, timedelta, time import asyncio from typing import Literal, Optional import re import math import itertools import ast from dotenv import load_dotenv load_dotenv() guild_id = 768851165671850015 database = 'quotaDB.sqlite' def print_red(text): print(f"\033[1;31m{text}\033[0m") def print_green(text): print(f"\033[1;32m{text}\033[0m") class colours(): red = 0xFF0000 darkred = 0x8b0000 orange = 0xFFA500 green = 0x00FF00 darkorange = 0xDC582A mp_purple = 0xA46FFF class channel_ids(): quota_logs = 1208810891626151976 strike_logs = 1208827574998933616 senior_quota_logs = 1259211052164583425 senior_strike_logs = 1259211191650222130 class role_ids(): management = 768851165671850022 senior = 768851165671850021 intern = 1234584425547694081 staff = 796462879246909532 candidate = 768851165671850017 mvp = 1270033237049348116 class bot(commands.Bot): def __init__(self): super().__init__(command_prefix="!!", intents=discord.Intents.all(), help_command=None) self.synced = False async def setup_hook(self) -> None: await self.load_extension("commands.rewards.rewards") await self.load_extension("commands.quota.quota") # await self.load_extension("commands.intern.intern") if not self.weekly_quota_reminder.is_running(): self.weekly_quota_reminder.start() async with aiosqlite.connect(database) as db: #YYYY-MM-DD # changed all db architecture, will need to modify all code. await db.execute("""CREATE TABLE IF NOT EXISTS Weeks( StartDate TEXT PRIMARY KEY, PostRequirement INTEGER, SeniorPostRequirement INTEGER, InternPostRequirement INTEGER )""") await db.execute("""CREATE TABLE IF NOT EXISTS Inspections( ID INTEGER PRIMARY KEY, InspecteeID INTEGER, InspectorID INTEGER, PostsCompleted INTEGER, InactivityExcused INTEGER, RewardExcused INTEGER, WeekStart TEXT, Pass INTEGER, FOREIGN KEY(WeekStart) REFERENCES Weeks(StartDate) )""") await db.execute("""CREATE TABLE IF NOT EXISTS SeniorInspections( ID INTEGER PRIMARY KEY, InspecteeID INTEGER, InspectorID INTEGER, PostsCompleted INTEGER, Activity INTEGER, InactivityExcused INTEGER, RewardExcused INTEGER, WeekStart TEXT, Pass INTEGER, FOREIGN KEY(WeekStart) REFERENCES Weeks(StartDate) )""") await db.execute("""CREATE TABLE IF NOT EXISTS Strikes( ID INTEGER PRIMARY KEY, RecipientID INTEGER, SeniorID INTEGER, DateGiven TEXT )""") await db.execute("""CREATE TABLE IF NOT EXISTS Rewards( ID INTEGER PRIMARY KEY, RecipientID INTEGER, SeniorID INTEGER, DateGiven TEXT, Type TEXT, Charges INTEGER )""") await db.execute("""CREATE TABLE IF NOT EXISTS Interns( ID INTEGER PRIMARY KEY, InternID INTEGER, DateJoined TEXT, RemovalReason TEXT )""") await db.execute("""CREATE TABLE IF NOT EXISTS Quotas( key TEXT PRIMARY KEY, value TEXT )""") await db.execute("""CREATE TABLE IF NOT EXISTS Excused( StaffID INTEGER PRIMARY KEY, InspectionCount INTEGER )""") async def on_ready(self): await self.wait_until_ready() # if not self.synced: # await tree.sync(guild = discord.Object(id=guild_id)) # self.synced = True print_green(self.guilds) print_green(f"Logged in as {self.user}.") weekly_reminder_time = time(hour=12) @tasks.loop(time=weekly_reminder_time) async def weekly_quota_reminder(self): if datetime.now().weekday() == 0: guild = aclient.get_guild(guild_id) reminder_channel = get(guild.channels, id = 1173680917374578718) dt = datetime.now() - timedelta(days=7) week_start = f"{dt.year}-{dt.month}-{dt.day}" async with aiosqlite.connect(database) as db: async with db.execute("""SELECT InspectorID FROM Inspections WHERE WeekStart=? ORDER BY PostsCompleted DESC""", (week_start,)) as cursor: results1 = await cursor.fetchall() async with aiosqlite.connect(database) as db: async with db.execute("""SELECT InspectorID FROM SeniorInspections WHERE WeekStart=? ORDER BY PostsCompleted DESC""", (week_start,)) as cursor: results2 = await cursor.fetchall() loggedLoggers = [row[0] for row in results1] + [row[0] for row in results2] expectedLoggers = [m.id for m in get(guild.roles, id = role_ids.senior).members] missingLoggers = list(set(loggedLoggers).symmetric_difference(set(expectedLoggers))) if len(missingLoggers) > 0: await reminder_channel.send(f"{",".join([f'<@{ml}>' for ml in missingLoggers])}\n\nQuotas should all be in by now. Last call.") aclient = bot() tree = aclient.tree @aclient.command() @commands.guild_only() @commands.is_owner() async def sync(ctx: commands.Context, guilds: commands.Greedy[discord.Object], spec: Optional[Literal["~", "*", "^"]] = None) -> None: if not guilds: if spec == "~": synced = await ctx.bot.tree.sync(guild=ctx.guild) print(synced) elif spec == "*": ctx.bot.tree.copy_global_to(guild=ctx.guild) synced = await ctx.bot.tree.sync(guild=ctx.guild) elif spec == "^": ctx.bot.tree.clear_commands(guild=ctx.guild) await ctx.bot.tree.sync(guild=ctx.guild) synced = [] else: synced = await ctx.bot.tree.sync() await ctx.send( f"Synced {len(synced)} commands {'globally' if spec is None else 'to the current guild.'}" ) return ret = 0 for guild in guilds: try: await ctx.bot.tree.sync(guild=guild) except discord.HTTPException: pass else: ret += 1 await ctx.send(f"Synced the tree to {ret}/{len(guilds)}.") async def has_role_f(staff_member, role_id): if isinstance(staff_member, discord.Member): return role_id in [r.id for r in staff_member.roles] elif isinstance(staff_member, int): print(guild_id) print(len(aclient.guilds)) guild = aclient.get_guild(guild_id) print(guild) try: staff_member_obj = await guild.get_member(staff_member) return role_id in [r.id for r in staff_member_obj.roles] except discord.NotFound: return False async def IsManagement(staff_member): return await has_role_f(staff_member, role_ids.management) async def IsSenior(staff_member): return await has_role_f(staff_member, role_ids.senior) async def IsIntern(staff_member): return await has_role_f(staff_member, role_ids.intern) async def get_variable(key): async with aiosqlite.connect(database) as db: async with db.execute('SELECT value FROM Quotas WHERE key=?', (key,)) as cursor: result = await cursor.fetchone() return result[0] if result else None async def set_variable(key, value): async with aiosqlite.connect(database) as db: await db.execute('INSERT OR REPLACE INTO Quotas (key, value) VALUES (?, ?)', (key, value)) await db.commit() async def getQuota(): result = await get_variable("normal") return int(result) if result is not None else None async def getSeniorTicketQuota(): result = await get_variable("senior_tickets") return int(result) if result is not None else None async def getSeniorQuota(): result = await get_variable("senior") return int(result) if result is not None else None async def getInternQuota(): result = await get_variable("intern") return int(result) if result is not None else None async def CheckValidDate(date : str): return re.match(r"^20[0-9]{2}-([1-9]|1[0-2])-([1-9]|[12][0-9]|3[01])$", date) is not None async def GetQuotaHistory(staff_member : int, limit : int = 20): if not await IsSenior(staff_member): async with aiosqlite.connect(database) as db: async with db.execute("""SELECT WeekStart, Pass, PostsCompleted, InactivityExcused, RewardExcused, TicketsCompleted FROM Inspections WHERE InspecteeID = ? ORDER BY printf("%04d-%02d-%02d", substr(WeekStart, 1, instr(WeekStart, '-') - 1), substr(WeekStart, instr(WeekStart, '-') + 1, 2), substr(WeekStart, -2)) DESC LIMIT ?""", (staff_member, str(limit))) as cursor: rows = await cursor.fetchall() else: async with aiosqlite.connect(database) as db: async with db.execute("""SELECT WeekStart, Pass, PostsCompleted, InactivityExcused, RewardExcused, TicketsCompleted FROM SeniorInspections WHERE InspecteeID = ? ORDER BY printf("%04d-%02d-%02d", substr(WeekStart, 1, instr(WeekStart, '-') - 1), substr(WeekStart, instr(WeekStart, '-') + 1, 2), substr(WeekStart, -2)) DESC LIMIT ?""", (staff_member, str(limit))) as cursor: rows = await cursor.fetchall() rows.reverse() output = "```ansi\n" for row in rows: if int(row[4]) == 1: output += f"?[0;34m{row[0]} - Reward Excused - {row[2]} posts\n" elif int(row[3] == 1): output += f"?[0;33m{row[0]} - Inactivity Excused - {row[2]} posts\n" elif int(row[1]) == 1: output += f"?[0;32m{row[0]} - Pass - {row[2]} posts - {row[5]} tickets\n" else: output += f"?[0;31m{row[0]} - Fail - {row[2]} posts - {row[5]} tickets\n" output += "```" if (len(rows) == 0): output = "No Results" return output # maybe done idk async def Get_Consecutive_Strikes(staff_member : int): # only accurate if quota logs are fully up to date flat_list = [] # as of 27-1-24, this seems to work async with aiosqlite.connect(database) as db: async with db.execute("""SELECT DateGiven FROM Strikes WHERE RecipientID = ? ORDER BY printf("%04d-%02d-%02d", substr(DateGiven, 1, instr(DateGiven, '-') - 1), substr(DateGiven, instr(DateGiven, '-') + 1, 2), substr(DateGiven, -2)) DESC LIMIT 10""", (staff_member,)) as cursor: rows = await cursor.fetchall() counter = 0 for row in rows: for val in row: flat_list.append(val) for i in range(-12,-5): # checks the last complete week's monday dt = datetime.now() + timedelta(days=i) if dt.weekday() == 0: last_monday = dt for i in range(len(flat_list)): date = str(flat_list[i]).split("-") dt = datetime(int(date[0]), int(date[1]), int(date[2])) if i == 0 and dt.date() != last_monday.date(): print("Not last monday") break try: next_date = datetime(int(flat_list[i + 1].split("-")[0]), int(flat_list[i + 1].split("-")[1]), int(flat_list[i + 1].split("-")[2])) if dt <= next_date + timedelta(days=7): print("Counted") counter += 1 if i == len(flat_list) - 2 and len(flat_list) != 1: counter += 1 else: break except IndexError: print("Except") break return counter @tree.command(guild = discord.Object(id=guild_id), name = "mvp_colour", description='Choose the MVP role colour') @app_commands.describe(hex_code="Expects 6 characters representing a colour. E.g: FF13A5") @app_commands.checks.has_role(role_ids.mvp) async def mvpcolour(interaction: discord.Interaction, hex_code : str): await get(interaction.guild.roles, id = role_ids.mvp).edit(colour=discord.Colour.from_str(f"0x{hex_code}")) await interaction.response.send_message("Success!", ephemeral=True) @tree.command(guild = discord.Object(id=guild_id), name = "check_history", description='Check your own quota history!') async def getownhistory(interaction: discord.Interaction): await interaction.response.send_message(await GetQuotaHistory(interaction.user.id), ephemeral=True) @tree.command(guild = discord.Object(id=guild_id), name = "csv_role", description='Get a csv of a role') async def csv_role(interaction: discord.Interaction, role : discord.Role, splitby : int = -1): if splitby == -1: await interaction.response.send_message(",".join([str(m.id) for m in role.members]), ephemeral=True) elif splitby > 5: ids = [str(m.id) for m in role.members] output = "" temp = "" for id in ids: temp += f"{id}," if len(temp.split(",")) > splitby: output += temp output += "\n\n" temp = "" if temp != "": output += temp await interaction.response.send_message(output, ephemeral=True) @tree.command(guild = discord.Object(id=guild_id), name = "sql", description='Run SQL') async def run_sql(interaction: discord.Interaction, sql : str): if interaction.user.id == 378963670589505557: if "SELECT" == sql.split(" ")[0]: async with aiosqlite.connect(database) as db: async with db.execute(sql) as cursor: rows = await cursor.fetchall() output = "" for row in rows: output += str(row) + "\n" if len(rows) > 0: await interaction.response.send_message(output, ephemeral=True) else: await interaction.response.send_message("Fetch result was none", ephemeral=True) return else: async with aiosqlite.connect(database).cursor() as cursor: await cursor.execute(sql) affectedNo = cursor.rowcount await cursor.commit() await interaction.response.send_message(f"Done! - Change impacted `{affectedNo}` rows", ephemeral=True) return else: await interaction.response.send_message("not for you go away!", ephemeral=True) @tree.command(guild = discord.Object(id=guild_id), name = "make_groups", description='Sorts interns into sr timezone groups') @app_commands.checks.has_role(role_ids.management) async def make_intern_groups(interaction: discord.Interaction, copyable : bool = False, csv_groups : bool = False): interns = [] leaders = [] candidate_role = get(interaction.guild.roles, id = role_ids.candidate) for m in candidate_role.members: if m.nick != None: if " | " in m.nick: nickname_split = m.nick.split(" | ") timezone_nickname = nickname_split[1] timezone_number = "" if "+" in timezone_nickname: timezone_number = timezone_nickname.split("+")[1] elif "-" in timezone_nickname: timezone_number = timezone_nickname.split("-")[1] if timezone_number == "5:30": timezone_number = 5.5 float(timezone_number) inter_array = [str(m.id), timezone_number] interns.append(inter_array) coach_role = get(interaction.guild.roles, id = 1229906177203372065) for m in coach_role.members: if m.nick != None: if " | " in m.nick: nickname_split = m.nick.split(" | ") timezone_nickname = nickname_split[1] timezone_number = "" if "+" in timezone_nickname: timezone_number = timezone_nickname.split("+")[1] elif "-" in timezone_nickname: timezone_number = timezone_nickname.split("-")[1] if timezone_number == "5:30": timezone_number = 5.5 float(timezone_number) leader_array = [str(m.id), timezone_number, []] leaders.append(leader_array) # print(leaders, interns) # ahmood. | GMT+3 #eaders = [["dav", 1, []], ["red", 10 , []], ["knight", 5.3, []], ["deep", 0, []], ["picture", -6, []]] #interns = [["Wezza", 3], ["lillyx", 0], ["ahmood", 3], ["6b", 1], ["Kmdq", 0], ["abluety", -4], ["Helix", 2], ["Synthe", -5], ["Keegan", -5], ["cap", -4], #["ZizzleWizard", -4], ["Bored", -5], ["Pocopotato", -7], ["carisoul", -7], ["okcrystal", +2], ["invanthegreat01", -4], ["jinxisfly", -5], ["Maximoose.7", -6], #["Rafael" , 1], ["Yoshi", 1], ["Potata" , 5.30], ["nzl" , 5.30], ["qvjk" , 8], ["FrostChain" , 8], ["Abdiel" , 8], ["Mamba" , -6], ["knnni" , -5]] sorted_pairs = [] def sort_interns(leaders: list, interns: list): leader_count = 0 amount_of_interns = len(interns) sorted_pairs = {} # Initialize as empty dictionary # removes any possible empty lists leaders = [i for i in leaders if i != []] interns = [j for j in interns if j != []] while len(interns) > 0: if len(leaders) == leader_count: leader_count = 0 leader_now = leaders[leader_count] best_intern_tz_dif = 100 best_intern = None # Initialize as None C = 25 # --12 + 12 + 1 (-gmt-12 + gmt+12 + 1) for intern in interns: distance_1 = int(leader_now[1]) - int(intern[1]) distance_2 = int(intern[1]) - int(leader_now[1]) D_1 = distance_1 if distance_1 >= 0 else distance_1 + C D_2 = distance_2 if distance_2 >= 0 else distance_2 + C actual_distance = min(D_1, D_2) if actual_distance < best_intern_tz_dif: best_intern_tz_dif = actual_distance best_intern = intern if actual_distance == 0: break if best_intern: # Check that best_intern is not None if leader_now[0] not in sorted_pairs: sorted_pairs[leader_now[0]] = [] sorted_pairs[leader_now[0]].append(best_intern) interns.remove(best_intern) leader_count += 1 return sorted_pairs sp = sort_interns(leaders, interns) output = "" for p in sp.keys(): output += f"<@{p}>**'s Group:**\n" for p2 in sp[p]: output += f"> <@{p2[0]}>\n" output += "\n\n" if copyable: await interaction.response.send_message(f"```\n{output}```") if csv_groups: output = "" for p in sp.keys(): output += f"<@{p}>: " for p2 in sp[p]: output += f"{p2[0]}," output += "\n\n" await interaction.response.send_message(output) else: embed = discord.Embed( color = colours.mp_purple, description = output, title = "Intern groupings" ) await interaction.response.send_message(embed=embed) # print(sort_interns(leaders, interns)) @tree.command(guild = discord.Object(id=guild_id), name = "view_all_history", description='View everyones quota history') @app_commands.checks.has_role(role_ids.management) async def view_all_history(interaction: discord.Interaction): await interaction.response.defer(thinking=True, ephemeral=True) msg = "" counter = 0 for id in [m.id for m in get(interaction.guild.roles, id = role_ids.staff).members]: msg += f"<@{id}>\n\n{await GetQuotaHistory(id, 10)}\n\n" counter += 1 if counter % 3 == 0: await interaction.user.send(msg) msg = "" await interaction.followup.send(f"Sent you all {counter} quota histories!", ephemeral=True) @tree.command(guild = discord.Object(id=guild_id), name = "get_date", description='Get the dates of the next inspection period') async def get_date(interaction: discord.Interaction): mondays = [] for i in range(-9,0): dt = datetime.now() + timedelta(days=i) if dt.weekday() == 0: mondays.append(f"{dt.month}/{dt.day}/{dt.year}|{abs(i)}") output = "" for m in mondays: date = str(m).split("|")[0] i = str(m).split("|")[1] output += f"`{date}` was `{i}` days ago\n" await interaction.response.send_message(f"{output}", ephemeral=True) def parse_timezone(name): timezone = name.split(" | GMT")[1] return int(timezone) def insert_returns(body): if isinstance(body[-1], ast.Expr): body[-1] = ast.Return(body[-1].value) ast.fix_missing_locations(body[-1]) if isinstance(body[-1], ast.If): insert_returns(body[-1].body) insert_returns(body[-1].orelse) if isinstance(body[-1], ast.With): insert_returns(body[-1].body) @tree.command(guild = discord.Object(id=guild_id), name="eval", description="Eval something") async def eval_py(interaction : discord.Interaction, cmd : str, ephemeral : bool = True): if interaction.user.id == 378963670589505557: fn_name = "_eval_expr" # wrap in async def body body = f"async def {fn_name}():\n\t{cmd}" parsed = ast.parse(body) body = parsed.body[0].body insert_returns(body) env = { 'bot': aclient, 'discord': discord, 'interaction': interaction, '__import__': __import__ } exec(compile(parsed, filename="<ast>", mode="exec"), env) result = (await eval(f"{fn_name}()", env)) if len(result) == 0: result = "No return value" await interaction.response.send_message(result, ephemeral=ephemeral) else: await interaction.response.send_message("YOU ARENT ME!!!", ephemeral=True) @aclient.event async def on_app_command_completion(interaction : discord.Interaction, command : app_commands.Command): print_red(f"{interaction.user.name} ({interaction.user.id}) Used command {command.name}") @tree.error async def on_app_command_error(interaction : discord.Interaction, error : AppCommandError): print_red(error) if isinstance(error, app_commands.MissingRole) or isinstance(error, app_commands.MissingAnyRole): await interaction.response.send_message("You're missing a role!", ephemeral=True) elif isinstance(error, app_commands.CommandOnCooldown): await interaction.response.send_message(f"You're on cooldown for another `{int(error.retry_after)}` seconds!", ephemeral=True) elif isinstance(error, app_commands.MissingPermissions): await interaction.response.send_message("You're missing a permission!", ephemeral=True) if __name__ == '__main__': aclient.run(f"{os.getenv('token')}")
quota.py Hide Copy Raw
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
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
from ..imports import * class parse_data_modal(ui.Modal, title = 'Data parser'): def __init__(self): super().__init__() data = ui.TextInput(label = 'Data', style = discord.TextStyle.paragraph, required = True) async def on_submit(self, interaction: discord.Interaction) -> None: splitData = self.data.value.split('\n') quotaDict = {} for i in range(0, len(splitData), 7): quotaDict.update({f"{splitData[i]}" : [int(splitData[i+4].split(': ')[1]), int(splitData[i+5].split(': ')[1])]}) output = "" for name in quotaDict: output += f"`/quota log staff_member:{name} post_count:{quotaDict[name][0]} ticket_count:{quotaDict[name][1]} week_start: `\n" await interaction.response.send_message(output, ephemeral=True) class quota(commands.GroupCog, group_name='quota', group_description='Manage quotas'): def __init__(self, bot): self.bot = bot @app_commands.command(name = "get_date", description='Get the dates of the next inspection period') async def get_date(self, interaction: discord.Interaction): mondays = [] for i in range(-9,0): dt = datetime.now() + timedelta(days=i) if dt.weekday() == 0: mondays.append(f"{dt.month}/{dt.day}/{dt.year}|{abs(i)}") output = "" for m in mondays: date = str(m).split("|")[0] i = str(m).split("|")[1] output += f"`{date}` was `{i}` days ago\n" await interaction.response.send_message(f"{output}", ephemeral=True) @app_commands.command(name = "parsedata", description='Parse data') async def parseData(self, interaction: discord.Interaction): await interaction.response.send_modal(parse_data_modal()) @app_commands.command(name = "set", description='Set a quota') @app_commands.checks.has_role(role_ids.management) async def set_quota(self, interaction: discord.Interaction, role : Literal["intern", "normal", "senior"], value : int): prev = await get_variable(role) await set_variable(role, value) await interaction.response.send_message(f"Changed quota for `{role}` from `{prev}` to `{value}`", ephemeral=True) @app_commands.command(name = "inactivity_add", description='Add a user to inactivity') @app_commands.checks.has_role(role_ids.management) async def inactivity_add(self, interaction: discord.Interaction, staff_member : discord.Member, inspection_count : int): async with aiosqlite.connect(database) as db: await db.execute('INSERT OR REPLACE INTO Excused (StaffID, InspectionCount) VALUES (?, ?)', (staff_member.id, inspection_count)) await db.commit() await interaction.response.send_message("Successfully added user!", ephemeral=True) @app_commands.command(name = "inactivity_view", description='View all active inactivity notices.') @app_commands.checks.has_role(role_ids.management) async def inactivity_view(self, interaction: discord.Interaction): async with aiosqlite.connect(database) as db: async with db.execute('SELECT StaffID, InspectionCount FROM Excused WHERE InspectionCount > 0') as cursor: results = await cursor.fetchall() output = f"Total: `{len(results)}`\n\n" for row in results: output += f"- <@{row[0]}> - `{row[1]}`\n" await interaction.response.send_message(embed=discord.Embed(title = f"Current Inactivity Notices", description=output, colour=colours.mp_purple), ephemeral=True) @app_commands.command(name = "log", description='Log a quota for an individual') @app_commands.describe(week_start="Format: YYYY-MM-DD | Must use Monday of week", activity="Senior Only", override_excused="Use to override excused", apply_rewards="Default: True", auto_strike="Default: True", override_existing="Default: False", dm_user="Default: True") async def logQuota(self, interaction: discord.Interaction, staff_member : discord.Member, post_count : int, ticket_count : int, week_start : str, activity : bool = None, override_excused : bool = False, apply_rewards : bool = True, auto_strike : bool = True, override_existing : bool = False, dm_user : bool = True): await interaction.response.defer(thinking=True, ephemeral=True) # all wrong to do with senior quota (post count) reward_excused = False striked = False Is_Senior = await IsSenior(staff_member) # work out the target users quota requirement requirement = 0 ticketrequirement = 0 if Is_Senior: requirement = await getSeniorQuota() ticketrequirement = await getSeniorTicketQuota() elif await IsIntern(staff_member): requirement = await getInternQuota() else: requirement = await getQuota() # check if inspector is a senior if not await IsSenior(interaction.user): await interaction.followup.send("Only seniors can do this >:(", ephemeral=True) return #ensure valid date if not await CheckValidDate(week_start): await interaction.followup.send("Please enter a valid date", ephemeral=True) return # checks if theyre a senior but the activity param is empty (somethings wrong) if Is_Senior and activity == None: await interaction.followup.send("The user you are logging is a senior - You need to fill in the activity parameter", ephemeral=True) return # checks if someone is trying to record activity for a non-senior if not Is_Senior and activity != None: await interaction.followup.send("Do not log activity for a non-senior!", ephemeral=True) return # ensure users quota hasnt already been recorded for that week existing_quota = None if not Is_Senior: async with aiosqlite.connect(database) as db: async with db.execute('SELECT InspecteeID FROM Inspections WHERE WeekStart=? AND InspecteeID=?', (week_start,staff_member.id)) as cursor: existing_quota = await cursor.fetchone() else: async with aiosqlite.connect(database) as db: async with db.execute('SELECT InspecteeID FROM SeniorInspections WHERE WeekStart=? AND InspecteeID=?', (week_start,staff_member.id)) as cursor: existing_quota = await cursor.fetchone() if existing_quota != None and not override_existing: await interaction.followup.send(f"This user already has a quota recorded for this week (`{week_start}`)", ephemeral=True) return # Excused excused = False if not override_excused and post_count < requirement: async with aiosqlite.connect(database) as db: async with db.execute('SELECT StaffID FROM Excused WHERE InspectionCount > 0 AND StaffID = ?', (staff_member.id,)) as cursor: row = await cursor.fetchone() if row is not None: excused = True await db.execute('UPDATE Excused SET InspectionCount = InspectionCount - 1 WHERE StaffID = ?', (staff_member.id,)) await db.commit() elif override_excused: excused = True # REWARDS if apply_rewards and not excused and post_count < requirement and not Is_Senior: async with aiosqlite.connect(database) as db: # get rewards async with db.execute('SELECT ID, Type, DateGiven, Charges FROM Rewards WHERE RecipientID=? AND Charges > 0', (staff_member.id,)) as cursor: results = await cursor.fetchall() valid_rewards = [] for r in results: date = str(r[2]).split("-") dt = datetime(int(date[0]), int(date[1]), int(date[2])) if dt > datetime.now() - timedelta(days=31): valid_rewards.append(r) if len(valid_rewards) > 0: # consume reward if applicable for i in range(2): for reward in valid_rewards: # 2 ifs so if half doesnt make them pass and they have an excused, then the excused will activate if i == 0: # check all the halfs first (less valuable) if "Half" in reward[1]: if post_count >= (requirement * 0.5): async with aiosqlite.connect(database) as db: await db.execute('UPDATE Rewards SET Charges=? WHERE RecipientID=? AND Charges > 0 AND ID=? AND Type=?', (reward[3] - 1, staff_member.id, r[0], "Quota Half")) await db.commit() reward_excused = True break if i == 1: if "Excused" in reward[1]: async with aiosqlite.connect(database) as db: await db.execute('UPDATE Rewards SET Charges=? WHERE RecipientID=? AND Charges > 0 AND ID=? AND Type=?', (reward[3] - 1, staff_member.id, r[0], "Quota Excused")) await db.commit() reward_excused = True break # STRIKES if auto_strike and not excused and not reward_excused: if not Is_Senior: if post_count < requirement: striked = True async with aiosqlite.connect(database) as db: await db.execute('INSERT INTO Strikes (RecipientID, SeniorID, DateGiven) VALUES (?, ?, ?)', (staff_member.id, interaction.user.id, week_start)) await db.commit() else: if post_count < requirement and not activity: striked = True async with aiosqlite.connect(database) as db: await db.execute('INSERT INTO Strikes (RecipientID, SeniorID, DateGiven) VALUES (?, ?, ?)', (staff_member.id, interaction.user.id, week_start)) await db.commit() # FINAL SQL async with aiosqlite.connect(database) as db: async with db.execute('SELECT StartDate FROM Weeks WHERE StartDate=?', (week_start,)) as cursor: existing_week = await cursor.fetchone() if existing_week is None: await db.execute('INSERT INTO Weeks (StartDate, PostRequirement, SeniorPostRequirement, InternPostRequirement) VALUES (?, ?, ?, ?)', (week_start, await getQuota(), await getSeniorQuota(), await getInternQuota())) await db.commit() if not Is_Senior: await db.execute('INSERT OR REPLACE INTO Inspections (InspecteeID, InspectorID, PostsCompleted, WeekStart, InactivityExcused, RewardExcused, Pass, TicketsCompleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', (staff_member.id, interaction.user.id, post_count, week_start, int(excused), int(reward_excused), int(post_count >= requirement or int(excused) or int(reward_excused)), ticket_count)) else: await db.execute('INSERT OR REPLACE INTO SeniorInspections (InspecteeID, InspectorID, PostsCompleted, Activity, WeekStart, InactivityExcused, RewardExcused, Pass, TicketsCompleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', (staff_member.id, interaction.user.id, post_count, int(activity), week_start, int(excused), int(reward_excused), int(post_count >= requirement and ticket_count >= ticketrequirement and activity or excused or reward_excused), ticket_count)) await db.commit() logchannel = get(interaction.guild.channels, id=channel_ids.quota_logs) strikelogchannel = get(interaction.guild.channels, id=channel_ids.strike_logs) if Is_Senior: logchannel = get(interaction.guild.channels, id=channel_ids.senior_quota_logs) strikelogchannel = get(interaction.guild.channels, id=channel_ids.senior_strike_logss) logmsg = f"### {interaction.user.mention} logged {staff_member.mention}'s quota.\n{GetQuotaHistory(staff_member.id, 1)}" logmsgsent = await logchannel.send(logmsg) finalmsg = f"Done! - Quota for {staff_member.mention} has been logged successfully." if override_existing: finalmsg += f"\nIf this user already had a quota recorded, it has been overridden!\n**Please do the following:**\n- Delete the old log in <#{channel_ids.quota_logs}>\n- Remove any old strikes the user may have gotten (if the old quota recorded as a fail)\n- Replenish any rewards mistakenly consumed by this action" if striked: finalmsg += "\n- The user was striked" if reward_excused: finalmsg += "\n- The user was excused by an active reward" # STRIKE CHECK strike_streak = await Get_Consecutive_Strikes(staff_member.id) if strike_streak > 0: finalmsg += f"\n\nPlease note that this user's current consecutive strike streak is now `{strike_streak}`, any actions that need to be taken based on this information are not automated." if striked: await strikelogchannel.send(f"{staff_member.mention} [was striked]({logmsgsent.jump_url})\n\nQuota History:\n{await GetQuotaHistory(staff_member.id)}") dm_msg = f"# <:MP:1173683497697808424> | Weekly Inspection Notice\n### {interaction.user.mention} has logged your quota for the week beginning {week_start}\n- Posts: {post_count}" if Is_Senior: dm_msg += f"\n- Activity: {activity}" dm_msg += f"\n\nYour recent quota history:\n{await GetQuotaHistory(staff_member.id, 5)}" if dm_user: await staff_member.send(dm_msg) await interaction.followup.send(finalmsg) @app_commands.command(name = "check_week", description='View information about a specific week') @app_commands.describe(week_start="Format: YYYY-MM-DD | Must use Monday of week") async def viewWeek(self, interaction: discord.Interaction, week_start : str): await interaction.response.defer(thinking=True, ephemeral=True) async with aiosqlite.connect(database) as db: async with db.execute("""SELECT InspecteeID, PostsCompleted, InspectorID, TicketsCompleted FROM Inspections WHERE WeekStart=? ORDER BY PostsCompleted DESC""", (week_start,)) as cursor: results1 = await cursor.fetchall() async with aiosqlite.connect(database) as db: async with db.execute("""SELECT InspecteeID, PostsCompleted, InspectorID, TicketsCompleted FROM SeniorInspections WHERE WeekStart=? ORDER BY PostsCompleted DESC""", (week_start,)) as cursor: results2 = await cursor.fetchall() results = sorted(results1+results2, key=lambda x: x[1], reverse=True) output = "" loggedLoggers = [] # ids loggedStaff = [] # list of ids expectedStaff = [m.id for m in get(interaction.guild.roles, id = role_ids.staff).members + get(interaction.guild.roles, id = role_ids.intern).members] expectedLoggers = [m.id for m in get(interaction.guild.roles, id = role_ids.senior).members] totalPosts = 0 totalTickets = 0 for i in range(0, len(results)): postsCompleted = results[i][1] if results[i][1] is not None else 0 ticketsCompleted = results[i][3] if results[i][3] is not None else 0 inspectorID = results[i][2] inspecteeID = results[i][0] totalPosts += postsCompleted totalTickets += ticketsCompleted loggedLoggers.append(inspectorID) loggedStaff.append(inspecteeID) output += f"- <@{inspecteeID}>: `{postsCompleted}` | `{ticketsCompleted}`\n" missingLoggers = list(set(loggedLoggers).symmetric_difference(set(expectedLoggers))) missingstaff = list(set(loggedStaff).symmetric_difference(set(expectedStaff))) output2 = "" if len(missingstaff) > 0: for id in missingstaff: output2 += f"<@{id}>, " else: output2 = "Nobody missing!" output3 = "" if len(missingLoggers) > 0: for id in missingLoggers: output3 += f"<@{id}>, " else: output3 = "Nobody missing!" await interaction.followup.send(embeds=[discord.Embed(title = "Results", description=f"Total Posts: `{totalPosts}`\nTotal Tickets: `{totalTickets}`\n\n**USERNAME: POSTS | TICKETS**\n{output}", colour=colours.mp_purple), discord.Embed(title = "Missing Users", description=output2, colour=colours.mp_purple), discord.Embed(title = "Missing Loggers", description=output3, colour=colours.mp_purple)], ephemeral = True) @app_commands.command(name = "get_history", description='Get a users most recent weeks of quota history') async def gethistory(self, interaction: discord.Interaction, staff_member : discord.Member): await interaction.response.send_message(await GetQuotaHistory(staff_member.id), ephemeral=True) @app_commands.command(name = "mvp", description='Get the mvp list for a week') @app_commands.describe(week_start="Format: YYYY-MM-DD | Must use Monday of week") async def getmvp(self, interaction: discord.Interaction, week_start : str, post_threshold : int, ticket_threshold : int, form_announcement : bool = False, give_role : bool = False): await interaction.response.defer(thinking=True, ephemeral=True) async with aiosqlite.connect(database) as db: async with db.execute("""SELECT InspecteeID, PostsCompleted FROM Inspections WHERE WeekStart=? AND PostsCompleted > ? ORDER BY PostsCompleted DESC""", (week_start, post_threshold)) as cursor: results1 = await cursor.fetchall() async with aiosqlite.connect(database) as db: async with db.execute("""SELECT InspecteeID, PostsCompleted FROM SeniorInspections WHERE WeekStart=? AND PostsCompleted > ? ORDER BY PostsCompleted DESC""", (week_start, post_threshold)) as cursor: results2 = await cursor.fetchall() async with aiosqlite.connect(database) as db: async with db.execute("""SELECT InspecteeID, TicketsCompleted FROM Inspections WHERE WeekStart=? AND TicketsCompleted > ? ORDER BY TicketsCompleted DESC""", (week_start, ticket_threshold)) as cursor: results3 = await cursor.fetchall() async with aiosqlite.connect(database) as db: async with db.execute("""SELECT InspecteeID, TicketsCompleted FROM SeniorInspections WHERE WeekStart=? AND TicketsCompleted > ? ORDER BY TicketsCompleted DESC""", (week_start, ticket_threshold)) as cursor: results4 = await cursor.fetchall() postresults = sorted(results1+results2, key=lambda x: x[1], reverse=True) ticketresults = sorted(results3+results4, key=lambda x: x[1], reverse=True) output = "" for i in range(0, len(postresults)): if i == 0: output += f"## :CH_Diamond_Shiny: - <@{postresults[i][0]}> - {postresults[i][1]} posts\n" else: output += f"\n### :Crown2Silver: - <@{postresults[i][0]}> - {postresults[i][1]} posts" for i in range(0, len(ticketresults)): if i == 0: output += f"\n\n## :CH_Diamond_Shiny: - <@{ticketresults[i][0]}> - {ticketresults[i][1]} tickets\n" else: output += f"\n### :Crown2Silver: - <@{ticketresults[i][0]}> - {ticketresults[i][1]} tickets" if give_role: mvp_role = get(interaction.guild.roles, id=role_ids.mvp) for user in mvp_role.members: await user.remove_roles(mvp_role) await get(interaction.guild.members, id = int(postresults[0][0])).add_roles(mvp_role) await get(interaction.guild.members, id = int(ticketresults[0][0])).add_roles(mvp_role) if form_announcement: await interaction.followup.send(f"```\n# <@&796462879246909532> Weekly Notice - {week_start.replace("-", "/")}\n\n{output}\n\n\nSigned,\n### :MLeader: | *deepforce123*\n```", ephemeral=True) else: await interaction.followup.send(f"```\n{output}\n```", ephemeral=True) @logQuota.autocomplete('week_start') async def autocomplete_callback(self, interaction: discord.Interaction, current: str): choicelist = [] for i in range(-9,0): dt = datetime.now() + timedelta(days=i) if dt.weekday() == 0: choicelist.append(app_commands.Choice(name = f'{dt.year}-{dt.month}-{dt.day}', value = f'{dt.year}-{dt.month}-{dt.day}')) return choicelist @viewWeek.autocomplete('week_start') @getmvp.autocomplete('week_start') async def autocomplete_callback(self, interaction: discord.Interaction, current: str): choicelist = [] for i in range(-61,1): dt = datetime.now() + timedelta(days=i) if dt.weekday() == 0: choicelist.append(app_commands.Choice(name = f'{dt.year}-{dt.month}-{dt.day}', value = f'{dt.year}-{dt.month}-{dt.day}')) return choicelist async def setup(bot): await bot.add_cog(quota(bot))