This repository has been archived by the owner on Apr 1, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMain.py
executable file
·272 lines (244 loc) · 15 KB
/
Main.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
263
264
265
266
267
268
269
270
271
272
from Imports import *
def addLogInformation(logInformation, toStdOut=True):
with open(LOG_FILE_PATH, 'a') as logFile:
logFile.write(f"[{round(time.time() - START_PROGRAM_TIME, 5)}] {logInformation}\n")
logFile.close()
if toStdOut: print(logInformation)
def extractRND(fromSoup):
"""
Extracts the RND number from the soup.
--> Whats the RND number? <--
The RND number is a random number generated by the UPF server,
you need it download any content from the server, so first of
all we go to URL_RND and then to URL_JSON to get the jSON file.
:param fromSoup: BeautifulSoup object, contains the page with the RND number.
:return: The RND value.
"""
javaScriptCode = str(fromSoup.findAll('script', type='text/javascript')[4]) # The number is inside a javascript function.
initialPosition = javaScriptCode.find('selecionarRangoHorarios?rnd=') + len('selecionarRangoHorarios?rnd=') # Obtaining the first position of the RND number.
RND = javaScriptCode[initialPosition:initialPosition + 6].replace("'", "").replace(" ", "")
try:
float(RND) # I do this to check if the RND is a number.
except ValueError:
addLogInformation("Something went pretty bad while searching the RND value. This is probably caused by wrong UserPreferences.ini data.")
return False
else: return RND
def downloadContent(fromData, fromDate, toDate, fromHeaders=''):
"""
Downloads the content from the server.
:param fromData: UserPreferences data
:param fromDate: Initial date.
:param toDate: Final date.
:param fromHeaders: User headers, better to leave it empty.
:return: The extracted JSON file.
"""
jsonFile = False
try:
addLogInformation("Establishing session.")
SESSION = Request(URL, fromHeaders, requests.Session(), requestMethod='GET').SESSION
except Exception as ErrorCode:
addLogInformation(f"Something went wrong while generating session. {ErrorCode}")
return False
addLogInformation("Session successfully established.")
try:
addLogInformation("Searching for RND number.")
soupRND = Request(URL_RND, fromHeaders, SESSION, requestMethod='POST', postData=fromData).Soup
except Exception as ErrorCode:
addLogInformation(f"Something went wrong while connecting to search RND number. {ErrorCode}")
return False
RND = extractRND(soupRND)
timeRND = f'rnd={RND}&start={fromDate}&end={toDate}'
addLogInformation("RND Number found.")
try:
addLogInformation("Downloading the content.")
contentRequest = Request(URL_JSON, fromHeaders, SESSION, requestMethod='POST', postData=timeRND)
except Exception as ErrorCode:
addLogInformation(f"Something went wrong while obtaining the request content. {ErrorCode}")
return False
jsonFile = contentRequest.getJSON(exportFile=True)
addLogInformation("Content downloaded, JSON file saved.")
return jsonFile
def deleteGeneratedEvents(MyCalendar, subjectsBlocks, calendarID, logMessages=None, initialLoadingStatus=0, loadingWork=100):
"""
Witth this function we delete all the events that were generated by the program.
:param MyCalendar: Google Calendar object from which we delete the events.
:param subjectsBlocks: List of subjects to be removed.
:param calendarID: Calendar ID from which we delete the events.
:param logMessages: File where we're going to save the log messages, if it's None, the output goes to stdout.
:return: True if everything went well, False otherwise.
"""
loadingStatus = initialLoadingStatus
loadingBarIncreaser = loadingWork/len(subjectsBlocks)
accumulatedLoadingStatus = 0
try:
# Getting the IDs of all the events that we're going to add.
subjectsID = list(map(lambda x: x.subjectID, subjectsBlocks))
events = MyCalendar.getEvents()
except Exception as ErrorCode:
addLogInformation(f"Something went wrong while extrating the current calendar events, "
f"that's possible caused by a Google Calendar API error. "
f"{ErrorCode}")
return False
for event in events: # For every event inside Google Calendar.
eventDescription, eventID, subjectID = event
if subjectID in subjectsID: # If a new event is alredy in the calendar.
addLogInformation(f"Deleting EventID: {eventID} | {eventDescription}")
try:
MyCalendar.deleteEvent(eventID, calendarID=calendarID)
except Exception as ErrorCode:
addLogInformation(f"Something went wrong while deleting an event, "
f"that can be caused by some problems with Google Calendar API. "
f"{ErrorCode}")
return False
else:
accumulatedLoadingStatus += loadingBarIncreaser
if accumulatedLoadingStatus >= 1:
loadingStatus += int(accumulatedLoadingStatus)
accumulatedLoadingStatus = accumulatedLoadingStatus - int(accumulatedLoadingStatus)
yield loadingStatus
else: addLogInformation(f"Skipped EventID: {eventID} | {eventDescription}")
addLogInformation("All events have been removed from the calendar.")
return True
def addGeneratedEvents(MyCalendar, subjectsBlocks, calendarID, logMessages=None, initialLoadingStatus=0, loadingWork=100):
"""
With this function we're going to add the generated events to the Google Calendar.
:param MyCalendar: That's the Google Calendar object were we're adding the events.
:param subjectsBlocks: List of subjects that we are going to add to the Google Calendar.
:param calendarID: The ID of the Google Calendar where we're going to add the events.
:param logMessages: File where we're going to save the log messages, if it's None, the output goes to stdout.
:return: True in case of success, False in case of error.
"""
loadingStatus = initialLoadingStatus
loadingBarIncreaser = loadingWork/len(subjectsBlocks)
accumulatedLoadingStatus = 0
for subject in subjectsBlocks:
addLogInformation(f"Adding {subject.name} to the calendar.")
try:
MyCalendar.addEvent(subject.subjectID, f"{subject.name} ({subject.type[0]})", subject.classroom, subject.getDescription(),
subject.start, subject.end, TIMEZONE, colorID=subject.colorID,
calendarID=calendarID)
except Exception as ErrorCode:
addLogInformation(f"Something went wrong while adding subject {subject.getDescription()} to calendar, "
f"that can be caused by a wrong CalendarID or a Google Calendar API problem:\n {ErrorCode}")
return False
else:
accumulatedLoadingStatus += loadingBarIncreaser
if accumulatedLoadingStatus >= 1:
loadingStatus += int(accumulatedLoadingStatus)
accumulatedLoadingStatus = accumulatedLoadingStatus - int(accumulatedLoadingStatus)
addLogInformation(f"{subject.name} added to the calendar.")
yield loadingStatus
addLogInformation("All events have been added to the calendar.")
return True
def RunApplication(deleteMode=False, replaceMode=True):
"""
This function is the main function of the application,
it's going to read the configuration file, then it's going to generate the events.
After generating the events, it's going to add them to the Google Calendar.
:param deleteMode: If this is true, the function will delete the events from the Google Calendar.
:param logMessages: File where we're going to save the log messages, if it's None, the output goes to stdout.
:param replaceMode: If this is true, the function will replace the events in the Google Calendar.
:return: True in case of success, False in case of error.
"""
loadingStatus = 0
yield loadingStatus # 0%
# Checking the Python version, in this case we're going to use Python 3.8 or higher, since I'm using the Walrus Operator.
if sys.version_info.major < PYTHON_VERSION['Major'] or (sys.version_info.major >= PYTHON_VERSION['Major'] and sys.version_info.minor < PYTHON_VERSION['Minor']):
addLogInformation(f"You're using Python {sys.version_info.major}.{sys.version_info.minor}, required version is 3.8 or bigger.")
return False
loadingStatus += 3
yield loadingStatus # 3%
# Checking if the configuration file exists, if it exists, we're going to read it.
if os.path.isfile(CONFIG_FILE):
userPreferences = getUserPreferences(CONFIG_FILE)
else:
addLogInformation(f"UserPreferences.ini not found at {CONFIG_FILE}.")
return False
loadingStatus += 6
yield loadingStatus # 9%
if isUsingEspaiAulaFilePath(userPreferences): # If the user is using the automatic mode, we're going to read the HTML file with user data.
try: espaiAulaFile = HTML_LocalFile(getEspaiAulaFilePath(userPreferences), DECODE_HTML_FILE)
except FileNotFoundError:
addLogInformation(f"HTML file not found at {getEspaiAulaFilePath(userPreferences)}.")
return False
fromGroups, fromSubjects, userSubjectsGroups, pGroups, sGroups = extractSubjectsPreferencesFromFile(espaiAulaFile)
else: # If the user is using the manual mode, we're going to read the user groups and subjects from the user preferences file.
fromGroups, fromSubjects, userSubjectsGroups, pGroups, sGroups = extractSubjectsPreferences(userPreferences)
loadingStatus += 5
yield loadingStatus # 14%
# Extracting the time and main information.
basicInformation, timeRange = extractRequestInformation(userPreferences)
DATA = generateData(fromSubjects, fromGroups, basicInformation)
fromDate = int(time.mktime(datetime.datetime.strptime(timeRange[0], "%d/%m/%Y").timetuple()))
toDate = int(time.mktime(datetime.datetime.strptime(timeRange[1], "%d/%m/%Y").timetuple()))
loadingStatus += 5
yield loadingStatus # 21%
"""
At this point, we have all the user data and we checked if it's correct.
Now, we're going to access the UPF website to extract the schedule information.
"""
if (jsonFile := downloadContent(DATA, fromDate, toDate, fromHeaders=getUserHeaders(CONFIG_FILE))):
filteredJSON = filter(lambda x: 'codAsignatura' in x, jsonFile)
diferentSubjects = set(map(lambda x: x['codAsignatura'], filteredJSON))
userDefinedColors = getColorList(userPreferences)
if userDefinedColors[0] == 'False':
# Generating a dictionary[subjectCode] = assignedColor
subjectsColors = dict(zip(diferentSubjects,
[str(x%GOOGLE_CALENDAR_API_MAX_COLORS+1) for x in range(len(diferentSubjects))]))
else:
# Generating a dictionary[subjectCode] = assignedColor
subjectsColors = dict(zip(diferentSubjects, userDefinedColors))
loadingStatus += 2
yield loadingStatus # 16%
if (n_downloadedSubjects := len(jsonFile)-1) > 0: # If we have downloaded at least one subject, we're going to process them.
addLogInformation(f"Downloaded {n_downloadedSubjects} subjects blocks.")
subjectsBlocks = generateBlocks(jsonFile,
dict(zip(fromSubjects,
zip(userSubjectsGroups, pGroups, sGroups))),
subjectsColors,
n_downloadedSubjects)
addLogInformation(f"Using {len(subjectsBlocks)} subjects blocks.")
else:
addLogInformation("No subjects blocks have been downloaded, closing program.")
return False
else:
addLogInformation("Something went wrong generating blocks, closing program.")
return False
loadingStatus += 10
yield loadingStatus # 31%
MyCalendar = Calendar()
calendarID = getCalendarID(userPreferences)
loadingStatus += 2
yield loadingStatus # 33%
# Now, with all the information downloaded, we're going to work with Google Calendar API.
if replaceMode: # In this case, we're deleting all the events in the calendar and then adding the new ones.
mainLoop = True
functionIterator = deleteGeneratedEvents(MyCalendar, subjectsBlocks, calendarID, initialLoadingStatus=33, loadingWork=33)
while mainLoop:
try: loadingStatus = next(functionIterator)
except StopIteration: mainLoop = False
yield loadingStatus
mainLoop = True
functionIterator = addGeneratedEvents(MyCalendar, subjectsBlocks, calendarID, initialLoadingStatus=66, loadingWork=33)
while mainLoop:
try: loadingStatus = next(functionIterator)
except StopIteration: mainLoop = False
yield loadingStatus
else:
if deleteMode: # Just deleting events.
mainLoop = True
functionIterator = deleteGeneratedEvents(MyCalendar, subjectsBlocks, calendarID, initialLoadingStatus=33, loadingWork=66)
while mainLoop:
try: loadingStatus = next(functionIterator)
except StopIteration: mainLoop = False
yield loadingStatus
else: # Just adding events.
mainLoop = True
functionIterator = addGeneratedEvents(MyCalendar, subjectsBlocks, calendarID, initialLoadingStatus=33, loadingWork=66)
while mainLoop:
try: loadingStatus = next(functionIterator)
except StopIteration: mainLoop = False
yield loadingStatus
addLogInformation("Everything has been done successfully.")
yield 100 # 100%
return True