-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathAnnounce.py
More file actions
405 lines (363 loc) · 15.3 KB
/
Copy pathAnnounce.py
File metadata and controls
405 lines (363 loc) · 15.3 KB
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
#!/usr/bin/env python
"""
Koala Bot Announce feature
Created by: Bill Cao
"""
# Built-in/Generic Imports
import math
# Libs
import discord
from discord.ext import commands
from utils.KoalaUtils import extract_id, wait_for_message
from utils import KoalaColours
import time
# Own modules
import KoalaBot
# constants
ANNOUNCE_SEPARATION_DAYS = 30
SECONDS_IN_A_DAY = 86400
TIMEOUT_TIME = 60.0
MAX_MESSAGE_LENGTH = 2000
def announce_is_enabled(ctx):
"""
A command used to check if the guild has enabled announce
e.g. @commands.check(announce_is_enabled)
:param ctx: The context of the message
:return: True if enabled or test, False otherwise
"""
try:
result = KoalaBot.check_guild_has_ext(ctx, "Announce")
except PermissionError:
result = False
return result or (str(ctx.guild) == KoalaBot.TEST_USER and KoalaBot.is_dpytest)
class Announce(commands.Cog,
description=f"Use `{KoalaBot.COMMAND_PREFIX}announce create` to create an announcement\n"
f"Use `{KoalaBot.COMMAND_PREFIX}announce send` to send it.\n"
):
"""
Send DM announcements to certain roles and people.
"""
def __init__(self, bot):
self.bot = bot
self.messages = {}
self.roles = {}
KoalaBot.database_manager.insert_extension("Announce", 0, True, True)
self.announce_database_manager = AnnounceDBManager(KoalaBot.database_manager)
self.announce_database_manager.create_tables()
def not_exceeded_limit(self, guild_id):
"""
Check if enough days have passed for the user to use the announce function
:return:
"""
if self.announce_database_manager.get_last_use_date(guild_id):
return int(time.time()) - self.announce_database_manager.get_last_use_date(
guild_id) > ANNOUNCE_SEPARATION_DAYS * SECONDS_IN_A_DAY
return True
def has_active_msg(self, guild_id):
"""
Check if a particular id has an active announcement pending announcement
:param guild_id: The id of the guild of the command
:return: Boolean of whether there is an active announcement or not
"""
return guild_id in self.messages.keys()
def get_role_names(self, guild_id, roles):
"""
A function to get the names of all the roles the announcement will be sent to
:param roles: The list of roles in the guild
:param guild_id: The id of the guild
:return: All the names of the roles that are tagged
"""
temp = []
for role in self.roles[guild_id]:
temp.append(discord.utils.get(roles, id=role).name)
return temp
def get_receivers(self, guild_id, roles):
"""
A function to get the receivers of a particular announcement
:param roles: The list of roles in the guild
:param guild_id: The id of the guild
:return: All the receivers of the announcement
"""
temp = []
for role in self.roles[guild_id]:
temp += discord.utils.get(roles, id=role).members
return list(set(temp))
def receiver_msg(self, guild):
"""
A function to create a string message about receivers
:param guild: The guild of the bot
:return: A string message about receivers
"""
if not self.roles[guild.id]:
return f"You are currently sending to Everyone and there are {str(len(guild.members))} receivers"
return f"You are currently sending to {self.get_role_names(guild.id, guild.roles)} and there are {str(len(self.get_receivers(guild.id, guild.roles)))} receivers "
def construct_embed(self, guild: discord.Guild):
"""
Constructing an embedded message from the information stored in the manager
:param guild: The the guild
:return: An embedded message for the announcement
"""
message = self.messages[guild.id]
embed: discord.Embed = discord.Embed(title=message.title,
description=message.description, colour=KoalaColours.KOALA_GREEN)
embed.set_author(name="Announcement from " + guild.name)
if message.thumbnail != 'https://cdn.discordapp.com/':
embed.set_thumbnail(url=message.thumbnail)
return embed
@commands.check(announce_is_enabled)
@commands.group(name="announce")
async def announce(self, ctx):
"""
Use k!announce create to create an announcement
"""
if ctx.invoked_subcommand is None:
await ctx.send(f"Please use `{KoalaBot.COMMAND_PREFIX}help announce` for more information")
@commands.check(announce_is_enabled)
@announce.command(name="create")
async def create(self, ctx):
"""
Create a new message that will be available for sending
:param ctx: The context of the bot
:return:
"""
if not self.not_exceeded_limit(ctx.guild.id):
remaining_days = math.ceil(
ANNOUNCE_SEPARATION_DAYS - ((int(time.time()) - self.announce_database_manager.get_last_use_date(
ctx.guild.id)) / SECONDS_IN_A_DAY))
await ctx.send("You have recently sent an announcement and cannot use this function for " + str(
remaining_days) + " days")
return
if self.has_active_msg(ctx.guild.id):
await ctx.send("There is currently an active announcement being created, you can use 'k!announce cancel' "
"or 'k!announce send' to complete it")
else:
await ctx.send("Please enter a message, I'll wait for 60 seconds, no rush.")
message, channel = await wait_for_message(self.bot, ctx)
if not message:
await channel.send("Okay, I'll cancel the command.")
return
if len(message.content) > MAX_MESSAGE_LENGTH:
await ctx.send("The content is more than 2000 characters long, and exceeds the limit")
return
self.messages[ctx.guild.id] = AnnounceMessage(f"",
message.content,
ctx.guild.icon_url)
self.roles[ctx.guild.id] = []
await ctx.send(f"An announcement has been created for guild {ctx.guild.name}")
await ctx.send(embed=self.construct_embed(ctx.guild))
await ctx.send(self.receiver_msg(ctx.guild))
@commands.check(announce_is_enabled)
@announce.command(name="changeTitle")
async def change_title(self, ctx):
"""
Change the title of the embedded message
:param ctx: The context of the bot
:return:
"""
if self.has_active_msg(ctx.guild.id):
await ctx.send("Please enter the new title, I'll wait for 60 seconds, no rush.")
title, channel = await wait_for_message(self.bot, ctx)
if not title:
await channel.send("Okay, I'll cancel the command.")
return
self.messages[ctx.guild.id].set_title(title.content)
await ctx.send(embed=self.construct_embed(ctx.guild))
else:
await ctx.send("There is currently no active announcement")
@commands.check(announce_is_enabled)
@announce.command(name="changeContent")
async def change_content(self, ctx):
"""
Change the content of the embedded message
:param ctx: The context of the bot
:return:
"""
if self.has_active_msg(ctx.guild.id):
await ctx.send("Please enter the new message, I'll wait for 60 seconds, no rush.")
message, channel = await wait_for_message(self.bot, ctx)
if not message:
await channel.send("Okay, I'll cancel the command.")
return
if len(message.content) > MAX_MESSAGE_LENGTH:
await ctx.send("The content is more than 2000 characters long, and exceeds the limit")
return
self.messages[ctx.guild.id].set_description(message.content)
await ctx.send(embed=self.construct_embed(ctx.guild))
else:
await ctx.send("There is currently no active announcement")
@commands.check(announce_is_enabled)
@announce.command(name="addRole", aliases=["add"])
async def add_role(self, ctx):
"""
Add a role to list of people to send the announcement to
:param ctx: The context of the bot
:return:
"""
if self.has_active_msg(ctx.guild.id):
await ctx.send("Please enter the roles you want to tag separated by space, I'll wait for 60 seconds, no rush.")
message, channel = await wait_for_message(self.bot, ctx)
if not message:
await channel.send("Okay, I'll cancel the command.")
return
for new_role in message.content.split():
role_id = extract_id(new_role)
if role_id not in self.roles[ctx.guild.id] and discord.utils.get(ctx.guild.roles,
id=role_id) is not None:
self.roles[ctx.guild.id].append(role_id)
await ctx.send(self.receiver_msg(ctx.guild))
else:
await ctx.send("There is currently no active announcement")
@commands.check(announce_is_enabled)
@announce.command(name="removeRole", aliases=["remove"])
async def remove_role(self, ctx):
"""
Remove a role from a list of people to send the announcement to
:param ctx: The context of the bot
:return:
"""
if self.has_active_msg(ctx.guild.id):
await ctx.send("Please enter the roles you want to remove separated by space, I'll wait for 60 seconds, no rush.")
message, channel = await wait_for_message(self.bot, ctx)
if not message:
await channel.send("Okay, I'll cancel the command.")
return
for new_role in message.content.split():
role_id = extract_id(new_role)
if role_id in self.roles[ctx.guild.id]:
self.roles[ctx.guild.id].remove(role_id)
await ctx.send(self.receiver_msg(ctx.guild))
else:
await ctx.send("There is currently no active announcement")
@commands.check(announce_is_enabled)
@announce.command(name="preview")
async def preview(self, ctx):
"""
Post a constructed embedded message to the channel where the command is invoked
:param ctx: The context of the bot
:return:
"""
if self.has_active_msg(ctx.guild.id):
await ctx.send(embed=self.construct_embed(ctx.guild))
await ctx.send(self.receiver_msg(ctx.guild))
else:
await ctx.send("There is currently no active announcement")
@commands.check(announce_is_enabled)
@announce.command(name="send")
async def send(self, ctx):
"""
Send a pending announcement
:param ctx: The context of the bot
:return:
"""
if self.has_active_msg(ctx.guild.id):
embed = self.construct_embed(ctx.guild)
if self.roles[ctx.guild.id]:
for receiver in self.get_receivers(ctx.guild.id, ctx.guild.roles):
await receiver.send(embed=embed)
else:
for receiver in ctx.guild.members:
await receiver.send(embed=embed)
self.messages.pop(ctx.guild.id)
self.roles.pop(ctx.guild.id)
self.announce_database_manager.set_last_use_date(ctx.guild.id, int(time.time()))
await ctx.send("The announcement was made successfully")
else:
await ctx.send("There is currently no active announcement")
@commands.check(announce_is_enabled)
@announce.command(name="cancel")
async def cancel(self, ctx):
"""
Cancel a pending announcement
:param ctx: The context of the bot
:return:
"""
if self.has_active_msg(ctx.guild.id):
self.messages.pop(ctx.guild.id)
self.roles.pop(ctx.guild.id)
await ctx.send("The announcement was cancelled successfully")
else:
await ctx.send("There is currently no active announcement")
class AnnounceDBManager:
"""
A class for interacting with the KoalaBot announcement database
"""
def __init__(self, database_manager: KoalaBot.database_manager):
"""
initiate variables
:param database_manager:
"""
self.database_manager = database_manager
def create_tables(self):
"""
create all the tables related to the announce database
"""
sql_create_usage_tables = """
CREATE TABLE IF NOT EXISTS GuildUsage (
guild_id integer NOT NULL,
last_message_epoch_time integer NOT NULL,
PRIMARY KEY (guild_id),
FOREIGN KEY (guild_id) REFERENCES GuildExtensions(guild_id)
);
"""
self.database_manager.db_execute_commit(sql_create_usage_tables)
def get_last_use_date(self, guild_id: int):
"""
Gets the last time when this function was used
:param guild_id: id of the target guild
:return:
"""
row = self.database_manager.db_execute_select(
"""SELECT * FROM GuildUsage WHERE guild_id = ?""", args=[guild_id])
if not row:
return
return row[0][1]
def set_last_use_date(self, guild_id: int, last_time: int):
"""
Set the last time when this function was used
:param guild_id: id of the guild
:param last_time: time when the function was used
:return:
"""
if (self.database_manager.db_execute_select("""SELECT last_message_epoch_time FROM GuildUsage where
guild_id = ?""", args=[guild_id])):
self.database_manager.db_execute_commit(
"""UPDATE GuildUsage SET last_message_epoch_time = ? WHERE guild_id = ?""", args=[last_time, guild_id])
else:
self.database_manager.db_execute_commit(
"""INSERT INTO GuildUsage (guild_id,last_message_epoch_time) VALUES (?,?)""",
args=[guild_id, last_time])
class AnnounceMessage:
"""
A class consisting the information about a announcement message
"""
def __init__(self, title, message, thumbnail):
"""
Initiate the message with default thumbnail, title and description
:param title: The title of the announcement
:param message: The message included in the announcement
:param thumbnail: The logo of the server
"""
self.title = title
self.description = message
self.thumbnail = thumbnail
def set_title(self, title):
"""
Changing the title of the announcement
:param title: A string consisting the title
:return:
"""
self.title = title
def set_description(self, message):
"""
Changing the message in the announcement
:param message: A string consisting the message
:return:
"""
self.description = message
def setup(bot: KoalaBot) -> None:
"""
Load this cog to the KoalaBot.
:param bot: the bot client for KoalaBot
"""
bot.add_cog(Announce(bot))
print("Announce is ready.")