def create_event(self, title, start_time): service = self.__create_sevice() calender_id = self.__get_calendar_id() event = { 'summary': title, 'start': { 'date': start_time.strftime("%Y-%m-%d"), }, 'end': { 'date': start_time.strftime("%Y-%m-%d"), }, 'reminders': { 'useDefault': False, 'overrides': [ { 'method': 'popup', 'minutes': 1440 }, ], }, } signal.alarm(30) event = service.events().insert(calendarId=calender_id, body=event).execute() signal.alarm(0) event_id = event["id"] write_log("Event created.", "Event-ID: " + event_id, "Event-Title: " + title) return event_id
def run(self): while True: try: # Threading for elternportal crawling while doing the google calendar crawler in the main thread. with concurrent.futures.ThreadPoolExecutor() as executor: future = executor.submit(crawler.fetch_exams) previous_exams = calendar.fetch_events() exams = future.result() # If the calendar is empty it means that this is the first time creating it. # So it needs to add all the exams to the calendar if self.__is_calendar_empty(previous_exams): self.__add_exams_to_calendar(exams) # Otherwise it checks for updates. else: write_log("Checking for updates") self.__check_for_updates(exams, previous_exams) write_log(f"Waiting for {config.wait_between_check} seconds before continuing.") # wait for the number of seconds specified in the settings before proceeding. time.sleep(config.wait_between_check) except KeyboardInterrupt: write_log("Application terminated.") exit(0) except Exception as exception: try: write_log("An Error Occurred.", exception, f"Waiting for {config.wait_between_error} seconds before continuing.") # After an error occurred the app will wait for the specified number of seconds before proceeding # and trying it again. time.sleep(config.wait_between_error) except KeyboardInterrupt: write_log("Application terminated.") exit(0)
def __check_for_updates(self, exams, previous_exams): exam_shift = 0 previous_exam_shift = 0 index = 0 # The "+ (exam_shift + previous_exam_shift)" in the next line is making it so that if there are two wrong # entries checking each other that the list is extended by one so that the program can find both the errors and # correct them. That only works if the two wrong entries checking themselves happen to be the last ones and if # exactly as many events get deleted as created. while index < len(self.__longest_list(exams, previous_exams)) + (exam_shift + previous_exam_shift): # The exam and previous exam shifts balance out the list if something new or old was found. # Because that means that if for example a new exam is inserted into the exam list the following exams will # be out of order by one. exam = self.__safe_list_get(exams, index - exam_shift) previous_exam = self.__safe_list_get(previous_exams, index - previous_exam_shift) # Whenever something is added or deleted the length of the list is expand by one. # When something is added and deleted the length stays the same but it will still continue counting. # So it breaks if both times nothing is returned. if exam is None and previous_exam is None: break # If the last part of the list contains a old exam but not a new one it gets deleted. # (Just if it is the last one in the list tough.) elif exam is None: write_log("Outdated Exam detected.") calendar.delete_event(previous_exam.event_id) # If the last part of the list contains a new exam but not a old one it gets created. # (Just if it is the last one in the list tough.) elif previous_exam is None: write_log("New Exam detected.") calendar.create_event(exam.title, exam.start_date) # If the titles are the same but the date changed, the date will be updated. elif exam.title == previous_exam.title and exam.start_date != previous_exam.start_date: write_log("Exam date change detected") calendar.update_event(previous_exam.event_id, exam.title, exam.start_date) # If the names don't madge each other it is either a new exam or a outdated one. elif exam.title != previous_exam.title: # If the number of titles in exams is bigger than the one in the previous exams it means that a new # exam must be created. if self.__title_counter(exams, exam.title) > self.__title_counter(previous_exams, exam.title): write_log("New Exam detected.") calendar.create_event(exam.title, exam.start_date) # A new exam was detected so the previous exams need to be moved back by one so that everything # stays the same previous_exam_shift += 1 # If the number of titles in exams is smaller than the one in the previous exams it means that an # old title was found and it must be deleted. elif self.__title_counter(exams, previous_exam.title) < self.__title_counter(previous_exams, previous_exam.title): write_log("Outdated Exam detected.") calendar.delete_event(previous_exam.event_id) # A old exam was detected so the exams need to be moved back by one so that everything stays the # same exam_shift += 1 index += 1
def delete_event(self, event_id): service = self.__create_sevice() calendar_id = self.__get_calendar_id() signal.alarm(30) service.events().delete(calendarId=calendar_id, eventId=event_id).execute() signal.alarm(0) write_log("Event deleted.", "Deleted-Event-ID: " + event_id)
def __save_credentials(self, credentials): with open(self.__token_path, "wb") as file: pickle.dump(credentials, file) write_log("authorization token saved in " + self.__token_path) print("") print( "The token has been created. You can now exit the app and run it with:" ) print("sudo nohup python3 main.py &") print("Or wait until the rest of the calendar was updated") print("Read the log for information on the current process") print("")
def __init__(self): try: # import Variables from the settings file cfg_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "SETTINGS.ini") cfg = configparser.ConfigParser() cfg.read(cfg_path) self.url = str(cfg.get("Settings", "url")) self.post_url = urljoin(self.url, "/includes/project/auth/login.php") self.end_url = urljoin(self.url, "/service/termine/liste") self.calendar_name = str(cfg.get("Settings", "calendar_name")) self.email = str(cfg.get("Settings", "email")) self.password = str(cfg.get("Settings", "password")) self.client_secret_path = str( cfg.get("Settings", "client_secret.json_path")) self.wait_between_check = int( cfg.get("Settings", "wait_between_check")) self.wait_between_error = int( cfg.get("Settings", "wait_between_error")) # Is the email correct if "@" not in self.email: raise Exception("Email is incorrect.") # raises an exception if the path to the client secret file was entered incorrectly if os.path.isfile(self.client_secret_path) is False: raise Exception("No such file: " + self.client_secret_path) # raises an exception if the time between the checks is negative or zero if self.wait_between_check <= 0 or self.wait_between_error <= 0: raise Exception( "A Negative int or zero was set in the SETTINGS.ini file under the wait_between_... " "section.") # raises an exception if the time is over a year = over 31622400 seconds if self.wait_between_check > 31622400 or self.wait_between_error > 31622400: raise Exception( "The waiting time is over a year. It needs to be under, or 31622400 seconds." ) except Exception as exception: print() print("An Error Occurred with your Settings:") print(exception) write_log("An Error Occurred with your Settings", exception, "Application terminated.") exit(0)
def __create_new_calendar(self): service = self.__create_sevice() calendar_body = { 'summary': self.__calendar_name, } signal.alarm(30) created_calendar = service.calendars().insert( body=calendar_body).execute() signal.alarm(0) calendar_id = created_calendar["id"] self.__save_calendar_id(calendar_id) write_log("Created new Calendar.", "Calendar-ID: " + calendar_id, "Calendar-ID has been stored in " + self.__calendar_id_path) return calendar_id
def __google_authentication(self): if self.__is_already_authenticated() is True: return self.__load_credentials() else: try: write_log("Authorizing the application.") flow = InstalledAppFlow.from_client_secrets_file( self.__client_secret_path, scopes=self.__scopes) flow.run_console(access_type='offline') self.__save_credentials(flow.credentials) return flow.credentials except InvalidGrantError: write_log("Invalid authorization code.", "Application terminated.") exit(0)
def fetch_exams(self): write_log("Crawling all exams from Elternportal.") soup = self.__fetch_soup() exams = [] for row in soup.find_all("tr"): # Because the elternportal site contains "<tr><tr>" I need to ignore them. if str(row).startswith("<tr><tr>") or "<h2>" in str(row) or "<h4>" in str(row): continue title = row.find_all("td")[-1].text.strip() date_str = row.find_all("td")[0].text.strip() start_date = self.__format_date(date_str) exams.append(CrawledExam(title, start_date)) # Sorts the list after the names first and then after the date. exams.sort(key=lambda exam: (exam.title.lower(), exam.start_date)) return exams
def fetch_events(self): write_log("Crawling all exams from the google calendar.") calendar_events = [] page_token = None service = self.__create_sevice() while True: signal.alarm(30) events = service.events().list(calendarId=self.__get_calendar_id(), pageToken=page_token).execute() signal.alarm(0) for event in events['items']: title = event["summary"] start_time = self.__turn_into_date_obj(event['start']["date"]) event_id = event["id"] calendar_events.append(CrawledExam(title, start_time, event_id)) page_token = events.get('nextPageToken') if not page_token: break calendar_events.sort( key=lambda exam: (exam.title.lower(), exam.start_date)) return calendar_events
def do_the_check(self): try: if self.__check_internet_connection() is False: print("There appears to be no internet connection!") write_log("There appears to be no internet connection!") return False if self.__check_response_code() is False: print(f"{config.url} could not be reached.") write_log(f"{config.url} could not be reached.") return False if self.__check_elternportal_logindata() is False: print("The login data for the elternportal website is wrong.") write_log("The login data for the elternportal website is wrong.") return False return True except Exception as e: print("An Error Occurred.") print(e) write_log("An Error Occurred.", e, "Application terminated.") exit(0)
self.__add_exams_to_calendar(exams) # Otherwise it checks for updates. else: write_log("Checking for updates") self.__check_for_updates(exams, previous_exams) write_log(f"Waiting for {config.wait_between_check} seconds before continuing.") # wait for the number of seconds specified in the settings before proceeding. time.sleep(config.wait_between_check) except KeyboardInterrupt: write_log("Application terminated.") exit(0) except Exception as exception: try: write_log("An Error Occurred.", exception, f"Waiting for {config.wait_between_error} seconds before continuing.") # After an error occurred the app will wait for the specified number of seconds before proceeding # and trying it again. time.sleep(config.wait_between_error) except KeyboardInterrupt: write_log("Application terminated.") exit(0) if __name__ == "__main__": write_log("Application started.") # Checks the login data and the internet connection before proceeding. if check.do_the_check(): app = App() app.run() write_log("Application terminated.")