-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcycle.py
More file actions
303 lines (247 loc) · 12.7 KB
/
Copy pathcycle.py
File metadata and controls
303 lines (247 loc) · 12.7 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
# Converts solat times into 24-hour format in SGT and handles time based events
import pytz
import datetime
from logs import logger
import asyncio
from telebot.async_telebot import AsyncTeleBot
from typing import List
from scrapeAPI import *
from blocked import *
from storage import save_data
import state
from state import sbot, loadArr, delete_user
from text import reminder_text, pre_reminder_text
global upcoming_prayer_time
upcoming_prayer_time = None
global change_prayer_time
change_prayer_time = None
# Memo of (prayer_name, minutes_before, YYYY-MM-DD) tuples already fired.
# Cleared at midnight when prayer times refresh.
pre_reminder_sent = set()
# Set the timezone to Singapore (Asia/Singapore)
sg_timezone = pytz.timezone('Asia/Singapore')
custom_timezone = pytz.FixedOffset(480) # Default 480 for SG, 450 for 7h:30mins ahead UTC and behind SG by 30 mins
# Function to format reminder text
async def solat_reminder(chat_id, prayer, masa, next_prayer=None, next_prayer_time=None):
reminder_message = await reminder_text(chat_id, prayer, masa, next_prayer, next_prayer_time)
# Send message
await sbot.send_message(chat_id, reminder_message, 'Markdown')
# Execute the send_reminder
async def send_reminder(chat_id: str, prayer: str, masa: str, upcoming_prayer_name, next_prayer=None, next_prayer_time=None):
try:
await solat_reminder(chat_id, prayer, masa, next_prayer, next_prayer_time)
logger.info(f"Sent reminder to {chat_id} for {upcoming_prayer_name} prayer")
return True
except telebot.apihelper.ApiException as e:
if "bot was blocked by the user" in e.result.text:
logger.warning(f"Bot was blocked by user {chat_id}")
else:
logger.error(f"An error occurred in sending reminders: {e}")
except Exception as e:
# Check if it's a 403 from the generic exception
if "403" in str(e) and "blocked" in str(e).lower():
logger.warning(f"Bot was blocked by user {chat_id} (caught in generic Exception)")
else:
# Catch all other possible errors to avoid stopping the loop
logger.error(f"Unexpected error for {chat_id}: {e}")
return False
# Bulk Send reminders at once
async def bulk_send_reminders(chat_ids: List[str], prayer: str, masa: str, upcoming_prayer_name, next_prayer=None, next_prayer_time=None):
tasks = [send_reminder(chat_id, prayer, masa, upcoming_prayer_name, next_prayer, next_prayer_time) for chat_id in chat_ids]
results = await asyncio.gather(*tasks)
successful_ids = [chat_id for chat_id, success in zip(chat_ids, results) if success]
if successful_ids:
logger.info(f"Sent {len(successful_ids)} reminders for {upcoming_prayer_name} ({masa}) to:\n" + '\n- '.join(successful_ids))
else:
logger.info(f"No successful reminders sent for {upcoming_prayer_name} ({masa})")
return
# Pre-reminder dispatch (fires N minutes before a prayer)
async def solat_pre_reminder(chat_id, prayer, masa, minutes_before):
msg = await pre_reminder_text(prayer, masa, minutes_before)
await sbot.send_message(chat_id, msg, 'Markdown')
async def send_pre_reminder(chat_id: str, prayer: str, masa: str, minutes_before: int):
try:
await solat_pre_reminder(chat_id, prayer, masa, minutes_before)
logger.info(f"Sent {minutes_before}-min pre-reminder to {chat_id} for {prayer}")
return True
except telebot.apihelper.ApiException as e:
if "bot was blocked by the user" in e.result.text:
logger.warning(f"Bot was blocked by user {chat_id}")
else:
logger.error(f"An error occurred in sending pre-reminders: {e}")
except Exception as e:
if "403" in str(e) and "blocked" in str(e).lower():
logger.warning(f"Bot was blocked by user {chat_id} (caught in generic Exception)")
else:
logger.error(f"Unexpected error for {chat_id}: {e}")
return False
async def bulk_send_pre_reminders(chat_ids: List[str], prayer: str, masa: str, minutes_before: int):
tasks = [send_pre_reminder(chat_id, prayer, masa, minutes_before) for chat_id in chat_ids]
results = await asyncio.gather(*tasks)
successful_ids = [chat_id for chat_id, success in zip(chat_ids, results) if success]
if successful_ids:
logger.info(
f"{minutes_before}-min pre-reminder sent to ({len(successful_ids)}):\n" +'\n- '.join(successful_ids))
else:
logger.info(f"No successful pre-reminders sent for {minutes_before}-min before {prayer}")
return
# Schedule the scraper to run daily at 5 AM SGT
async def scheduleRun(chat_id_dict):
# Iterate through chat_id_dict to check relevant ids for sending
for chat_id, chat_info in chat_id_dict.items():
if chat_info['daily_timings_enabled']:
try:
# Try to send the reminder to check if the bot is blocked
# fetch formatted times
times_text = await printTimes()
# Send message
await sbot.send_message(chat_id, times_text, 'MarkdownV2')
logger.info(f"Sent daily reminder to {chat_id}")
except telebot.apihelper.ApiException as e:
if "bot was blocked by the user" in e.result.text: # e.result.status_code == 403 and
logger.warning(f"Bot was blocked by user {chat_id}")
else:
logger.error(f"An error occurred in sending reminders: {e}")
except Exception as e:
# Check if it's a 403 from the generic exception
if "403" in str(e) and "blocked" in str(e).lower():
logger.warning(f"Bot was blocked by user {chat_id} (caught in generic Exception)")
else:
# Catch all other possible errors to avoid stopping the loop
logger.error(f"Unexpected error for {chat_id}: {e}")
finally:
continue
await save_data(chat_id_dict)
logger.info(f"Updated database.")
async def cycleCheck(chat_id_dict, reminders_enabled_arr, daily_enabled_arr,
custom_5_enabled_arr=None, custom_10_enabled_arr=None,
custom_15_enabled_arr=None):
now = datetime.now(sg_timezone) # Use the Singapore timezone
new_day = now.replace(hour=23, minute=59, second=0, microsecond=0)
global upcoming_prayer_time, change_prayer_time
global pre_reminder_sent
custom_5_enabled_arr = custom_5_enabled_arr or []
custom_10_enabled_arr = custom_10_enabled_arr or []
custom_15_enabled_arr = custom_15_enabled_arr or []
# Get raw prayer time data
solatTimesRaw = state.database_prayer_times
if solatTimesRaw is None or not solatTimesRaw:
logger.error("Failed to retrieve prayer times from local database scan")
logger.info("Fetching from API database")
state.database_prayer_times = RefreshPrayerTime()
logger.info(f"Successfully fetched RAW: {state.database_prayer_times}")
return
# Empty arrays are valid state (every user opted out of that channel) —
# only reload from disk if BOTH arrays are empty AND the user database has
# entries to populate from. Don't early-return either way; daily updates
# and the midnight refresh must keep running independently of these arrays.
if (not reminders_enabled_arr) and (not daily_enabled_arr) and chat_id_dict:
logger.warning("Both reminder and daily arrays are empty despite users in DB; reloading from disk")
(reminders_enabled_arr, daily_enabled_arr,
custom_5_enabled_arr, custom_10_enabled_arr, custom_15_enabled_arr) = await loadArr(chat_id_dict)
# Update times
AM_12 = now.replace(hour=0, minute=1, second=0, microsecond=0)
if now < AM_12 + timedelta(minutes=2) and now >= AM_12:
logger.info("Updating Prayer Times")
state.database_prayer_times = RefreshPrayerTime()
pre_reminder_sent.clear()
print("Updated: ", state.database_prayer_times)
logger.info("Entering deep sleep after 12:00 AM")
await asyncio.sleep(17100)
return
# /daily command
# Set the target time to 5:00 AM
AM_5 = now.replace(hour=5, minute=0, second=0, microsecond=0)
if now < AM_5 + timedelta(minutes=1) and now >= AM_5:
logger.info("Sending daily prayer times at 5:00 AM")
await scheduleRun(chat_id_dict)
await asyncio.sleep(61)
# Resource Preservation
# Set the target time to between 9:00 to 10:00 PM
PM_9 = now.replace(hour=21, minute=0, second=0, microsecond=0)
if now < PM_9 + timedelta(hours=1) and now >= PM_9:
logger.info("Entering deep sleep after 9:00 PM")
await asyncio.sleep(7200)
# Set the target time to between 8:00 to 9:00 AM
AM_8 = now.replace(hour=8, minute=0, second=0, microsecond=0)
if now < AM_8 + timedelta(hours=1) and now >= AM_8:
logger.info("Entering deep sleep after 8:00 AM")
await asyncio.sleep(10800)
# Filter data
filtered_data = formatData(solatTimesRaw)
if filtered_data is None:
logger.error("Failed to filter prayer time data in filtered_data")
return
solatTimes = filtered_data[0] # Only prayer times
dateCalendar = filtered_data[1] # Only dates Islamic, Roman
dateToday = datetime.now()
# Find the nearest upcoming prayer time
for prayer, masa in solatTimes.items():
try:
# Convert AM/PM time to 24-hour format datetime object
masa_time = datetime.strptime(masa, "%I:%M %p")
except ValueError:
logger.warning(f"Invalid time format, masa: {masa}")
continue
# Combine the date and time
this_prayer_time = dateToday.replace(
hour=masa_time.hour,
minute=masa_time.minute,
second=0,
microsecond=0
)
this_prayer_time = sg_timezone.localize(this_prayer_time)
upcoming_prayer_time = this_prayer_time
upcoming_prayer_name = prayer
# Reduce CPU Load, fast return
if (prayer == 'isyak') and now > this_prayer_time + timedelta(minutes=1) and now <= new_day:
print ("Returning from isyak: ", now)
return
if now <= this_prayer_time + timedelta(minutes=1):
break
# Add 3 seconds to the upcoming prayer time to create a buffer for the 1-minute reminder window
# and avoid edge cases where the time check might miss the window due to processing delays.
upcoming_prayer_time = upcoming_prayer_time + timedelta(seconds=3)
# ========== DEBUGGING OUTPUT ===========
#print("RAW:", solatTimesRaw);print("Now: ", now);print("Upcoming: ", upcoming_prayer_name, upcoming_prayer_time)
# Pre-reminder dispatch (5/10/15 min before the upcoming prayer)
pre_slots = [
(5, custom_5_enabled_arr),
(10, custom_10_enabled_arr),
(15, custom_15_enabled_arr),
]
# --- Memo-based variant (uncomment + remove the sleep below to switch back) ---
# today_key = now.strftime('%Y-%m-%d')
# for minutes_before, group_arr in pre_slots:
# if not group_arr:
# continue
# pre_time = upcoming_prayer_time - timedelta(minutes=minutes_before)
# memo_key = (upcoming_prayer_name, minutes_before, today_key)
# if (now >= pre_time
# and now < pre_time + timedelta(minutes=1)
# and memo_key not in pre_reminder_sent):
# await bulk_send_pre_reminders(group_arr, upcoming_prayer_name, masa, minutes_before)
# pre_reminder_sent.add(memo_key)
# --- end memo variant ---
for minutes_before, group_arr in pre_slots:
if not group_arr:
continue
pre_time = upcoming_prayer_time - timedelta(minutes=minutes_before)
if now >= pre_time and now < pre_time + timedelta(minutes=1):
await bulk_send_pre_reminders(group_arr, upcoming_prayer_name, masa, minutes_before)
await asyncio.sleep(61)
# If time is within 1 minute after azan
if now < upcoming_prayer_time + timedelta(minutes=1) and now >= upcoming_prayer_time:
prayer_keys = list(solatTimes.keys())
next_prayer_name = None
next_prayer_time = None
if prayer in prayer_keys:
if prayer == 'isyak':
next_prayer_name = 'subuh' # next day
next_prayer_time = get_tomorrow_subuh() or solatTimes[next_prayer_name]
else:
current_index = prayer_keys.index(prayer)
next_prayer_name = prayer_keys[current_index + 1]
next_prayer_time = solatTimes[next_prayer_name]
await bulk_send_reminders(reminders_enabled_arr, prayer, masa, upcoming_prayer_name, next_prayer_name, next_prayer_time)
await asyncio.sleep(61)