-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathBotClass.py
More file actions
255 lines (222 loc) · 11.5 KB
/
BotClass.py
File metadata and controls
255 lines (222 loc) · 11.5 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
import discord
import config
import asyncio
import time
from datetime import datetime, timedelta
import pytz
import logging
import random
from managers.CommandsManager import CommandsManager
from managers.DatabaseManager import DatabaseManager
from managers.StatisticsManager import StatisticsManager
from managers.TeamupManager import TeamupManager
from managers.CacheManager import CacheManager
from managers.CalendarsManager import CalendarsManager
from managers.helpers.embeds import Embeds
from discord.ext import tasks
# logging stuff
logger = logging.getLogger('calendar_bot')
logger.setLevel(logging.INFO)
class Bot:
_client = None
_commandsManager = None
_databaseManager = None
_cacheManager = None
_statisticsManager = None
def __init__(self):
self._client = discord.Client(heartbeat_timeout=60, guild_subscriptions=False, fetch_offline_members=False)
self._databaseManager = DatabaseManager(self)
self._cacheManager = CacheManager(self)
self._commandsManager = CommandsManager(self)
self._statisticsManager = StatisticsManager(self)
self._teamupManager = TeamupManager(self, config.bot["teamup_dev_token"])
self._calendarsManager = CalendarsManager(self)
print("✓ Bot initialized")
def run_client(self, token):
if self._client != None:
self._client.run(token)
def backend_log(self, source, msg):
err_str = "[{0} - {1}] {2}".format(source, datetime.now(), msg)
logger.debug(err_str)
# =======================
# PERIODIC CHECKING
# =======================
@tasks.loop(seconds=1314900)
async def periodic_clean_db(self):
self._databaseManager.clean_reminded_events()
@tasks.loop(seconds=100)
async def periodic_update_calendars(self):
# let's skip DatabaseManager and create custom query
cursor = self._databaseManager.get_cursor()
start_time = time.time()
# i dont even know what I'm trying to accomplish
channel_cache = dict()
# Filtering servers/calendars goes like this, after successful update it saves timestamp with variation
# if bot can't reach server/channel/message/teamup and timestamp hasn't been updated for 2 days
# - if server unreachable -> delete server + all calendars
# - if message/teamup unreachable -> delete given calendar
# - if channel unreachable -> delete all calendars in this channel
try:
variation_min = random.randint(8, 10)
time_min_back = (datetime.now() - timedelta(minutes=variation_min))
calendars = cursor.execute("SELECT * FROM calendar WHERE last_update <= ? AND is_alive='TRUE';", (time_min_back, )).fetchall()
except Exception as e:
cursor.close()
self.backend_log("periodic_update_calendars", str(e))
cursor.close()
start_time = time.time()
date_fmt = "%Y-%m-%d"
logger.info("[{0}] updating {1} calendars.".format(datetime.now(), len(calendars)))
i = 0
for calendar in calendars:
# lets wait 15 seconds after every 10 calendars because of the f*cking rate limit
# losing my mind pt. 4
if i > 0 and i % 15 == 0:
logger.debug('[{0}] ===== WAITING FOR 35s ====='.format(datetime.now()))
await asyncio.sleep(35)
logger.debug("[{0}] [{1}] CALENDAR:SERVERID: {2}:{3}".format(datetime.now(), i, calendar["ID"], calendar["server_id"]))
# Let's check if this calendar is a boomer
try:
lup_dt = datetime.strptime(calendar["last_update"], '%Y-%m-%d %H:%M:%S.%f')
last_update_hours = (datetime.now() - lup_dt).total_seconds() / 60.0 / 60.0
calendar_old = False
if last_update_hours >= 48:
calendar_old = True
except Exception as e: # mark it false if you can't parse date
calendar_old = False
# increment now in case we 'continue' the loop
message = None
try:
if self._client == None:
continue
if calendar["channel_id"] not in channel_cache:
try:
channel_cache[calendar["channel_id"]] = await self._client.fetch_channel(calendar["channel_id"])
logger.debug('\t ADDED CACHED CHANNEL')
except Exception as e:
# admin deleted this channel, let's delete all calendars with it
if calendar_old:
self._databaseManager.delete_calendars_by_channel(calendar["channel_id"])
logger.debug("\t DELETE BY CHANNEL")
continue # obv skip
else:
logger.debug('\t USED CACHED CHANNEL')
channel = channel_cache[calendar["channel_id"]]
if channel == None:
if calendar_old:
self._databaseManager.delete_calendars_by_channel(calendar["channel_id"])
logger.debug("\t DELETE BY CHANNEL")
continue
try:
message = await channel.fetch_message(calendar["message_id"])
except Exception as e:
# can't find message, delete calendar
if calendar_old:
self._databaseManager.delete_calendars_by_message(calendar["message_id"])
logger.debug("\t DELETE BY MESSAGE")
continue # obv skip
if message == None:
if calendar_old:
self._databaseManager.delete_calendars_by_message(calendar["message_id"])
logger.debug("\t DELETE BY MESSAGE")
continue
logger.debug("\t MESSAGE FOUND")
i = i + 1
# save people to remind
users_to_dm = []
for reaction in message.reactions:
if str(reaction) == "🖐️":
async for user in reaction.users():
if user != self._client.user:
users_to_dm.append(user)
logger.debug("\t {0} USERS FOUND".format(len(users_to_dm)))
# do teamup magic
calendar_tz = pytz.timezone(calendar["timezone"])
calendar_now = datetime.now().astimezone(calendar_tz)
start_date = calendar_now
end_date = start_date + timedelta(days=7)
teamup_events = await self._teamupManager.get_calendar_events(calendar["teamup_calendar_key"], start_date.strftime(date_fmt), end_date.strftime(date_fmt), calendar["timezone"], None)
if teamup_events != None:
calendar_events = self._calendarsManager.prepare_calendar_data(teamup_events, start_date, end_date, calendar["timezone"])
else:
# Can't fetch events from teamup, skip this calendar (maybe they deleted key)
logger.info("[{0}] periodic_update_calendars(reminding users) - can't fetch teamup data".format(datetime.now()))
continue
# update timestamp for calendar
# Let's add some random variation to spread number of calendars updating every loop
# time will tell if it helped
variation = random.randint(0, 360)
self._databaseManager.update_calendar_timestamp(calendar["ID"], variation)
#
# HANDLING REMINDERS
# - if it takes too long, we can optimize by putting it into `self._calendarsManager.prepare_calendar_data()`
logger.debug("\t CHECKING EVENTS FOR REMINDERS")
for day in calendar_events:
for event in day:
# don't remind all_day events
if event["all_day"]:
continue
event_delta_minutes = (event["start_dt"] - calendar_now).total_seconds() / 60.0
if event_delta_minutes <= calendar["reminder_time"]:
# check if this event has already been reminded
reminded_event = self._databaseManager.get_reminded_event(event["id"], event["version"])
# skip reminded
if reminded_event != None:
continue
for user in users_to_dm:
logger.debug("\t\t SENDING DM - {0}".format(user.id))
await asyncio.sleep(0.3)
try:
dm_channel = user.dm_channel
if dm_channel == None:
dm_channel = await user.create_dm()
await asyncio.sleep(0.3)
event["user"] = user
event["calendar_data"] = calendar
reminder_embed = Embeds.create_reminder_embed(event)
await dm_channel.send(content="", embed=reminder_embed)
except Exception as e:
logger.info("[{0}] periodic_update_calendars(reminding users) - {1}".format(datetime.now(), str(e)))
# save that we reminded this one
self._databaseManager.add_reminded_event(event["id"], event["version"])
events_data = {
"week": calendar_events,
"start_date": start_date,
"end_date": end_date
}
logger.debug("\t CREATING EMBED")
calendar_embed = self._calendarsManager.create_calendar_embed(calendar, events_data)
Embeds.add_footer(calendar_embed, None)
if message != None:
await asyncio.sleep(2)
logger.debug("\t UPDATING MESSAGE")
await message.edit(content="", embed=calendar_embed)
await message.add_reaction("🖐️") # in case admin removed reactions, add it back
except Exception as e:
logger.info("[{0}] periodic_update_calendars(for calendar) - {1}".format(datetime.now(), str(e)))
# log every loop time
loop_time = (time.time() - start_time)
logger.info("[{0}] update took {1}s".format(datetime.now(), round(loop_time, 4)))
# ==============
# Messages
# ==============
async def send_embed(self, channel, embed_data):
if not "type" in embed_data:
embed_data["type"] = "INFO"
embed = Embeds.generate_embed(embed_data)
await channel.send(embed=embed)
async def send_message(self, channel, text):
return await channel.send(text)
def exception_msg(self, error):
return {
"embed": {
"type": "ERROR",
"title": "An error has occured",
"fields": [
{
"name": "Exception",
"value": error
}
]
}
}