From d02362a3dad92801e42a952b47c9b4aeefd36898 Mon Sep 17 00:00:00 2001 From: lpetrov02 <71082527+lpetrov02@users.noreply.github.com> Date: Mon, 30 Nov 2020 21:52:08 +0300 Subject: [PATCH 1/5] better bot with better code Secret switch off password: 'lsr_memkn6' Solved the problem of incorrect group id Impossible to cancel the task given buy another user Much more readable, i believe --- messages_test.py | 394 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 394 insertions(+) create mode 100644 messages_test.py diff --git a/messages_test.py b/messages_test.py new file mode 100644 index 0000000..4ad8ffa --- /dev/null +++ b/messages_test.py @@ -0,0 +1,394 @@ +import vk +import datetime +import requests +import time +import random +import math + + +class Group: + """ + we don't really need this now but i suppose it would be useful in the nearest future + """ + + def __init__(self, group_id, freq): + self.group_id = group_id + self.begin = datetime.datetime.now() + self.frequency = freq + + def count_online_proportion(self): + """ + gets the number of members online, ant the total number of members, not counting those, + whose info is not is not available + :param self: the object of the class Group. We need the group id + :return: two integers - the number of members with available online-information and the number of members online + """ + amount = 0 + online = 0 + your_group_info = vk_api.groups.getById(group_id=self.group_id, fields='members_count') + number_of_members = your_group_info[0]['members_count'] + one_more_number_of_members = number_of_members + already_count = 0 + while already_count < number_of_members: + group_members_ids = vk_api.groups.getMembers(group_id=self.group_id, offset=already_count, fields='online') + for x in group_members_ids['items']: + if 'online' in x: + online += x['online'] + else: + one_more_number_of_members -= 1 + already_count += 1000 + if one_more_number_of_members == 0: + return -1, -1 + else: + return one_more_number_of_members, online + + def group_analyse(self): + t0 = time.time() + current_time = datetime.datetime.now() + + all_members, online_members = self.count_online_proportion() + percent = online_members / all_members * 100 + percent = math.ceil(percent) + + t0 = time.time() - t0 + return t0, percent + + def work_and_print(self, count, current_user_id): + start_time, percent = self.group_analyse() + string = "Online percent in " + self.group_id + " is " + str(percent) + "%" + vk_api2.messages.send(user_id=current_user_id, message=string, random_id=count) + return count + 1 + + def group_analyse1(self): + t0 = time.time() + all_members, online_members = self.count_online_proportion() + percent = online_members / all_members * 100 + percent = math.ceil(percent) + + t0 = time.time() - t0 + if t0 >= self.frequency * 60: + print("Sorry, I can't work so fast, so I will count statistics as fast as i can") + while True: + # message, user_id = get_message(my_number_group_id) + current_time = datetime.datetime.now() + + all_members, online_members = self.count_online_proportion() + percent = online_members / all_members * 100 + percent = math.ceil(percent) + + print("Time: " + str(current_time) + ". Online percent in " + self.group_id + " is " + str( + percent) + "%") + print( + "_____________________________________________________________________________________________") + else: + while True: + current_time = datetime.datetime.now() + t0 = time.time() + + all_members, online_members = self.count_online_proportion() + percent = online_members / all_members * 100 + percent = math.ceil(percent) + + print("Time: " + str(current_time) + ". Online percent in " + self.group_id + " is " + str( + percent) + "%") + print( + "_____________________________________________________________________________________________") + t0 = time.time() - t0 + time.sleep(self.frequency * 60 - t0) + + + +token = "65e6efa565e6efa565e6efa54f6593fb1f665e665e6efa53a5c6937a4636b3416a8bd92" +group_token = "17e681fbe171945431a04f1abc752d41ff888698288abf74124de4e782c67f36e76484601991870f56b7a" +analyse_group_id = 'memkn' +my_group_id = 'memkn_funclub' +my_number_group_id = 200698416 + +session1 = vk.AuthSession(access_token=token) +session2 = vk.AuthSession(access_token=group_token) +vk_api = vk.API(session1, v=5.92) +vk_api2 = vk.API(session2, v=5.92) + +month_length = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + + +def get_user_last_seen(profile_id): + """ + shows when user was online + :param profile_id: + :return time: + """ + value = vk_api.users.get(user_ids=profile_id, fields='last_seen') + if 'last_seen' not in value[0]: + return None + online_time = datetime.datetime.fromtimestamp(value[0]['last_seen']['time']) + return online_time + + +def get_group_followers(group_page_id, done): + """ + gets the list of the group members, not more than 1000 + :param group_page_id: + :param done: shows the indent + :return list of members' ids: + """ + value = vk_api.groups.getMembers(group_id=group_page_id, offset=done) + followers_id = [] + for user in value['items']: + followers_id.append(user) + return followers_id + + +def approximate_time(real_time): + """ + gets time user was last seen and adds two minutes + :param real_time: time user was last seen online + :return: real_time plus delta min + """ + + hours = real_time.hour + days = real_time.day + months = real_time.month + years = real_time.year + # обработка редких случаев + if years % 4 == 0 and years % 100 != 0 or years % 400 == 0: + month_length[1] += 1 + if real_time.minute >= 58: + hours = real_time.hour + 1 + if hours == 24: + hours = 0 + days += 1 + if days > month_length[months - 1]: + days = 1 + months += 1 + if months > 12: + months = 1 + years += 1 + + approximate_online_time = real_time.replace(minute=(real_time.minute + 2) % 60, + hour=hours, day=days, month=months, year=years) + return approximate_online_time + + +def is_online(online_time): + # just checks if the user is online or not, returns 0 or 1 + + now_time = datetime.datetime.now() + now_time = now_time.replace(microsecond=0) + if online_time >= now_time: + return 1 + else: + return 0 + + +def get_message(group_id, server_, ts_, key_): + # gets the message from the user + response = requests.get('{server}?act=a_check&key={key}&ts={ts}&wait=25'.format + (server=server_, key=key_, ts=ts_)).json() + if len(response['updates']) > 0: + return response['updates'][0]['object']['body'], response['updates'][0]['object']['user_id'], response['ts'] + return "", -1, response['ts'] + + +def count_new_time(time_now, period): + # gets the time, when the counting stats process was last started and returns time, when to start the process again. + # just adds period. + d_minutes = period + d_hours = 0 + if period > 59: + d_minutes = period % 60 + d_hours = period // 60 + minutes = time_now.minute + hours = time_now.hour + days = time_now.day + months = time_now.month + years = time_now.year + if years % 4 == 0 and years % 100 != 0 or years % 400 == 0: + month_length[1] += 1 + + minutes = (minutes + d_minutes) % 60 + if minutes < time_now.minute: + hours += 1 + hours += d_hours + if hours > 23: + hours %= 24 + days += 1 + if days > month_length[time_now.month]: + days = 1 + months += 1 + if months > 12: + months = 1 + years += 1 + month_length[1] -= 1 + + new_time = time_now.replace(minute=minutes, hour=hours, day=days, month=months, year=years) + return new_time + + +def handle_input_message(message): + # gets the massage from the user and returns a code which depends on the message type. Also returns the group id + # and needed frequency. These fields will be used afterwards only if the message is: group_id: ...; period: ... + if message == "stop" or message == "Stop": + return 1, "", -1 + if message == "": + return 0, "", -1 + if message == "lsr_memkn6": + return 2, "", -1 + if message.count(';') == 1: + index = message.find(';') + if message[0: 9].strip() != "group_id:" or message[index + 1:].count('d') != 1 \ + or message[index + 1: message[index + 1:].find('d') + 3 + index].strip() != "period:": + return -1, "", -1 + period = message[message[index + 1:].find('d') + 3 + index:].strip() + group = message[9: index].strip() + if not check_for_correct(group): + return 5, "", -1 + if not period.isdigit() and not period == "frequently": + return -1, "", -1 + elif period == "frequently": + return 3, group, 0 + else: + return 3, group, int(period) + string1 = message[0: 6] + string2 = message[0: 5] + if string1 == "Привет" or string1 == "привет" or string2 == "Hello" or string2 == "hello": + return 4, "", -1 + return -1, "", -1 + + +def switch_off(count, current_user_id): + # switches the bot off, the special password is needed + vk_api2.messages.send(user_id=current_user_id, message="Goodbye!", random_id=count) + return 0, count + 1 + + +def incorrect_id(count, current_user_id): + # informs about the mistake + vk_api2.messages.send(user_id=current_user_id, message="Wrong group id!", random_id=count) + return count + 1 + + +def cancel_the_task(have_a_task, current_user_id, count, master_id): + # Cancels the current task, it is is asked by the user who gave the task earlier + if have_a_task and current_user_id == master_id: + vk_api2.messages.send(user_id=current_user_id, message="Your task is cancelled!", random_id=count) + return 0, count + 1 + elif have_a_task: + vk_api2.messages.send(user_id=current_user_id, message="Sorry, I am working on the other user's task!", + random_id=count) + return 1, count + 1 + else: + vk_api2.messages.send(user_id=current_user_id, message="Я сделал ничего, не благодари!", + random_id=count) + return 0, count + 1 + + +def say_hello(count, current_user_id): + # sends a message 'Ну привет, ....' + string = "Ну привет, " + value = vk_api2.users.get(user_ids=current_user_id, fields='first_name') + string += value[0]['first_name'] + vk_api2.messages.send(user_id=current_user_id, message=string, random_id=count) + return count + 1 + + +def instruction_message(count, current_user_id): + # sends the message if user did send us the message of an unknown format + string = "Неправильный формат данных! Я понимаю примитивные приветствия, команду 'stop' для отмены" + \ + "и формат: 'group_id: ....; period: ....' Подумай головой, пока не забанил тебя." + vk_api2.messages.send(user_id=current_user_id, message=string, random_id=count) + return count + 1 + + +def repeat_the_process(master_id, count, next_time): + # current_minutes = time_start.minute + next_time = count_new_time(next_time, group.frequency) + count = group.work_and_print(count, master_id) + return next_time, count + + +def first_process(count, master_id, group): + t0 = time.time() + time_start = datetime.datetime.now() + next_time = count_new_time(time_start, group.frequency) + # next_minutes - when to count again + count = group.work_and_print(count, master_id) + t0 = time.time() - t0 + # if analysing took more time than the period: + if t0 > group.frequency * 60: + vk_api2.messages.send(user_id=master_id, message="Can't work so fast((", + random_id=count) + next_time = datetime.datetime.now() + group.frequency = 0 + return next_time, count + 1 + return next_time, count + + +def check_for_correct(group_id): + try: + your_group_info = vk_api.groups.getById(group_id=group_id, fields='members_count', count=1) + except vk.exceptions.VkAPIError: + return 0 + return 1 + + + +# starts working, gives default values to some variables +some = vk_api2.groups.getLongPollServer(group_id=my_number_group_id) +current_ts = some['ts'] +server = some['server'] +key = some['key'] +message = "" +run = 1 +count = 0 +have_a_task = 0 +group = Group(-1, -1) +master_id = -1 +while run: + message, current_user_id, current_ts = get_message(my_number_group_id, server, current_ts, key) + if have_a_task and datetime.datetime.now() >= next_time: + next_time, count = repeat_the_process(master_id, count, next_time) + + code, analyse_group_id, frequency_number = handle_input_message(message) + + if code == 3: + if not have_a_task or current_user_id == master_id: + have_a_task = 1 + master_id = current_user_id + group.group_id = analyse_group_id + group.frequency = frequency_number + next_time, count = first_process(count, master_id, group) + else: + vk_api2.messages.send(user_id=current_user_id, message="Sorry, I am busy!", random_id=count) + count += 1 + elif code == 2: + run, count = switch_off(count, current_user_id) + elif code == 1: + have_a_task, count = cancel_the_task(have_a_task, current_user_id, count, master_id) + elif code == 4: + count = say_hello(count, current_user_id) + elif code == -1: + count = instruction_message(count, current_user_id) + elif code == 5: + count = incorrect_id(count, current_user_id) + +''' + # time when we start - in seconds and in a special type + t_start = time.time() + time_start = datetime.datetime.now() + current_minutes = time_start.minute + next_time = count_new_time(time_start, frequency_number) + # next_minutes - when to count again + group.group_id = analyse_group_id + group.frequency = frequency_number + count = group.work_and_print(count, current_user_id) + t_finish = time.time() - t_start + # if analysing took more time than the period: + if t_finish > frequency_number * 60: + vk_api2.messages.send(user_id=current_user_id, message="Can't work so fast((", + random_id=count) + count += 1 + next_time = datetime.datetime.now() + frequency_number = 0 + group.frequency = 0 + ''' \ No newline at end of file From 00879dc8bb00b68509c840885c58bf01bafd2129 Mon Sep 17 00:00:00 2001 From: lpetrov02 <71082527+lpetrov02@users.noreply.github.com> Date: Tue, 9 Feb 2021 23:37:58 +0300 Subject: [PATCH 2/5] Delete main.py --- main.py | 69 --------------------------------------------------------- 1 file changed, 69 deletions(-) delete mode 100644 main.py diff --git a/main.py b/main.py deleted file mode 100644 index dc51b8e..0000000 --- a/main.py +++ /dev/null @@ -1,69 +0,0 @@ -import vk -import datetime - - -token = "65e6efa565e6efa565e6efa54f6593fb1f665e665e6efa53a5c6937a4636b3416a8bd92" -session1 = vk.AuthSession(access_token=token) -vk_api = vk.API(session1, v=5.92) - -group_id = 'memkn' - - -def get_user_last_seen(profile_id): - """ - gets when user was online - :param profile_id: - :return time: - """ - value = vk_api.users.get(user_ids=profile_id, fields='last_seen') - if 'last_seen' not in value[0]: - return None - online_time = datetime.datetime.fromtimestamp(value[0]['last_seen']['time']) - return online_time - - -def get_group_followers(group_page_id): - """ - getting group followers - :param group_page_id: - :return list of followers id: - """ - value = vk_api.groups.getMembers(group_id=group_page_id) - followers_id = [] - for user in value['items']: - followers_id.append(user) - return followers_id - - -def approximate_time(real_time): - approximated_online_time = real_time.replace(minute=(real_time.minute + 2) % 60) - return approximated_online_time - - -def is_online(online_time): - now_time = datetime.datetime.now() - now_time = now_time.replace(microsecond=0) - if online_time >= now_time: - return 1 - else: - return 0 - - -def online_proportion(group_page_id): - group_members_ids = get_group_followers(group_page_id) - group_amount = 0 - group_online = 0 - for profile in group_members_ids: - profile_last_seen = get_user_last_seen(str(profile)) - if profile_last_seen is not None: - group_amount += 1 - profile_last_seen = approximate_time(profile_last_seen) - group_online += is_online(profile_last_seen) - if group_amount == 0: - return -1 - else: - percent_online = group_online / group_amount * 100 - return percent_online - - -print(online_proportion(group_id)) From c4fa9aad6157dcfae12a6aca75a1a30612aca256 Mon Sep 17 00:00:00 2001 From: lpetrov02 <71082527+lpetrov02@users.noreply.github.com> Date: Tue, 9 Feb 2021 23:38:09 +0300 Subject: [PATCH 3/5] Delete messages_test.py --- messages_test.py | 394 ----------------------------------------------- 1 file changed, 394 deletions(-) delete mode 100644 messages_test.py diff --git a/messages_test.py b/messages_test.py deleted file mode 100644 index 4ad8ffa..0000000 --- a/messages_test.py +++ /dev/null @@ -1,394 +0,0 @@ -import vk -import datetime -import requests -import time -import random -import math - - -class Group: - """ - we don't really need this now but i suppose it would be useful in the nearest future - """ - - def __init__(self, group_id, freq): - self.group_id = group_id - self.begin = datetime.datetime.now() - self.frequency = freq - - def count_online_proportion(self): - """ - gets the number of members online, ant the total number of members, not counting those, - whose info is not is not available - :param self: the object of the class Group. We need the group id - :return: two integers - the number of members with available online-information and the number of members online - """ - amount = 0 - online = 0 - your_group_info = vk_api.groups.getById(group_id=self.group_id, fields='members_count') - number_of_members = your_group_info[0]['members_count'] - one_more_number_of_members = number_of_members - already_count = 0 - while already_count < number_of_members: - group_members_ids = vk_api.groups.getMembers(group_id=self.group_id, offset=already_count, fields='online') - for x in group_members_ids['items']: - if 'online' in x: - online += x['online'] - else: - one_more_number_of_members -= 1 - already_count += 1000 - if one_more_number_of_members == 0: - return -1, -1 - else: - return one_more_number_of_members, online - - def group_analyse(self): - t0 = time.time() - current_time = datetime.datetime.now() - - all_members, online_members = self.count_online_proportion() - percent = online_members / all_members * 100 - percent = math.ceil(percent) - - t0 = time.time() - t0 - return t0, percent - - def work_and_print(self, count, current_user_id): - start_time, percent = self.group_analyse() - string = "Online percent in " + self.group_id + " is " + str(percent) + "%" - vk_api2.messages.send(user_id=current_user_id, message=string, random_id=count) - return count + 1 - - def group_analyse1(self): - t0 = time.time() - all_members, online_members = self.count_online_proportion() - percent = online_members / all_members * 100 - percent = math.ceil(percent) - - t0 = time.time() - t0 - if t0 >= self.frequency * 60: - print("Sorry, I can't work so fast, so I will count statistics as fast as i can") - while True: - # message, user_id = get_message(my_number_group_id) - current_time = datetime.datetime.now() - - all_members, online_members = self.count_online_proportion() - percent = online_members / all_members * 100 - percent = math.ceil(percent) - - print("Time: " + str(current_time) + ". Online percent in " + self.group_id + " is " + str( - percent) + "%") - print( - "_____________________________________________________________________________________________") - else: - while True: - current_time = datetime.datetime.now() - t0 = time.time() - - all_members, online_members = self.count_online_proportion() - percent = online_members / all_members * 100 - percent = math.ceil(percent) - - print("Time: " + str(current_time) + ". Online percent in " + self.group_id + " is " + str( - percent) + "%") - print( - "_____________________________________________________________________________________________") - t0 = time.time() - t0 - time.sleep(self.frequency * 60 - t0) - - - -token = "65e6efa565e6efa565e6efa54f6593fb1f665e665e6efa53a5c6937a4636b3416a8bd92" -group_token = "17e681fbe171945431a04f1abc752d41ff888698288abf74124de4e782c67f36e76484601991870f56b7a" -analyse_group_id = 'memkn' -my_group_id = 'memkn_funclub' -my_number_group_id = 200698416 - -session1 = vk.AuthSession(access_token=token) -session2 = vk.AuthSession(access_token=group_token) -vk_api = vk.API(session1, v=5.92) -vk_api2 = vk.API(session2, v=5.92) - -month_length = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] - - -def get_user_last_seen(profile_id): - """ - shows when user was online - :param profile_id: - :return time: - """ - value = vk_api.users.get(user_ids=profile_id, fields='last_seen') - if 'last_seen' not in value[0]: - return None - online_time = datetime.datetime.fromtimestamp(value[0]['last_seen']['time']) - return online_time - - -def get_group_followers(group_page_id, done): - """ - gets the list of the group members, not more than 1000 - :param group_page_id: - :param done: shows the indent - :return list of members' ids: - """ - value = vk_api.groups.getMembers(group_id=group_page_id, offset=done) - followers_id = [] - for user in value['items']: - followers_id.append(user) - return followers_id - - -def approximate_time(real_time): - """ - gets time user was last seen and adds two minutes - :param real_time: time user was last seen online - :return: real_time plus delta min - """ - - hours = real_time.hour - days = real_time.day - months = real_time.month - years = real_time.year - # обработка редких случаев - if years % 4 == 0 and years % 100 != 0 or years % 400 == 0: - month_length[1] += 1 - if real_time.minute >= 58: - hours = real_time.hour + 1 - if hours == 24: - hours = 0 - days += 1 - if days > month_length[months - 1]: - days = 1 - months += 1 - if months > 12: - months = 1 - years += 1 - - approximate_online_time = real_time.replace(minute=(real_time.minute + 2) % 60, - hour=hours, day=days, month=months, year=years) - return approximate_online_time - - -def is_online(online_time): - # just checks if the user is online or not, returns 0 or 1 - - now_time = datetime.datetime.now() - now_time = now_time.replace(microsecond=0) - if online_time >= now_time: - return 1 - else: - return 0 - - -def get_message(group_id, server_, ts_, key_): - # gets the message from the user - response = requests.get('{server}?act=a_check&key={key}&ts={ts}&wait=25'.format - (server=server_, key=key_, ts=ts_)).json() - if len(response['updates']) > 0: - return response['updates'][0]['object']['body'], response['updates'][0]['object']['user_id'], response['ts'] - return "", -1, response['ts'] - - -def count_new_time(time_now, period): - # gets the time, when the counting stats process was last started and returns time, when to start the process again. - # just adds period. - d_minutes = period - d_hours = 0 - if period > 59: - d_minutes = period % 60 - d_hours = period // 60 - minutes = time_now.minute - hours = time_now.hour - days = time_now.day - months = time_now.month - years = time_now.year - if years % 4 == 0 and years % 100 != 0 or years % 400 == 0: - month_length[1] += 1 - - minutes = (minutes + d_minutes) % 60 - if minutes < time_now.minute: - hours += 1 - hours += d_hours - if hours > 23: - hours %= 24 - days += 1 - if days > month_length[time_now.month]: - days = 1 - months += 1 - if months > 12: - months = 1 - years += 1 - month_length[1] -= 1 - - new_time = time_now.replace(minute=minutes, hour=hours, day=days, month=months, year=years) - return new_time - - -def handle_input_message(message): - # gets the massage from the user and returns a code which depends on the message type. Also returns the group id - # and needed frequency. These fields will be used afterwards only if the message is: group_id: ...; period: ... - if message == "stop" or message == "Stop": - return 1, "", -1 - if message == "": - return 0, "", -1 - if message == "lsr_memkn6": - return 2, "", -1 - if message.count(';') == 1: - index = message.find(';') - if message[0: 9].strip() != "group_id:" or message[index + 1:].count('d') != 1 \ - or message[index + 1: message[index + 1:].find('d') + 3 + index].strip() != "period:": - return -1, "", -1 - period = message[message[index + 1:].find('d') + 3 + index:].strip() - group = message[9: index].strip() - if not check_for_correct(group): - return 5, "", -1 - if not period.isdigit() and not period == "frequently": - return -1, "", -1 - elif period == "frequently": - return 3, group, 0 - else: - return 3, group, int(period) - string1 = message[0: 6] - string2 = message[0: 5] - if string1 == "Привет" or string1 == "привет" or string2 == "Hello" or string2 == "hello": - return 4, "", -1 - return -1, "", -1 - - -def switch_off(count, current_user_id): - # switches the bot off, the special password is needed - vk_api2.messages.send(user_id=current_user_id, message="Goodbye!", random_id=count) - return 0, count + 1 - - -def incorrect_id(count, current_user_id): - # informs about the mistake - vk_api2.messages.send(user_id=current_user_id, message="Wrong group id!", random_id=count) - return count + 1 - - -def cancel_the_task(have_a_task, current_user_id, count, master_id): - # Cancels the current task, it is is asked by the user who gave the task earlier - if have_a_task and current_user_id == master_id: - vk_api2.messages.send(user_id=current_user_id, message="Your task is cancelled!", random_id=count) - return 0, count + 1 - elif have_a_task: - vk_api2.messages.send(user_id=current_user_id, message="Sorry, I am working on the other user's task!", - random_id=count) - return 1, count + 1 - else: - vk_api2.messages.send(user_id=current_user_id, message="Я сделал ничего, не благодари!", - random_id=count) - return 0, count + 1 - - -def say_hello(count, current_user_id): - # sends a message 'Ну привет, ....' - string = "Ну привет, " - value = vk_api2.users.get(user_ids=current_user_id, fields='first_name') - string += value[0]['first_name'] - vk_api2.messages.send(user_id=current_user_id, message=string, random_id=count) - return count + 1 - - -def instruction_message(count, current_user_id): - # sends the message if user did send us the message of an unknown format - string = "Неправильный формат данных! Я понимаю примитивные приветствия, команду 'stop' для отмены" + \ - "и формат: 'group_id: ....; period: ....' Подумай головой, пока не забанил тебя." - vk_api2.messages.send(user_id=current_user_id, message=string, random_id=count) - return count + 1 - - -def repeat_the_process(master_id, count, next_time): - # current_minutes = time_start.minute - next_time = count_new_time(next_time, group.frequency) - count = group.work_and_print(count, master_id) - return next_time, count - - -def first_process(count, master_id, group): - t0 = time.time() - time_start = datetime.datetime.now() - next_time = count_new_time(time_start, group.frequency) - # next_minutes - when to count again - count = group.work_and_print(count, master_id) - t0 = time.time() - t0 - # if analysing took more time than the period: - if t0 > group.frequency * 60: - vk_api2.messages.send(user_id=master_id, message="Can't work so fast((", - random_id=count) - next_time = datetime.datetime.now() - group.frequency = 0 - return next_time, count + 1 - return next_time, count - - -def check_for_correct(group_id): - try: - your_group_info = vk_api.groups.getById(group_id=group_id, fields='members_count', count=1) - except vk.exceptions.VkAPIError: - return 0 - return 1 - - - -# starts working, gives default values to some variables -some = vk_api2.groups.getLongPollServer(group_id=my_number_group_id) -current_ts = some['ts'] -server = some['server'] -key = some['key'] -message = "" -run = 1 -count = 0 -have_a_task = 0 -group = Group(-1, -1) -master_id = -1 -while run: - message, current_user_id, current_ts = get_message(my_number_group_id, server, current_ts, key) - if have_a_task and datetime.datetime.now() >= next_time: - next_time, count = repeat_the_process(master_id, count, next_time) - - code, analyse_group_id, frequency_number = handle_input_message(message) - - if code == 3: - if not have_a_task or current_user_id == master_id: - have_a_task = 1 - master_id = current_user_id - group.group_id = analyse_group_id - group.frequency = frequency_number - next_time, count = first_process(count, master_id, group) - else: - vk_api2.messages.send(user_id=current_user_id, message="Sorry, I am busy!", random_id=count) - count += 1 - elif code == 2: - run, count = switch_off(count, current_user_id) - elif code == 1: - have_a_task, count = cancel_the_task(have_a_task, current_user_id, count, master_id) - elif code == 4: - count = say_hello(count, current_user_id) - elif code == -1: - count = instruction_message(count, current_user_id) - elif code == 5: - count = incorrect_id(count, current_user_id) - -''' - # time when we start - in seconds and in a special type - t_start = time.time() - time_start = datetime.datetime.now() - current_minutes = time_start.minute - next_time = count_new_time(time_start, frequency_number) - # next_minutes - when to count again - group.group_id = analyse_group_id - group.frequency = frequency_number - count = group.work_and_print(count, current_user_id) - t_finish = time.time() - t_start - # if analysing took more time than the period: - if t_finish > frequency_number * 60: - vk_api2.messages.send(user_id=current_user_id, message="Can't work so fast((", - random_id=count) - count += 1 - next_time = datetime.datetime.now() - frequency_number = 0 - group.frequency = 0 - ''' \ No newline at end of file From 162907139f258691e9bb996cd21bfa59692aa28b Mon Sep 17 00:00:00 2001 From: lpetrov02 <71082527+lpetrov02@users.noreply.github.com> Date: Tue, 9 Feb 2021 23:39:07 +0300 Subject: [PATCH 4/5] Add files via upload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit текущая версия многопользовательского режима --- analyse_and_bot_functions.py | 443 +++++++++++++++++++++++++++++++++++ begin_task.txt | 8 + group_class.py | 415 ++++++++++++++++++++++++++++++++ main.py | 219 +++++++++++++++++ super_vk_bot3.db | Bin 0 -> 266240 bytes 5 files changed, 1085 insertions(+) create mode 100644 analyse_and_bot_functions.py create mode 100644 begin_task.txt create mode 100644 group_class.py create mode 100644 main.py create mode 100644 super_vk_bot3.db diff --git a/analyse_and_bot_functions.py b/analyse_and_bot_functions.py new file mode 100644 index 0000000..83683a4 --- /dev/null +++ b/analyse_and_bot_functions.py @@ -0,0 +1,443 @@ +import vk +import datetime +import requests +import json +# from new_main import vk_api2 as vk_api2 +# from new_main import month_length as month_length +import group_class as gc +import time +import random +import math + + +token = "65e6efa565e6efa565e6efa54f6593fb1f665e665e6efa53a5c6937a4636b3416a8bd92" +group_token = "17e681fbe171945431a04f1abc752d41ff888698288abf74124de4e782c67f36e76484601991870f56b7a" +analyse_group_id = 'memkn' +my_group_id = 'memkn_funclub' +my_number_group_id = 200698416 + +session1 = vk.AuthSession(access_token=token) +session2 = vk.AuthSession(access_token=group_token) +vk_api = vk.API(session1, v=5.92) +vk_api2 = vk.API(session2, v=5.92) + +month_length = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] +days_of_the_week = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] + + +def get_message(group_id, server_, ts_, key_): + # gets the message from the user + response = requests.get('{server}?act=a_check&key={key}&ts={ts}&wait=25'.format + (server=server_, key=key_, ts=ts_)).json() + if 'updates' in response and len(response['updates']) > 0: + return response['updates'][0]['object']['body'], response['updates'][0]['object']['user_id'], response['ts'] + return "", -1, response['ts'] + + +def find_the_task(group_id, user_id, tasks): + for x in range(len(tasks)): + if tasks[x]['master'] == user_id and tasks[x]['group'].group_id == group_id: + return x + return -1 + + +def find_the_group(group_id, tasks): + for x in tasks: + if x['group'].group_id == group_id: + return 1 + return 0 + + +def process_input_message(message): + # gets the massage from the user and returns a code which depends on the message type. Also returns the group id + # and needed frequency. These fields will be used afterwards only if the message is: group_id: ...; period: ... + if message == "": + return 0, "" + if message[0] == '~': + if not check_recommend_time(message): + return -1, "" + if len(message) < 7 or not check_for_correct(message[6:].strip()): + return 5, "" + return 12, message + if message == "Начать": + return 14, "" + if message == "Что обрабатывается для меня?": + return 15, "" + if message == "Сколько свободных мест?": + return 13, "" + if message == "Текущий процент: ВКЛ": + return 17, "1" + if message == "Текущий процент: ВЫКЛ": + return 17, "0" + if message == "Set time": + return 11, "" + if message == "Want to give a task": + return 10, "" + if message.lower() == "help": + return 9, "" + if len(message) > 4 and (message[0: 4] == "stop" or message[0: 4] == "Stop") and message[4] == ':': + group_to_stop = message[5:].strip() + if not check_for_correct(group_to_stop): + return 5, group_to_stop + return 1, group_to_stop + if message == "lsr_memkn6": + return 2, "" + string1 = message[0: 6] + string2 = message[0: 5] + if string1 == "Привет" or string1 == "привет" or string2 == "Hello" or string2 == "hello": + return 4, "" + if len(message) > 13 and (message[0: 13] == "Recommend day" or + message[0: 13] == "recommend day") and message[13] == ':': + recommend_group = message[14:].strip() + if not check_for_correct(recommend_group): + return 5, recommend_group + return 7, recommend_group + if len(message) > 14 and (message[0: 14] == "Recommend week" or + message[0: 14] == "recommend week") and message[14] == ':': + recommend_group = message[15:].strip() + if not check_for_correct(recommend_group): + return 5, recommend_group + return 8, recommend_group + if message[0] == '$' and " " not in message[1:].strip(): + group = message[1:].strip() + if not check_for_correct(group): + return 5, "" + if not check_group_size(group): + return 16, "" + return 3, group + return -1, "" + + +def get_r_id(): + t = datetime.datetime.now() + random_id = t.minute * 60000000 + t.second * 1000000 + t.microsecond + return random_id + + +def switch_off(current_user_id, tasks): + # switches the bot off, the special password is needed + r_id = get_r_id() + vk_api2.messages.send(user_id=current_user_id, message="Я терпел, но сегодня я ухожу...", random_id=r_id) + value = vk_api2.users.get(user_ids=current_user_id, fields=['first_name', 'last_name']) + string = f"потому что меня выключил {value[0]['last_name']} {value[0]['first_name']}" + for x in tasks: + r_id = get_r_id() + vk_api2.messages.send( + user_id=x['master'], + message=f"Ваш запрос по группе '{x['group'].name}' отменён, {string}", + random_id=r_id) + return 0 + + +def incorrect_id(current_user_id): + # informs about the mistake + r_id = get_r_id() + vk_api2.messages.send(user_id=current_user_id, message="Группы с таким айдишником нету как бэ", random_id=r_id) + return + + +def cancel_the_task(have_a_task, index_to_delete, current_user_id): + # Cancels the current task, it is is asked by the user who gave the task earlier + r_id = get_r_id() + have_a_task.pop(index_to_delete) + vk_api2.messages.send(user_id=current_user_id, message="Штош, отменяю!", + random_id=r_id) + return + + +def say_hello(current_user_id): + # sends a message 'Ну привет, ....' + r_id = get_r_id() + string = "Ну привет, " + value = vk_api2.users.get(user_ids=current_user_id, fields='first_name') + string += value[0]['first_name'] + something = vk_api2.messages.send(user_id=current_user_id, message=string, random_id=r_id) + return + + +def instruction_message(current_user_id): + # sends the message if user did send us the message of an unknown format + r_id = get_r_id() + string = "Я вас не понимаю! Пожалуйста, прочитайте инструкцию! " + string += "Получить инструкцию можно, нажав на кнопочку 'help'" + + # attachment="photo-200698416_457239022" + vk_api2.messages.send( + user_id=current_user_id, message=string, random_id=r_id + ) + return + + +def check_recommend_time(time_string): + if time_string[3] != ':' or not time_string[1: 3].isdigit() or not time_string[4: 6].isdigit(): + return 0 + hours = int(time_string[1: 3]) + if 0 <= hours < 24: + return 1 + return 0 + + +def send_big_instruction(current_user_id): + # sends the instruction for the user + r_id = get_r_id() + + kb = \ + { + "one_time": False, + "buttons": [ + [ + { + "action": { + "type": "text", + "payload": "{\"button\": \"2\"}", + "label": "help" + }, + "color": "positive" + }, + { + "action": { + "type": "text", + "payload": "{\"button\": \"2\"}", + "label": "Сколько свободных мест?" + }, + "color": "primary" + } + ], + [ + { + "action": { + "type": "text", + "payload": "{\"button\": \"2\"}", + "label": "Что обрабатывается для меня?" + }, + "color": "secondary" + } + ], + [ + { + "action": { + "type": "text", + "payload": "{\"button\": \"2\"}", + "label": "Текущий процент: ВКЛ" + }, + "color": "positive" + }, + { + "action": { + "type": "text", + "payload": "{\"button\": \"2\"}", + "label": "Текущий процент: ВЫКЛ" + }, + "color": "negative" + } + ] + ] + } + + kb = json.dumps(kb, ensure_ascii=False).encode('utf-8') + kb = str(kb.decode('utf-8')) + + text = "Вот что я понимаю и умею:\n 1) $group_id - где вместо group_id айдишник нужной группы. " + \ + "Я приму группу на обаботку и буду писылать отчёты. \n\n" + \ + "2) recommend day: group_id - именно в таком фомате. Я пришлю лучшее время для постов за сегодня\n\n" + \ + "3) recommend week: group_id - именно в таком фомате." + \ + "Я пришлю лучшее время для постов за текущую неделю\n\n" + \ + "4) ~22:00 group_id - тут минуты значения не имеют, можно всегда писать 00, а вместо 22 - любое число" + \ + ", не большее 23, конечно. Тогда отчёты по группе group_id будут приходить в указанный час\n\n" + \ + "5) Stop: group_id - и я сразу перестану следить за группой group_id\n\n" + \ + "Можно здооваться - 'привет' или 'hello' а начале предложения\n\n" + + vk_api2.messages.send( + user_id=current_user_id, + message=text, + # attachment="photo-200698416_457239023", + random_id=r_id, + keyboard=kb + ) + return + + +def send_keyboard(current_user_id): + # sends the instruction for the user + r_id = get_r_id() + + kb = \ + { + "one_time": False, + "buttons": [ + [ + { + "action": { + "type": "text", + "payload": "{\"button\": \"2\"}", + "label": "help" + }, + "color": "positive" + }, + { + "action": { + "type": "text", + "payload": "{\"button\": \"2\"}", + "label": "Сколько свободных мест?" + }, + "color": "primary" + } + ], + [ + { + "action": { + "type": "text", + "payload": "{\"button\": \"2\"}", + "label": "Что обрабатывается для меня?" + }, + "color": "secondary" + } + ], + [ + { + "action": { + "type": "text", + "payload": "{\"button\": \"2\"}", + "label": "Текущий процент: ВКЛ" + }, + "color": "positive" + }, + { + "action": { + "type": "text", + "payload": "{\"button\": \"2\"}", + "label": "Текущий процент: ВЫКЛ" + }, + "color": "negative" + } + ] + ] + } + + kb = json.dumps(kb, ensure_ascii=False).encode('utf-8') + kb = str(kb.decode('utf-8')) + + vk_api2.messages.send( + user_id=current_user_id, + message="Лови клавиатуру)", + random_id=r_id, + keyboard=kb + ) + return + + +def check_for_correct(group_id_to_check): + your_group_info = vk_api2.groups.getById(group_id=group_id_to_check, fields='members_count', count=5) + if your_group_info[0]['id'] == my_number_group_id and group_id_to_check != 'memkn_funclub': + return 0 + return 1 + + +def not_available(current_user_id): + r_id = get_r_id() + vk_api2.messages.send(user_id=current_user_id, message="Временно недоступно!", random_id=r_id) + vk_api2.messages.send(user_id=current_user_id, sticker_id=4331, random_id=(r_id + 1)) + return + + +def cannot_receive_more_tasks(current_user_id): + r_id = get_r_id() + vk_api2.messages.send(user_id=current_user_id, message="Извини, я уже завален работой...", random_id=r_id) + vk_api2.messages.send(user_id=current_user_id, sticker_id=2, random_id=(r_id + 1)) + return + + +def have_such_a_task_already(current_user_id): + r_id = get_r_id() + vk_api2.messages.send(user_id=current_user_id, message="Чел, ты... уже давал мне такое задание...", random_id=r_id) + vk_api2.messages.send(user_id=current_user_id, sticker_id=6158, random_id=(r_id + 1)) + return + + +def no_such_a_task(current_user_id): + r_id = get_r_id() + vk_api2.messages.send( + user_id=current_user_id, message="Вы мне такой группы на обработку не давали...", random_id=r_id + ) + vk_api2.messages.send(user_id=current_user_id, sticker_id=6559, random_id=(r_id + 1)) + return + + +def free_places(current_user_id, number): + r_id = get_r_id() + number = 10 - number + if number > 4: + vk_api2.messages.send( + user_id=current_user_id, message=f"Могу принять ещё {number} запросов", random_id=r_id + ) + elif number == 1: + vk_api2.messages.send( + user_id=current_user_id, message="Могу принять ещё один запрос", random_id=r_id + ) + elif 1 < number < 5: + vk_api2.messages.send( + user_id=current_user_id, message=f"Могу принять {number} запроса", random_id=r_id + ) + else: + vk_api2.messages.send( + user_id=current_user_id, message="Мне жаль, но я загужен до отказа((", random_id=r_id + ) + return + + +def task_by_button(current_user_id): + r_id = get_r_id() + vk_api2.messages.send( + user_id=current_user_id, + message="Send the group_id and the period with a whitespace between them and '$' in the beginning", + random_id=r_id + ) + + +def user_groups(current_user_id, have_a_task): + r_id = get_r_id() + found_something = 0 + groups = "" + for x in have_a_task: + if x['master'] == current_user_id: + found_something = 1 + groups += x['group'].name + "\n" + if found_something: + vk_api2.messages.send( + user_id=current_user_id, message=("Вот ваши группы: \n" + groups), random_id=r_id + ) + else: + vk_api2.messages.send( + user_id=current_user_id, message="Вы ещё не давали заданий", random_id=r_id + ) + + +def check_group_size(group_id): + your_group_info = vk_api.groups.getById(group_id=group_id, fields='members_count') + number_of_members = your_group_info[0]['members_count'] + if number_of_members > 500000: + return 0 + return 1 + + +def too_big_group(current_user_id): + r_id = get_r_id() + vk_api2.messages.send( + user_id=current_user_id, + message="Ваша группа слишком большая, её обработка существенно меня затормозит, так что прошу простить", + random_id=r_id + ) + + +def change_recommend_time(begin_file, task_number, return_message): + file = open(begin_file, "r") + commands = list(file.read().split('\n')) + file.close() + file = open(begin_file, "w") + for cmd_number in range(len(commands)): + if cmd_number != task_number: + file.write(commands[cmd_number] + '\n') + else: + ind = commands[cmd_number].rfind(" ") + file.write(commands[cmd_number][:ind] + f" {int(return_message[1: 3])}" + '\n') + file.close() diff --git a/begin_task.txt b/begin_task.txt new file mode 100644 index 0000000..4a097bb --- /dev/null +++ b/begin_task.txt @@ -0,0 +1,8 @@ +192003983 427479334 0 +171934615 427479334 0 +200698416 427479334 0 +136245663 427479334 0 +52298374 427479334 0 +336 427479334 0 +171934615 151408129 0 +172053584 151408129 0 \ No newline at end of file diff --git a/group_class.py b/group_class.py new file mode 100644 index 0000000..b499f0a --- /dev/null +++ b/group_class.py @@ -0,0 +1,415 @@ +import vk +import datetime +import sqlite3 as sql +import requests +import time +import random +import analyse_and_bot_functions as func +import math + +# from new_main import vk_api2 as vk_api2 +# from new_main import days_of_the_week as days_of_the_week + +con = sql.connect('super_vk_bot3.db') +cur = con.cursor() + +token = "65e6efa565e6efa565e6efa54f6593fb1f665e665e6efa53a5c6937a4636b3416a8bd92" +group_token = "17e681fbe171945431a04f1abc752d41ff888698288abf74124de4e782c67f36e76484601991870f56b7a" +analyse_group_id = 'memkn' +my_group_id = 'memkn_funclub' +my_number_group_id = 200698416 + +session1 = vk.AuthSession(access_token=token) +session2 = vk.AuthSession(access_token=group_token) +vk_api = vk.API(session1, v=5.92) +vk_api2 = vk.API(session2, v=5.92) + +month_length = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] +days_of_the_week = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] + + +class Group: + def __init__(self, group_id, master_id): + """ + group_id: the group to analyse + master_id: id of the user who gave the task + frequency: how many minutes should pass between two adjoining analyses + analyses_per_day: daily amount of analyses + percents: online percents in each 'moment' of the week (not more than 672 moments) + archive: old data, we need this to delete old info whe 4 weeks pass + number: technical moment. Actually, first three weeks are special: to count the average meaning we need not to + divide by 4, but to divide by the number - spacial for each array cell. + Just because we don't have enough information yet!! + index_to_date: in the storage we keep the moment of time as a code - gust an integer number. But the user + would prefer 'Mon, 00:00: 30%' to '0: 30%'. That's why we need this array + """ + self.recommend_hour = 0 + self.group_id = group_id + group_object = vk_api2.groups.getById(group_id=group_id, fields=["name"]) + self.name = str(group_object[0]['name']) + self.master_id = master_id + self.period = 15 + self.analyses_per_day = 96 + self.current_percents = 1 + + # con = sql.connect('vk_bot.db') + name = "stats" + str(self.group_id) + str(self.master_id) + with con: + # cur = con.cursor() + cur.execute(f"CREATE TABLE IF NOT EXISTS {name} (" + "'analyse_number' INTEGER PRIMARY KEY, " + "'average_percent' INTEGER, " + "'archive1' INTEGER, " + "'archive2' INTEGER, " + "'archive3' INTEGER, " + "'archive4' INTEGER, " + "'weeks_passed' INTEGER, " + "'time' STRING" + ")" + ) + sq = f"SELECT * FROM {name} WHERE analyse_number={0}" + cur.execute(sq) + table_existed = cur.fetchone() + con.commit() + if table_existed is None or table_existed == []: + for j in range(7): + for i in range(self.analyses_per_day * j, self.analyses_per_day * (j + 1)): + s = days_of_the_week[j] + ", " + hours = (i % self.analyses_per_day) * self.period // 60 + minutes = (i % self.analyses_per_day) * self.period % 60 + if hours < 10: + s += "0" + s += str(hours) + ":" + if minutes < 10: + s += "0" + s += str(minutes) + stats = (i, 0, 0, 0, 0, 0, 0, s) + cur.execute(f"""INSERT OR REPLACE INTO {name} VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", stats) + con.commit() + + def del_table(self): + # con = sql.connect('vk_bot.db') + name = "stats" + str(self.group_id) + str(self.master_id) + # cur = con.cursor() + cur.execute(f"""DELETE FROM {name}""") + con.commit() + + def count_online_proportion(self): + """ + gets the number of members online, ant the total number of members, not counting those, + whose info is not is not available + :param self: the object of the class Group. We need the group id + :return: two integers - the number of members with available online-information and the number of members online + """ + online = 0 + your_group_info = vk_api.groups.getById(group_id=self.group_id, fields='members_count') + number_of_members = your_group_info[0]['members_count'] + number_of_members1 = number_of_members + already_count = 0 + while already_count < number_of_members: + group_members_ids = vk_api.groups.getMembers(group_id=self.group_id, offset=already_count, fields='online') + for x in group_members_ids['items']: + if 'online' in x: + online += x['online'] + else: + number_of_members1 -= 1 + already_count += 1000 + if number_of_members1 == 0: + return -1, -1 + else: + return number_of_members1, online + + def group_analyse(self): + """ + counts the online percent + :return: + """ + all_members, online_members = self.count_online_proportion() + percent = online_members / all_members * 100 + percent = math.ceil(percent) + + return percent + + def update_data(self, new_one, cell_to_update): + # con = sql.connect('vk_bot.db') + # cur = con.cursor() + name = "stats" + str(self.group_id) + str(self.master_id) + + sq = f"SELECT * FROM {name} WHERE analyse_number={cell_to_update}" + cur.execute(sq) + values_arr = cur.fetchall() + values_tuple = values_arr[0] + values = [] + for i in range(7): + values.append(values_tuple[i]) + print(values) + if values[6] >= 4: + values[1] *= 4 + values[1] = values[1] - values[2] + new_one + values[1] /= 4 + else: + values[1] *= values[6] + values[1] += new_one + values[6] += 1 + values[1] /= values[6] + for j in range(3): + values[2 + j] = values[3 + j] + values[5] = new_one + + stats = (values[0], values[1], values[2], values[3], values[4], values[5], values[6], values_tuple[7]) + cur.execute(f"""INSERT OR REPLACE INTO {name} VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", stats) + con.commit() + + def recommendation_for_this_day_of_the_week(self): + """ + this function runs at about 00:00 daily and recommends: when it is going to be the best time for posts this day. + :return: returns nothing, just sends a message with recommendation + """ + # average stats and recommendation for this day of the week past 4 last weeks + + # con = sql.connect('vk_bot.db') + # cur = con.cursor() + name = "stats" + str(self.group_id) + str(self.master_id) + + r_id = func.get_r_id() + + today_or_tomorrow = "сегодня" + day = datetime.datetime.now().weekday() + if self.recommend_hour != 0: + day = (day + 1) % 7 + today_or_tomorrow = "завтра" + + start = day * self.analyses_per_day + finish = (day + 1) * self.analyses_per_day + check = 0 + + for t in range(start, finish): + sq = f"SELECT weeks_passed FROM {name} WHERE analyse_number={t}" + cur.execute(sq) + checker = cur.fetchone() + check += checker[0] + if check == 0: # Если за этот день недели вообще нет данных + day = (day - 1) % 7 # то возьмём данные за предыдущий день + start = day * self.analyses_per_day + finish = (day + 1) * self.analyses_per_day + + recommend_message = f"Вероятно, {today_or_tomorrow} лучше всего выкладывать посты в группе " + \ + f"'{self.name}' в " + max_online, best_time = 0, 0 + for i in range(start, finish): + sq = f"SELECT average_percent FROM {name} WHERE analyse_number={i}" + cur.execute(sq) + current_percent = cur.fetchone()[0] + if current_percent > max_online: + max_online = current_percent + best_time = i + + sq = f"SELECT time FROM {name} WHERE analyse_number={best_time}" + cur.execute(sq) + recommend_time = cur.fetchone()[0] + recommend_message += recommend_time[5:] + ": " + str(max_online) + "%" + vk_api2.messages.send(user_id=self.master_id, message=recommend_message, random_id=r_id) + return + + def get_one_day_information_v1( + self, day, max_summary_percent, day_with_the_highest_summary_percent, best_time, max_online + ): + """ + function to look for highest percents between average percents on a week + :param day: the number of the day to get information about + :param max_summary_percent: the day withe the highest summary online percent + (among those that we have already checked). So, it is the current highest percent for a day. + :param day_with_the_highest_summary_percent: Number of the day when the 'max_summary_percent' was fixed + :param best_time: the moment of time withe highest online percent during the week + :param max_online: that highest percent + :return: updated 'max_summary_percent', 'day_with_the_highest_summary_percent', 'best_time' and 'max_online' + """ + # needed for 'recommendation_for_this_week' + + # con = sql.connect('vk_bot.db') + # cur = con.cursor() + name = "stats" + str(self.group_id) + str(self.master_id) + + summary_percents = 0 + for i in range(self.analyses_per_day * day, self.analyses_per_day * (day + 1)): + sq = f"SELECT average_percent FROM {name} WHERE analyse_number={i}" + cur.execute(sq) + current_percent = cur.fetchone()[0] + summary_percents += current_percent + if current_percent > max_online: + max_online = current_percent + best_time = i + if summary_percents > max_summary_percent: + return summary_percents, day, best_time, max_online + return max_summary_percent, day_with_the_highest_summary_percent, best_time, max_online + + def get_one_day_information_v2( + self, day, max_summary_percent, day_with_the_highest_summary_percent, best_time, max_online + ): + # needed for 'give_this_week_stats' + """ + function to look for highest percents between certain percents on the current week + :param day: the number of the day to get information about + :param max_summary_percent: the day withe the highest summary online percent + (among those that we have already checked). So, it is the current highest percent for a day. + :param day_with_the_highest_summary_percent: Number of the day when the 'max_summary_percent' was fixed + :param best_time: the moment of time withe highest online percent during the week + :param max_online: that highest percent + :return: updated 'max_summary_percent', 'day_with_the_highest_summary_percent', 'best_time' and 'max_online' + """ + # needed for 'recommendation_for_this_week' + + # con = sql.connect('vk_bot.db') + # cur = con.cursor() + name = "stats" + str(self.group_id) + str(self.master_id) + + summary_percents = 0 + for i in range(self.analyses_per_day * day, self.analyses_per_day * (day + 1)): + sq = f"SELECT archive4 FROM {name} WHERE analyse_number={i}" + cur.execute(sq) + current_percent = cur.fetchone()[0] + summary_percents += current_percent + if current_percent > max_online: + max_online = current_percent + best_time = i + if summary_percents > max_summary_percent: + return summary_percents, day, best_time, max_online + return max_summary_percent, day_with_the_highest_summary_percent, best_time, max_online + + def recommendation_for_this_week(self): + """ + function that runs weekly at about 00:00 and sends two messages: day withe the highest average percent and + time(with a day) when the percent was highest + Takes average percents for last four weeks + :return: nothing + """ + # recommendation gives the day with the highest average percents past 4 last weeks + + # con = sql.connect('vk_bot.db') + # cur = con.cursor() + name = "stats" + str(self.group_id) + str(self.master_id) + + r_id = func.get_r_id() + recommend_message = f"Вероятно, на этой неделе лучшим временем для постов в группе '{self.name}' будет " + max_online, best_time, max_summary_during_the_day, best_day = 0, 0, 0, 0 + + for j in range(7): + max_summary_during_the_day, best_day, best_time, max_online = \ + self.get_one_day_information_v1(j, max_summary_during_the_day, best_day, best_time, max_online) + max_average_during_the_day = max_summary_during_the_day / self.analyses_per_day + + sq = f"SELECT time FROM {name} WHERE analyse_number={best_time}" + cur.execute(sq) + recommend_time = cur.fetchone()[0] + recommend_message += recommend_time + ": " + str(max_online) + "%" + + vk_api2.messages.send(user_id=self.master_id, message=recommend_message, random_id=r_id) + recommend_message = "This week, the day with the biggest average online percent was " + \ + days_of_the_week[best_day] + ": " + str(max_average_during_the_day) + "%" + vk_api2.messages.send(user_id=self.master_id, message=recommend_message, random_id=(r_id + 1)) + return + + def give_today_stats(self): + """ + Gives today's time with the highest online percent + :return: nothing + """ + # Just today's stats + + # con = sql.connect('vk_bot.db') + # cur = con.cursor() + name = "stats" + str(self.group_id) + str(self.master_id) + + r_id = func.get_r_id() + + day = datetime.datetime.now().weekday() + + start = day * self.analyses_per_day + finish = (day + 1) * self.analyses_per_day + + recommend_message = f"Сегодня лучшим для постов временем в группе '{self.name}' было " + + max_online, best_time = 0, 0 + for i in range(start, finish): + sq = f"SELECT archive4 FROM {name} WHERE analyse_number={i}" + cur.execute(sq) + current_percent = cur.fetchone()[0] + if current_percent > max_online: + max_online = current_percent + best_time = i + + sq = f"SELECT time FROM {name} WHERE analyse_number={best_time}" + cur.execute(sq) + recommend_time = cur.fetchone()[0] + recommend_message += recommend_time[5:] + ": " + str(max_online) + "%" + vk_api2.messages.send(user_id=self.master_id, message=recommend_message, random_id=r_id) + return + + def give_this_week_stats(self): + """ + does the same as the 'recommendation_for_this_week' but can be summoned by the user every moment. It also take + certain percents of the current week, not average + :return: nothing + """ + # Just this week stats + + # con = sql.connect('vk_bot.db') + # cur = con.cursor() + name = "stats" + str(self.group_id) + str(self.master_id) + + r_id = func.get_r_id() + recommend_message = f"На этой неделе лучшим для постов в группе '{self.name}' временем было " + max_online, best_time, max_summary_during_the_day, best_day = 0, 0, 0, 0 + + for j in range(7): + max_summary_during_the_day, best_day, best_time, max_online = \ + self.get_one_day_information_v2(j, max_summary_during_the_day, best_day, best_time, max_online) + max_average_during_the_day = max_summary_during_the_day // self.analyses_per_day + 1 + + sq = f"SELECT time FROM {name} WHERE analyse_number={best_time}" + cur.execute(sq) + recommend_time = cur.fetchone()[0] + recommend_message += recommend_time + ": " + str(max_online) + "%" + vk_api2.messages.send(user_id=self.master_id, message=recommend_message, random_id=r_id) + + recommend_message = "This week, the day with the biggest average online percent was " + \ + days_of_the_week[best_day] + ": " + str(max_average_during_the_day) + "%" + vk_api2.messages.send(user_id=self.master_id, message=recommend_message, random_id=(r_id + 1)) + return + + def work_and_print(self): + """ + updates data and sends current online percent + :return: + """ + r_id = func.get_r_id() + + week_day = datetime.datetime.now().weekday() + t = datetime.datetime.now() + + array_cell = week_day * self.analyses_per_day + (t.hour * 60 + t.minute) // self.period + percent = self.group_analyse() + + self.update_data(percent, array_cell) + + if self.current_percents: + string = f"В группе '{self.name}' сейчас онлайн {str(percent)}% участников" + vk_api2.messages.send(user_id=self.master_id, message=string, random_id=r_id) + return + + def calculate_new_analyse_time(self): + """ + This function runs in the very beginning. It counts when to start analysing and when to give recommendations + """ + r_id = func.get_r_id() + current_time = datetime.datetime.now().replace(second=0, microsecond=0) + current_minutes = current_time.minute + current_time.hour * 60 + minutes_to_wait = self.period - current_minutes % self.period + next_analyse_time = current_time + datetime.timedelta(0, 0, 0, minutes_to_wait // 60, minutes_to_wait % 60, 0, + 0) + next_recommend_time = current_time.replace(microsecond=0, second=0, minute=0, hour=0) + datetime.timedelta( + days=1 + ) + ok_message = f"Йес, май диар! Обработка '{self.name}' начата, результат через {minutes_to_wait} минут!" + vk_api2.messages.send(user_id=self.master_id, message=ok_message, random_id=r_id) + return next_analyse_time, next_recommend_time diff --git a/main.py b/main.py new file mode 100644 index 0000000..8c633a5 --- /dev/null +++ b/main.py @@ -0,0 +1,219 @@ +import vk +import datetime +import requests +import sqlite3 as sql +import group_class as gc +import analyse_and_bot_functions as func +import time +import random +import math + +begin_file = "begin_task.txt" + +token = "65e6efa565e6efa565e6efa54f6593fb1f665e665e6efa53a5c6937a4636b3416a8bd92" +group_token = "17e681fbe171945431a04f1abc752d41ff888698288abf74124de4e782c67f36e76484601991870f56b7a" +analyse_group_id = 'memkn' +my_group_id = 'memkn_funclub' +my_number_group_id = 200698416 + +session1 = vk.AuthSession(access_token=token) +session2 = vk.AuthSession(access_token=group_token) +vk_api = vk.API(session1, v=5.92) +vk_api2 = vk.API(session2, v=5.92) + +month_length = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] +days_of_the_week = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] + +# starts working, gives default values to some variables + +some = vk_api2.groups.getLongPollServer(group_id=my_number_group_id) +current_ts = some['ts'] +server = some['server'] +key = some['key'] +change_server = datetime.datetime.now().replace(microsecond=0, second=0) + datetime.timedelta(minutes=30) +message = "" +run = 1 +count = 10000 +master_id = -1 +have_a_task = [] +# next_recommend = datetime.datetime.now() +# next_time = datetime.datetime.now() + +file = open(begin_file, "r") +task_from_the_previous_session = list(file.read().split('\n')) +for the_task in task_from_the_previous_session: + task = list(the_task.split()) + if len(task) > 0 and task[0] != '-1': + this_task = {} + gr_id = task[0] + group_object = vk_api2.groups.getById(group_id=gr_id, fields=["id"]) + gr_id = str(group_object[0]['id']) + usr_id = int(task[1]) + this_task['master'] = usr_id + rec_hr = int(task[2]) + this_task['group'] = gc.Group(gr_id, usr_id) + this_task['group'].recommend_hour = rec_hr + this_task['next_time'], this_task['next_recommend'] = this_task['group'].calculate_new_analyse_time() + next_time = this_task['next_time'] + hrs = datetime.datetime.now().hour + if hrs > rec_hr: + this_task['next_recommend'] = this_task['next_recommend'].replace(hour=rec_hr) + else: + this_task['next_recommend'] = this_task['next_recommend'].replace(hour=rec_hr) - datetime.timedelta(days=1) + have_a_task.append(this_task) +file.close() + +# the end of 'initialization' block + +while run: + # wait for new requests + message, current_user_id, current_ts = func.get_message(my_number_group_id, server, current_ts, key) + + # if it is time to change LongPollServer + if datetime.datetime.now() >= change_server: + some = vk_api2.groups.getLongPollServer(group_id=my_number_group_id) + current_ts = some['ts'] + server = some['server'] + key = some['key'] + change_server = change_server + datetime.timedelta(minutes=30) + + # chek if it is time to analyse again + if len(have_a_task) > 0 and datetime.datetime.now() >= next_time: + next_time += datetime.timedelta(minutes=15) + for task in have_a_task: + task['group'].work_and_print() + + # check if it is time to give recommendations + for task in have_a_task: + if len(have_a_task) > 0 and datetime.datetime.now() >= task['next_recommend']: + task['group'].recommendation_for_this_day_of_the_week() + if datetime.datetime.now().weekday() == 0: + task['group'].recommendation_for_this_week() + task['next_recommend'] += datetime.timedelta(days=1) + + code, return_message = func.process_input_message(message) + + if code == 3: + analyse_group_id = return_message + group_object = vk_api2.groups.getById(group_id=analyse_group_id, fields=["id"]) + analyse_group_id = str(group_object[0]['id']) + # this code means that user gave a correct task + if len(have_a_task) < 10 and func.find_the_task(analyse_group_id, current_user_id, have_a_task) == -1: + this_task = {} + # group initialising block + file = open(begin_file, "r") + commands = list(file.read().split('\n')) + file.close() + file = open(begin_file, "w") + for command in commands: + if len(command) > 0 and command[0] != '-': + file.write(command + '\n') + file.write(f"{analyse_group_id} {current_user_id} 0") + file.close() + this_task['group'] = gc.Group(analyse_group_id, current_user_id) + # group.fill_in_index_to_date() + this_task['master'] = current_user_id + # counting time when to start and when to give a new recommendation + this_task['next_time'], this_task['next_recommend'] = this_task['group'].calculate_new_analyse_time() + if len(have_a_task) == 0: + next_time = this_task['next_time'] + have_a_task.append(this_task) + elif len(have_a_task) == 10: + # in the case when the user wants to give a new task while bot is already working on the OTHER user's task. + func.cannot_receive_more_tasks(current_user_id) + else: + func.have_such_a_task_already(current_user_id) + elif code == 2: + # if the bot has received a secret password which switches it off + run = func.switch_off(current_user_id, have_a_task) + for x in have_a_task: + x['group'].del_table() + del x['group'] + file = open(begin_file, "w") + file.write("-1 0 0") + file.close() + elif code == 1: + # if the user WHO GAVE A TASK decided to cancel it with a 'stop' or 'Stop' command + group_object = vk_api2.groups.getById(group_id=return_message, fields=["id"]) + return_message = str(group_object[0]['id']) + task_number = func.find_the_task(return_message, current_user_id, have_a_task) + if task_number != -1: + have_a_task[task_number]['group'].del_table() + del have_a_task[task_number]['group'] + file = open(begin_file, "r") + commands = list(file.read().split('\n')) + file.close() + file = open(begin_file, "w") + for cmd_number in range(len(commands)): + if cmd_number != task_number: + file.write(commands[cmd_number] + '\n') + file.close() + func.cancel_the_task(have_a_task, task_number, current_user_id) + else: + func.no_such_a_task(current_user_id) + elif code == 4: + # greeting + func.say_hello(current_user_id) + elif code == -1: + # unknown message + func.instruction_message(current_user_id) + elif code == 5: + # if the group does not exist + func.incorrect_id(current_user_id) + elif code == 7: + # If the user needs today's best online percent and the time it happened + group_object = vk_api2.groups.getById(group_id=return_message, fields=["id"]) + return_message = str(group_object[0]['id']) + task_number = func.find_the_task(return_message, current_user_id, have_a_task) + if task_number != -1: + have_a_task[task_number]['group'].give_today_stats() + else: + func.no_such_a_task(current_user_id) + elif code == 8: + # If the user needs week's best online percent and the time it happened + group_object = vk_api2.groups.getById(group_id=return_message, fields=["id"]) + return_message = str(group_object[0]['id']) + task_number = func.find_the_task(return_message, current_user_id, have_a_task) + if task_number != -1: + have_a_task[task_number]['group'].give_this_week_stats() + else: + func.not_such_a_task(current_user_id) + elif code == 9: + func.send_big_instruction(current_user_id) + elif code == 10: + func.task_by_button(current_user_id) + elif code == 12: + group_id = return_message[6:].strip() + group_object = vk_api2.groups.getById(group_id=group_id, fields=["id"]) + group_id = str(group_object[0]['id']) + task_number = func.find_the_task(group_id, current_user_id, have_a_task) + if task_number != -1: + r_id = func.get_r_id() + have_a_task[task_number]['next_recommend'] = \ + have_a_task[task_number]['next_recommend'].replace(hour=int(return_message[1: 3])) + if datetime.datetime.now().hour <= int(return_message[1: 3]): + have_a_task[task_number]['next_recommend'] -= datetime.timedelta(days=1) + have_a_task[task_number]['group'].recommend_hour = int(return_message[1: 3]) + vk_api2.messages.send( + user_id=current_user_id, + message=f"Отчёты по группе {group_id} буду присылать в {return_message[1:3]}:00!", + random_id=r_id + ) + # group.recommend_hour = int(return_message[1: 3]) + func.change_recommend_time(begin_file, task_number, return_message) + else: + not func.no_such_a_task(current_user_id) + elif code == 13: + func.free_places(current_user_id, len(have_a_task)) + elif code == 14: + func.send_keyboard(current_user_id) + elif code == 15: + func.user_groups(current_user_id, have_a_task) + elif code == 16: + func.too_big_group(current_user_id) + elif code == 17: + for x in have_a_task: + if x['master'] == current_user_id: + x['group'].current_percents = int(return_message) + r_id = func.get_r_id() + vk_api2.messages.send(user_id=current_user_id, message="OK", random_id=r_id) diff --git a/super_vk_bot3.db b/super_vk_bot3.db new file mode 100644 index 0000000000000000000000000000000000000000..a7a87bf9121dd5b9556d92c40751a773d38cf7dd GIT binary patch literal 266240 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 literal 0 HcmV?d00001 From 40d639151ace29bf56df1b24da1f81873ad3022f Mon Sep 17 00:00:00 2001 From: lpetrov02 <71082527+lpetrov02@users.noreply.github.com> Date: Thu, 11 Feb 2021 16:42:58 +0300 Subject: [PATCH 5/5] Delete super_vk_bot3.db --- super_vk_bot3.db | Bin 266240 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 super_vk_bot3.db diff --git a/super_vk_bot3.db b/super_vk_bot3.db deleted file mode 100644 index a7a87bf9121dd5b9556d92c40751a773d38cf7dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 266240 zcmeFa39OxGb@zMrIcLw46Wg&fa-7i#iE{?endgxtIL;j6*v^Ta#C9A{u`{v4fn#@l0K zW0%SA74o|zzn9AIN%`%+3jQyz|9{1?;*zfwzwqnPrd9&p)zv)9%fC_s&0%<@+AmIe*sFts6I7chfazcNaIyOV+I`^seI2 z%&weXw|dRonz`9&8JoGaGy0bfbY&cv@S3A{T+VBhmt1#Up?4d{<-r}B_swtKvv2#} zN4GvSzwglK_`L4_IvL05bzgh*j@RP)>wb<#XPAFRhV+;?KH)V-@3<_jg{5cf_W7N= zXXfVC42M7`Sj2CRBNJbA^bSp19Xn05u9nj$cMUHyWM{(Rdt`(H9(z!yzelv(ZGRy#W6hJFoqd_7B_NZNJ*y z*}k)VMSDg2`1WM$pITpU{YmSStq-)`(R!?PZ|j=YZ0qD!rTH(-Z#4g``RV3wHs9IY z-@LzhLvwX=S+mjj*Ty#+f6;ic@mr1O8iyJWG;VIJZ=BIsQ2%NDJN3V;f3E)9_4m}D zu5Yj3TEDn{c70Lp-)rBi{Z;Ly+V9kUz1FGisNGS!ymnq~X>FqVYV~W?FH}EKeSh_E zb$9ii>ebbm>PgjdW&rKej+%kFN@tXn@fy|8m$pfjr$cFqlST3-9+99_G9rM&i>K;b<* zP%wpzxlSqpQ_>YM}5g3l!d|K;eB;j;>PgDS^U!a-i^@ z6ezqW=IBcGo)9R!#|H}U(m>%|lB09#JuXmq7Y7ROqCnwYn4`1mJvLByj|mjs1%blb z&e0k5wgQE>87RDsK;fGn*3VtqP5kvKyls~pg8ZyK^@QA0~F_N0g7{Dfa1J02X#C*1Srn81}M&3 z0u<-Xqfoy1K0t9^7oa$=4N#odv(LiX7B+>GA-@d0Bwsyfi>@UXp{lE?pd;I4=rNoEHWt&I@u-*QNCVigR6n;#?b` zIM?K$&ZE@M5_!|9g35& zszY%yR5hsch>@y8aWYVKC{D(y4#mkZRiS;&GfH(RP6nwC#mN}ep*R_$8q|5j2-TrD z8K628C*xCx;$(PgQ0Eb&Q-|VYaOzN;j7=SilcA|WokxsJ9g35IsY7uxE_EnQhNT8| z9x*C)C{6~Y4#mls)S);Tk{Z-`#E8_PI2n*S6er_RhvH;7YEb79qfv+AWH9PboQy>s zij$$JL7hj8L>-Egfv7`qG7fbpPKKcdbsjMabtp~-p$^5#7}TLS8G;(rdBh0Rp*R_U z4xl8T?cRCv&lUY~`DeY0f1l_akJ|i1`&Iep*oB(^zuNAm{Qr&4{}&^ZD@eXys4iQy zXyg8r|4+@N{NHwdbOGydrFAN^IOYE{)4DIlW!FY$dc7=>DgT#2Ncq34sh*L zN2dIr1CsLp(PU;;^OltV;}fEB;mlerq$&UBfULqAlJb8JNXq|5*O2M8h)7cYKm3GD zufiIV@_!DS&^y#&!znT=T-k-EIagr^ndA6 z`Q}aef6D&{S<6hy|2?Ni{|a|qonjv(<^M7Yss4|zSp8T?sxRMXss7IanL*5v>i@$a zOj3PWPE!4UI3SYh%NlasXfl)X|5X3yCq(m>RR13ih@|?mhNSvG2V@o2kW~NYfXpHe zIU4H!!~B0%^Zyym|EEXt|I<_d|IaJ`M|(x~SIYlW{-5&y(GxIQtW5cT>i-|6D4yy| z%#r&45p&pp%;38`_5TlpuvyJpQvd&OKqRY|ye0Mjb3in2N&WvE5Y1as|Nn46 zpQiqQ4oK?%AI2fpLO+YW4;G>v9wqs|WS_@s|NkP*{}*Wge{H1y-}3*||DVqP%PN3{ zz$&nskaT#m%BjqTbpC&4C02!W{@+dnrt|;S9hlDlXBH6a58OBWHnk*H`)t$s|KTz_ zt@Y{$bEBmF>goJHMoHr>N%bY(O6UKF10vT4NZyjp|8qdn`TyY>qElu@^Okh}fA|TJ zRA1JRbpD?MvKnhhI{(iB`7e0>e^~#w{C`&K|J9ND|MFD-Pxb$F|KHS%<|gU>KZzXV zdm-KbXSIQJ|KCWvhGg|pV>~d7H9f1B-LU-uO1zcwe}oq{N;HfcAR4S>y%%gPw9|uJ9mURCg2PED9H@b$%H@B=I>HfdrCuDjx){u1n9|vR(G46kl z`~S|?`v19F|8HvjztlNCbN+vE%KuaTpYs2d|9j_0%Kt52(;8dK|5N_Y1YY(&9v;S; zp4CgN`A~ooZ*31yVvdym4}&nN>dSbh`~QanVow7~_y2Q1G;c}w|8qbzZ%Oz64+lh2 zeOW`;rE2ZjDgPgS_1J)<{69A!|Bdqh6Ey!nL-YTZ=Kod8|34uQsB8aY`>X9gYJa@_ z8|nW4^!z_rR=rv^lluQPzL!pa`GV>2DJxDT>QDXussDda+tpjPQvZKu0ZIM;DgVc} ziBKdi|D~4A zh1DwDss8Vk{#5^;KX*zh{TPSVaB;QK(VNQB{eS8HzjXfJ;#b=x(fjIB{eKvl$)evtf2#kd`oDaa`UxPpG$_^obv)Af|0zk}CDy!eG?EY~b<4K} zD7Cv(|HnMC-iK8GAN_>PXx@_Y|KTSj)&I}VeL_0Gh<+cj8f9l6QvE+S zO4Cy9Uz|Drzevyj%fV&j|Bdl4kG1}v=F5%$tN!8IbJatYd&-xVW{Ss6yf*&j!e_=l zF!n#>MZ;ee{9AWvtGwib3ktn!_HEv>V}9?x&HMJwteIIiw{rE&s+F^AR<2n$H@EV2 zaOsK-S6#O8s;P~aU4G+LQ}&{vKKn1F|MLy~Gs63uT7K5%U7L3t+B?5#*Z!Sb=J%X6 zb=^%Hue#={4O6#lxb7{NZMbXdhO6#6cj~Oo2j=%|-ZsB!_xzr%^Sky9Uxxo_&(`e^ z9hjdv0-HSon>zwqnPrd9&p)zv)9%fC_s&0%<@+AmIe*sFts6I7chfazcNaIyOV+I` z^seIY&#s(aw|dRonz`9&8UMMpGdi$=u8e~pUUT%0%Xy9RlIyN3^lsz0Jh)@?zWL32 z_HEz$=+=kk_Z=D?pV$3gC*wH1?rV?U@mgGe-OthJEcVaHkRB7qC%oq99harGu)J;E zKEHGK%-r0X;SlHqi}=lPWa5jC-l1t!%S-0w3cX7>#dgl`d}P9CBV zGvknqzToH`j-{3GN!hz~=hnS@cW>E0`bmlSc@`cJJJ= zX~+ELZTsg($L0w9_&7L6c-7H6I>(mEOV+F@^seCO?2y=E^VaR7Lz9O~;<)5ra`X;L zyI5YbYE_|k83$$CgFAQ3O^=RAj#c82xf-;bS68%gmbDm7_zFhs8K1`Ij8M zgR*oYF1ovSZQi!+p*ry-VK~V5`AphyyMXW^E-2^ z?veh7avYu`z3k{6phn@y@yqDvM#GaK8jpiA`huf&IGXk6#u{Vq8>=0t-cz}>d`77@ z`A-wCjQ`(-|6}ZZt#3E~T+{J)XOr@u`@h=;L+QBklJn0m^e*7%cy4a>@F$`{W)@r& ze?-~;`KbK?br&xVDcY*p*>!6r5g8qgKqummgx4Isx5XpRj-Gdn%KdfnQUGr7ej;^X4b z#1|dKLvu_RnwfQLXJ_YDty-J=)C9aR4o&=TkK&2)$>Z7)h9>;CNAb|S!8MXpzmTfNf9d+g*x2}mseWhtQ-^Z$l@%c=kWt;0`6QTHvy|@t8-8q3|NrXIPe|(jPyPSH*&(g| zvWBGd{~VB&IER?}|2ZJj2tgTv+S!L`oW*;+^Z#RGr|JEFtGc-V?^wJ4?}fsavG(`c zue3kk{#g4x?Wfugwr^>#Z=c?7wSL_CX6s9>&lI+_KHPd|YhUZW*0rtK)=8~W^Pihv zZ+@ZqyUq7CyUiWV+nbj*&uK1dyw><`<13BNH9p#ScjJl11C5&+Ya6FF8ucI5|E~V$ z^-tG7RR5*=p8CD@tLxMC6Y7(-S8IP=`{UZjYrkH5d+p)c#@fZT<+Wq0|5p7@_2uek zs~@R8S3OwWQoXUdx;j;@Reo6cM&*l@PgOoxc}Hb;E4>78pL)t1PU+18GC1Xja8TNj8k|S&=}SG;kq~Fym~)emya~|*W&%&7+wqS z4LPbG_U`q8;{9G1D7@DO3hyxet;pM}QjB2vjoz4AeE7dy_D7@2w!h3#>+I(9P zD8}KuK;b<%P*qc-2p2^3z2J@$9A`Nn&Xox+>BpA)Obw9Pj@`Pg1-^X-h>pEiS# zqI-Iv@Sc{VHs4MS6z_Lgpzuxw3h$eS^p5!l&K(>e@17DUye9_=FJmA#z{^XJjN16} z79^+e@-awG;a!^h(`FH0beH6)jqh=R!n-(7cozi<@4_6l@jW(Bc#jDb-UWfe%YaD# zZg!zumnR~f!pkd>ox;m!A~}V(KKiH4*m_nA6y9o}@Kyqax16K4o|OWHw-_kAlYzoJ zk)yVrF*>rp+iG1O7#=x=m-iz%6>sO+oLASg&NG3^`|Ug(sCYY11q$zzIcodMPXr3@ zp+Mn17%04aP>203bUo`F$h}ri$ac6@=dXpAdv#9X3Y`L z9Vony=BV8-%N;xO+WoTJvU7^}%RM`%@N(16sLrVU;oTf4y!QtR@1`8J`<(9!6kcB8p?_mbK|A+` z*TQ>Gj_Q2t+#M*qcLj>~%lOy+G`(l0!}!;z%{RusPT^(z>l9wbzfSRf8UGr!`NsIy zDZGq-ox;ob*D1V=e~sFFWBltBUdF#p;br{m6kf)^Ms2<^{&fm3<6o!nGX8Z6FXLaM zHs2WkI)#_q>#V(-fWFp-Gg&`B^ z4k!$nNLQ(iWG2!bP`tBDq&uMSW+L4Ig*OxFDz%Z!M7jeCZzj?mPi=h_`hU9rFWvu_?*B{o{|)z6uue*Zht-q$ zg;)Ah{hwKdjfw8+FsX1TUrEd<9Lkt7r7)Q4|HC-h21K(SCJ_#0Jefl{l<{N=VNe4H zW)Kb~ZeRl8P@K#k9Ey|agH^UQyk+*_P@GI29Ey{Vw6elwVhvHxJO5ke;82`Q6C8?@S%O1xGD$F~^N73Q4#mmM zaEIdLUbsVXax2`R&Li%GI}|53!X1i}```}6$!&0j>G^*rjDF_j)?OLU;{%lOTpFN^ z=aL-Md30QW;#?e{I2Q#d&V@Os^XS+B#d%DC;#?4*INLd>^N73M_F+i(|K&!>@Bbeg zJ6G%f%eDSL)mc%LBK1W3Rg|hz|9^V^pX51QSTzbv{r{=|-#ZO-*U)x3x#HTV#OgDw zFFKWFyu6yEZ1=~FtW&#=De6kgUBox;od zqEmQN{-6CAOly3Q@_+5X(Osfh#9J&R+D9in|8F=OH8PXxjm)K#|Knq0$;>+RaI#wH zP(Dj67CMy0iM2w9GM+3I8r0G;D}@fl$wHw+ak5V6P@F6i8q~5etAq~4$s(acak56} zP@F6g8cg^9kIs(N|DW>z;mVNu|5N_Y55o+;HyMKJ`Z=v3^Z4i*B2d=kLV&U+k5whh zU#0o~jOPD~t^a>)0n-1m=~Vwu_5YOrr}}@Y|EK!DeDkE||4HwM=BtkkzfC=>mz?e4 z0Ofl(<^RKwMN)lHq)u>30=(VCu^*jr`d0lXogR zKfPyjRJY@MJcVel)l=ZTr^9Q-+j}Zdc%RHs-IDJ;5vaW1-l0I@Js2pwkLReC3wx{= z+WXah;T{`?ox;nGVW;r2W!R{;6!rGx{xm%W-g_)icy|X1@1r@YU(dZ=fnpqX1`6+v zK;dQTVDDF3^m@!4oWjfG!704kb6!2A+}oC;T9xnd6rvflD&J!Ru~T^2LF^Romo3Cb zb&Ijb9%83>zic9Q3NO2eox;mD;;Z*O?3vxFB^%S!pls;DZDHt8ny9dFR@d2 z*-Y#dUUm~Zg_rHbMs0j=AC9_Hc-c_w6kc`|JB61m#YSy>*;DKkUN#jwh4+@+d(f<+ z$F^d9t+f^RSXXokFAIxK;bmpfDc&zji$=Ay*kf(cDZJN>zB4&74lk>V{#tlhUNma! z8S9Hq;bno*DZH#OI)#@dMx(Z#vB%gcylgUd3NO2iox;mDW23g7vCr5kylgaf3NJg2 zox;mjW23g7vDersylggh3NO2jox;m@W23g7t~v`@0fhGTy%yzDr33NKrZjoN%; z&#_ayUp5^(g?Bpl9&9~h+p)dY=G%(gYi&I{FHm^T4HWP9%{glG?VLdIewlpOd(fuI z-tzETc+VQ8)@9T?Gf;TX2o&%4^g!V~Ek|v>of;^-%L0XWDo}Xel%qD^P6-s=lLLkK zq(I?4F-L8_u@_lK(t445Y({npFT0VQ!pnAKqc-1;%l&BR$UkfiglAXfKmSm$g z-`JDv6yw0AWT)^lD{%@h+memid~4<2ug$k+pzt;Vg}0ugHs5N2;{8?wg|`wYyyYCV z`Bn-P-eREeP6i4uOO!SaHs2WkI)#_O8UGs9@$EAHbqX)zU#IXg{&fm3<6omXzFo$@ zPT^(z>l9wbzfR$0{Ht_uiwxsmr|>fVbqX)zU#IXg{xz!WS(ovzQ+OHwI)#_l9wbzeaUE>oWdz3NPbd zr|>fVbqX)zU!%I7bs7IUg_rTK)0tH`|Nn`?fwA^Kw!hl`qxQ$!zme|$Pxt@Z{(}~~ zQvZMI|JRR$cLnG-S%;C0eM(aQzma@zl zF9Rsw+nMlX0L7V!UItK{nczjC28Ws0WdOyQ30($IoSDdF0L96`MQgSecr$KsC{Bhg z4#ml+#i2MEv>4Q&n=y++aWZ6aC{FI!ITR;1>HPobJd##E`7Cjl%|=P*5jWWk>OA5en?w10a*NHOIJv{-P(GjB zU^A%mi2G{}#mVh8hvMY!nnQ7NbIqX6Bkrv^6eqXV9Ey`WYYwNUGw1(LO8x(-|3CHr zr~ZG>^tCeAVP(TJecXGO@_*gQPS5|#?hVPM0P@|FDQ9RJnV$bQ3`BfWK)%aU{x6># zI|DzP^8eu{L?biV6p(rJmH;J&yD>odEZvZUx-?xMpg6A!P@LBWD9&qgPz!8V2Pn>~ z0u<+!0gCgA9Ms}k%KwKGA)WtE`Ty|4koy1GX=C|ndj8+&`uV?g{{Iy1|6im1|4T>C z|DTxh|CIly{6FRYDgXBqIF_%a`~M|&&ond+vU=GD%Jef1V9NhTHbm0>f1@Ewy8kcL z|A!gQOv?X9XGhBaQ~p1k9V!1$`9D7lZ}9p5i;jkz4&l(V=>+TFZZ0YvC8Vb zy7AvRbNEf=R3iP(8G*{yf9Lc-C4cCgmZSRR-#IlPf*4Pc}M*mnR#Y!poD5 zPT}RrMx$C(>+ocwQ+Ro@(J8!KZ=J%M>i;SKm(M}U|M6w46JuJZR^|+&?tP^D|A))7 zmTS`ef4AfYB;EfvIv^?kU!5Bey&qszfb!*>`u~Ty(2UL_hN)H@*5_z2N_8mF4ue#O zGMN5cr!p*R_!Ius}4Q-|VYcxq6?X-20G#mV5*p*R_vIus{EQ-c~x zGct83P6nn9#mTtTp*R_q8q_eFQK>_5GAMN@PR67T#mSJ=poY+lNF9ok0jWcAG9Gm( zPKKifHGF0_szY(|nhl5IO#T1Ec_i%v)6+QrKe+!t_5Y{*U-CUJtd<3){NKhQ<^MJg zdXOpg|4*m>|5X2%@6ybsfW#aR4P$rTDUg^Wo&O)k9I5`F>i@$JL(2bC{?89Xy8nOp z-C_Ca8k|>8`Ty_}GM)PWbDxm^`1${_v8CGof1=j^Pw1@3oc}*N_5Y{-|CIly{{Phf zpZfphTX&e*uEw)xQ~jR>A4+lhU5tcP1o&V>6 zXx%d9{~VB%|Bo)JssBIK|A(_<`VH&b_d)9aA4VrB|4;Y-4?hf&>Py`+-T%h{(YocD(dq{$3r<5wi9~A$l_@~8B7T;g&7k3r!EM8tbw|HD}Z1Q`P zuS|Y^@?(?lnS5&U!O2@D*H4~4*_!zA#5X6tH1U~<4^O;vV&BAl6W316PMkDR8vp0< zuaAFW{CCISCqtKh{Y)dU@Gy@^-MW95KO6oq&qz6y8q;AOl5#4+#$k3zIhAU|VIGq* zs%5*wJSXK8ULKTk3NKGeIfa);rHpDv{9&GzatbdGOF4y?r=^_2%i~f;wY}#s&r3Ol zmj|Yt!pjxYDZD%~rPLldb(m+SoWjdPQ%>RKsVS%M^06sKwIlv8pPS+o-stC9oL7&|^`8k;-f#cuK*igCDo}Wz%uzjo(tjdQcn<{%@4-OfWhvkO78;8759D5} zHQoOHK;hjND7<@fROefNPoQ|ej|B?v?m*#vG)Hy5^>+md@6JHs-4Q6fOzUkNbiVbO z*E@xmjr~sHWoN%rc(;xIY4&KU{)2(SJ0B>#4+IMD)*RLO*548+yqg1s_x?cP-ISv` z-}?6j3NMTN`Zu;9wSRATExh;SsLr?k-GRb;SD<*mcLoaY9XV?A?e;+7y)969HwFst ztvPD*Z9|~&GU2yzSc#;!e@l2RylnZm*V=r$Dfe2NZ@l#1Ukfj9{dWp4ul+Y_^X>Ya zSMR0hUl%C6y!qb^@LrR9t`h^t3J#BMs2=bo-=43*uN}LcrOhU z@AneY&HFrl=wBQt-tR?$!h2z$@LrIkHoofvg?C+`@U9IMUTy{4s_)mvmwN$D;a!z8 z%;@KAWuWlR<*1GCY@qPY1d8`N9Voo#k5Y>t`YQs3_q;&iJvUHz-<+eio}CjYyk`dr z@A5$5@Qk!p?_;*0z&BVV03U4O<9Z+~P@vl;wZ<+XaK;g~A zzXJ+yCjK2zcr)>@Qk!p?_;*0z&BVV03U4O<9Z+~P@h|8ge#peX0}5{@{vA+wGx6_$ z!kdYIm14eS&i~u}|KDxD+TPi|vwcN-Mf>>nWb2<=UvK?M>yxbywBFHrtaWee8rcCj zxm9WYOYbf4aWCerx^W`q}kGwSTXDul859mukNwy8xZqj@liy%WLP= zmewY!uU5ZS{X+E<)%RBqS9e$Msa{>3sh(6VSAJ0WTiFNry~>9wzg*c@*;Ki{vZ^vw zsh59L{`>Nm%D-QJzWgiYgXOK|o676Tr#W-;FOy~b)AJVJAGrD@F^Z)7mf4cu) zrv)zsl<#A16QuM1`n+ZC3|M2l_ET_Uz@P?t+!t^tCShZ5d!SHPjH*W45^sLK@h z1RRQ!TLKQn$sGZQ;^c;aK@IS@AK*}&+zxOkPVNRc6el+W6k0OHy#R;e9LveBkz@a#~0bo!QDCYkT z#mV&Fp*WfSI}|6Ae}kGlG52>UPNx12#mUUyp*WfN8`Q*!dA~z(GVOOLPGS@0$1fc}XM9oNwZeA`UnzX9@X^A%3s1{ zev&)LcSoN;B&YJ_(H|TB=$y*8NAKAj)vx2;Gl7b?_jI7*?L8GJyiew+ekJ#w2o&B! zfx>$*Pr|>eJ zFlyt=e8MTbOembf%Z$P)yi6&K+W0c3a0)Mz3a9Wgt8fZ0(+Z`QR`i%xIE9yqg;RK$ zSvZB4sfAHZEqcr?oWjfG!YRDWE}X*4^unmEXUs30!pj81DZI=uoWjc#!>Fxi%rTt8 z%Ot}oyv#D3!pk(nsI6zrGn~T9M8he(%ru(S)l9wbzeaU@ zyNrLG!pr#ADZGq-ox;ob*Qkzfm+`Mtcp3jXg_rTKQ+OHw8rAXbGX8Z6FXLaQ@G|~& z3NPbdr4~PQ8UH$km+`Mtcp3jXg_rTKQC-ivjDMZN%lOwRyo`UH!pr#AsIF&S#=lPC zW&GO8UH$km+`Mtcp3j1 z)%C2)_}3}CjDMZZX#GDu|1Z`5Q~jS6?o|J`j)z(8gJ5K1RKLkm{XflQ_&beab^k{3UvcJQ_mPcac0UH11Qc+HDdt9nJH!{)XnQmEn@)1nJHxqpg1#? zi~$s9rjVgf3vrn`#sG>lQ^puTab~I*11Qc+5ksLCi83{e0TgGZgfW2P%v3N2P@I_p zhC(a#m%FpPc z0L6J>fa1I$2X$RqAD}qb1t`w70g7`?4(dEw9iTW@1t`vy0g7`j2X!9J1}M&%0L3{S zpg7OZL7hkJ!_jFdX8^^?HXMiIWEYM@ak2@=pw6R||0Ckm0g+T+#`EOSkx%(Q<53%> zl>g^GA#YIr|3YEQ|DylDuqE99`1&^hzTOD{YyV%|k#D1d%=JQb*)hjxd#a<8>cCq_5+T0QyEWq9KhUdA_0;bnkh)HvUqI;}l+oI!@tbtm71320KQr(}~fJQ+OHfIE9z-j#GFU z@EEoE#)!u$ybO7q!poS)DZC7NjM{u-)Z-N1vvZ#vJ^9gH9w@wL<*3cKGXsV9j6m^z zPY)E{({j}2+o^%VyDU(6rviodO+$Le`~$YvDaHM{Rsh2o&Dq z1BG{KpztoqQ5)am0)=;RpztmV6yAk7YU6uspzt0OD7=h^Y;>k|-D!`$cF?)m3KZUE zpzt;V#rv)2sI6zUK;f+h3U4J)c*{9z>scvKc#DC;I~gdv+@I6Yv4_id$8)dM?6O-3 z6yC8w#oKu{M|C~xJQJwA-_Fy4insGrpzuDKqq?4To(L4)LxIA3Fi?0O&rw~^ItK!U zcYmPp?h6#&y*aA$t+OXkcpnQC-ra%1`)H2peCzBA6yBYI!n-3-cpu48oo}6o1BI8{ zcJ|rP$2WGkZ|4+VZrmxg_@Tp{JE!n+>&_|OFZb@8!pqG&qqe`iW%T{p$}eSzZrzAaFA?+p~*dva9chtA!B!h2Vs@G}0jzp*}Nw!`?>sLeOVzfR$0 z{Oc56#=lPSei{E7wfV;Q*D1V=f1Se1_}3}CjDL;Vd}I9U6kf)^PT^(z>l9wbzea7o zG5&Q5FXLaQ@G|~&3NPbdqc-0d|2l=2@vl>O8UH$km+`Mrn{SMNox;ob*D1V=f1Sdc ziGQ&xZ5z;?Or$%YFk~X#0fiwG=_<96%tX2aig%WYbO#jPOr$%Y@Ma=ir8bh8NOwTt z%|yBb3U4OT9Z+~Pk*-o3$xNg>pzvlQ-2sI+6X^~pyqQQBR3n9S{{Lsa|Nr%}|8)M} zYN&RWU03mR{(oB6o^<}-R`GQHUmAs_oZ4d)qGuk3%G#dJ|I5wI7zainIy&}%v2^}_ zM(>9GsPDfWZdh2u2k|8l2l%Kw+yj>jt92VobE zQ{=tu!ZE5<;NG#r`m|Gg&e(Ha^9AKsAr|Nf^u#J>H#_ABkrw?Ec?Py4C% zgY8?|>)WTdTdg0rzS;Ux>ockQ|Mk27{g(gqfB*MJVBukQ2<~7p9gBzA zA?y@hb_hF#mmR`R;bn)gQSDef%no6v@UlbLDZK0ub_y>$gpKOn?qPNaJB63AiBovl zA?#GVeRc>N)n1oAJA|Ezx6ck?r{e9iL)a<2><~7pv1*?k!cO63hpP8vP0M@y!(dHu2XpT=BUoM{+>YLeJoITcLxeDYZLaT>3r+&%Dq<4r1se% z?5~BF9l}oGWrwg)oo{`12s_35Wrwg+co~~Gg_j+|O07e<&kkXy@UlbLDZK0ub_y>$ zgpKNa>$5}HDZK0ub_y>$gq_064q>A@-}>wjb_y>$gq_064q>P8vP0OY&bK~0gq_06 z4q>P8vP0M@yzCG*YV(a9!cO63hpH;bn)gQ@mex2s?$B9l}O!zOh5t zDZK2>aSAUxgq_064q>A<-`FAS6kc`+JB61W!cO63hp<*4EPm);%<;w3u7$$OzF~hY zyzCox3NQPHjoSFKZ`dik>>G9pFZ+g_!ppv4qc*XT#nlK z&IStaOrY>i2a5N5{wTHhp}!(fc)2&H?^j+6@44Z%@V+@mZ9O|DPc!3h!w-YU|mlfx^2iPbd zZ#wBO9iEsM+{d%g(S4?L(%*VK-hh+-+{eq@otDo3+ZoX}#r3o6i)#N~`(Ev@YA@A(r}pc$PHji+j@sq5^J+_L6V+F%U#oth`ibiM ztB0$*tM^o|uFh0Xs+KE1sQhi^imFp|3DpQqu`A6lyFMp~0`{n1$ zzfwL}-det?ysmtDxn256>D#53OMg)MXz5o=PnEWnHk2+ZEiWxBzE=E);w!~JEWS|u zwRHb~y8j=yvFZwAoi!b19Y%Hcw8N~!sYKfy?w&f8D6_-eQ>T*Eb+~(K)V@Wyd+HQk z?w&e@m%FD<;pOhBQH$ERd+HQk?w&e@m%FD<;pOhBQQbXF&;OI19DMt;dq77=D8`rF z15Po%%sQOP_%i7*sKttBhSdXyVrA=qLy@v`z@a#w%>CyYusso=I1dFV&VvDp^YI+i zLPQ*2Lw;(RPXaqbRKoR8+9POn`7igRax;$)|Qji;UxWut&W zom%V@a41f;2{;tz_S_$&9q()suvhA|Vvm4Bak53gp*Yzg;86bVY!EQ01qt>CI20$_ z100I;{@nZ2lRKM+P%mM&;_7_?ij%DY{z`GOGr*y|&wFzJxsK=E0gCgk0L6J{fa1I( z2X#De4^W)91t`vq0gCh19MtjL5TH2U8lX6D2~eCjk3uV^GKbJnvOS!)gjb4_odEty zak3G>pe{}918^wslWhPF#d&S+57G`>HUZcxbzNc)fJ1Sz1;C*=*#Y2C-X|LX4C=bX z`oBYQvi$E*oR{X_r|#NZl7qS~F=w#@;+JQS7@6vtnoV(CrkVe#mNf4LvgaeZ&2qE>-!GH z$@0EKaZZg6h{OWod{YkUJUS&nak8{;&NbMoWM$u>I2oE6)Op0n)S);Tm^u_E<5Gv> zWLRoY=Mke)hvH;V>QJ1FNgaxlA*n%~M~p}vijx7ULvb=5btq1TqXu;zwQ}RBdobJr z_g9LOm3)WdWFg<6&Lh_G9m@M;8Q-BeE4e>izu#HJw^!;sVh!J+I9bAXC{9-J9m@M; z0pFm`BSxSO#mNBFp*SV}92+~kKQ8}V-n~=)c~)ly>i?<#|7SV>F9Vo<{R|_J`v0fw zYqZ33g3e)qXg;1phN6gY*KI|WYR6B8X_=Do#6#umN$>RHq{o=0ToyE&j|G(YSZ?|h$Kk_ZZ9WAHwxlB*`vp76G z>CfWu^rSz#<1szy&+d3kPx`Zcy!50$+s8{!`WtkTJvsa~wQ~g;ho>j~$(cZOQ0>Z% zj^5*up7b~9B-=bXIzlCHNKg8+^MmP0e|CQGzx0#-*vq4>YIcuD>i>TO`~Oq@Kh^(# zh64W2_@Acwf2#jimT6Tg)&B?MpwFsG_5W1=x6ebW{}09?)&H$}ucuPd{eQOanC}0x zeaCeFpT!U9{=Y&0V5Y{xm&G)O#ZL+STYG>>Uae-h+X{ z`*@COPgCzepz!Vw6yAM-!n-#|wU*M`6DYio1q$!(K;eBfN42M^$J&IAjy=C;XLv2V zI|7CGksQ^YrryJW;{84pD7@PPg?C$yYAvP5^TYOjwU*N30b-}{@&vI{c(>-f+SAnA z5-8p;YZGR$=l9$nUJLK0A=PL7*fHYXeSyOJwm|WI?+p~*dvesq_wGR9y(>_7?+g@P z_88mywee+-u~T?&%Ngu>@!b$8yl)K@@0Yzf_8#o{iZ_qG){YhTZVD9M zw*(6Bje+9*-jJhuB(isXpzyNCSl^jMWAI)ZUJLIvIcn<}&lj6vjjlU9VC)oLo-lR_ z?-e<(t!I}9iuZe2pzvNAD7=^CsI6zLP1t+TbH=@k!fWBZFi?0e$WdF*)(49ByDm_8 z*9HpjnjE$DY;~aUt_l?1m4U)Lm!me{W&?$HCQx{%1BLhe9JTqjB2akG3l!dS1BLg^ zIcoFmoIv3{J5YF+2MX_5qttpddpv)vp9i7v@&K|^czFWZDZK14Hfr;YJ;qMqU6y+f zHs9D|?5~BFJ;p|DzMYaY*nDG;vA-7Ymp#T#;bo3t)aDy|jGf~Bvd7pdyzDV{3NL$% zjoN%;kFisD*<a$Ji;n>@juQl&hK-rc zH^#qCF}{p{onm|$|2mcN?K1w=j#P^ux{QCFinq)7*Qt2BjDMZN%lOx*u4i4wzfR$0 z{Oc56#=lPCW&CSY*Rw9;U#IXg{&fm3<6o!nGX6EHl9wbzfR$0{Oc56#=lA} ze&{m(bqX)zU#IXg{&fm3<6on?o^=`jI)#_PT^(z>l9wbzfR$0{A*O#vo7Obr|>fVbqX)zU#IXg{xz!WS(ovz zQ+OHwI-Sw`{|EQ~y-Dx?`>xyonC}03dGfQ9ADMh^^5Eo_$r~qEPfktNCVn{ajfpQ# zd}`u@6YrSVJ#qKMl@lu_mQIY1e}DX|<1dZBF#g{0XU4aWZy3LD{LJwMg`X6@Rrqq@ z#lrK2cNGrgOXLeXjBK>4#curRFtTwfKlu(L8>jMev6>M*i#3NIrYr|>eeaSAUZ8>2e2I*e?b!pq3UDZGqqoWje<#;DG$4kH_< z@G`P-3NIrYr|>eeF{(2w-Tyb}+H4J@AR8U)+H3}j@ofZ(@vY~mev@@-f#T;@4HVu= zpzxMQsYT|UQlRh_1BG`oPH=-*g6G1L8jnRsTu+#o7lnKG)GaAp8yP&3iY0LqwV zf*FN6#WJzX0E#mc$_$`5Gm*>yiZc_)DAdF^6UPjoI5T0)0E#mc#SEZ0GeL|(4H7dk z%m9ir6T%FjI5QE<0E#mcz$nxJF%!QGpg1$(%K(Zq6TJ+eI5WYELY-2X*ku64nF(D6 zP@I{_WdOy=z(sEovcQ{hi$ifTY;h<~MlBA-$)LrcjwfRlhvH<&;!vF2v2!R+ZrB;r z@w|OFlO2llwgAPsF+g$Nnu9u?8v+#PTLTp5Edh#?8+H1YEYQAb^pzG3yd^+!-WZ@b zZ^%JinywE}-sg1ziu2k4#d%E*Y8Y^Jfa1I=Kyh9fpg6C{K@9;e4^W(!1t`u-0~F^a zIjCR%7Y8WLivkqqg#n85f*jO!X?=j=To<4?xg%#kV%;I&hMYlNmsaQgbFD9|3Q(LY z1C;kUmxDTwW&@P>ITN5brvnrxH{$Gl>O5k!>QI~vRvn6yv8qFHGE_CF^N5kELvb=t zbtq28sSd@-Fjb*_%`-}MC{6~c4#mkB)uA{Uq8ikB#0b@)I2oWi6er_ThvH;-YEb79 zqf>|CWN_+GoQzE!ij$$KL7hh@jQ%v`jHWn`4^W)kar6INoZN6TsPpK!+<&g$?~4Nz z=b`|`xiANH9vvH?yw76-6z75f#o5k5okxsD?ainA|8k==Eg9%Jz4PRs=j;7{XLa%K zQ=Q|}^Z$MU=l@gvUor#9mQwv+&TC;Dm^)bIT<>T~^?y6tmFoX?wky^D<=!QU4SGy4 zv?3%_f`e55pINEnz-+@t$Lf(xH;ktGzm0>Y9zEtBPVqTo^5GPpGiD!7B??XT|7pDw zE!F?0Q~h7(MXLWV)2gaGomSR|RR5PfOpHTj>wNqEbEl*yP~zWI|CbXQ7zgg7TSZ>? zSN^l=|L3RY{}q=PkDdIt$?r_2`v350c&YxM>i>fqB2g7g^?!RxWUBwm7g(zQ51$%p z_pGP-KL$jLFtV8^>2s?8b3pW@POAS82V^GI|3}MoGpYWc8xTEG$Q?LcSY~t{rTYJH z9!aTRzKc`+p97NW|D*FrE=rN_;xRn`Z>`q^HL;8#(8pDVxD%J1{#_Zjkg zx%>t@TYjG=zrjwB-{>V+Ex%XEZ}8R4{muKEH#Ao_mo*!We{FoT@fVF38^6_fu5qaG zK;!1d`oH7Bit@VrRXV(|i{=N3S+F#XPs{Kyw*K3{H zj@liy%WLP=mewY!uU5ZS{X+E<)%RBqS9e$Msa{>3sh(6VSAJ0WTX_z^?^Qlj`Q^&K z%BITol~t9gO1=D}^52)gRQ~<)^W|SDA1rSz-&9^#KE2$QCjorB^m6GBN*^u#YU!!c zw$g^uMWyAXg~iv3|4@9T_=m+8ioaHTd-37o#^S{(|4;q@Q-fQZ*<_XSe_2_F`$5_e z*kQ+2>i^G-&fwPO!f^TZvjVdGc4Yn4msW;52(m-|Y!2#n+cN=5ee&r5Mfy~L;(Rg( zH9mMEKye-lP@D$?6zAhPs2kWR{~ykal>evxe|#8h??dCQ)c-#m5PO1J>i_3}r2hZm z7m8=~@_ob>9Eb9Kl=A=K$HuyD7@F!RrTm}Ks0~QU|3@d+F#nei5hB6v;>SJ@_4{ijfb8>B zzf;-K=(9J`sP=~UGiP)5?~;C(X`Dgmv#{^4g?D@IJ!oaB&!heNT05HB=h=Rz@bYlK zQ+Rp0-znZNkM|qZUdlet_dA7m^XNOX=Z5v~4-{S=@i&8RLG{_2=oDTi`A+eES><;M z?>)IcO}C-?cLxeD5Bi&7H3}B}JHuWw;mUIyT=#rx%L08ZiMbpS?fe0d*$Q@meZ2;dZ6-U#3n-t$M_nO#}YUlAz0 zyc9qU@>+O#D}YmYc`blZThDkl$0@wr7;p+NcLtoo%hmv+wx02B0H^Tsasa3B@^%2H z@bY>9qqd&$egLQNF3XLMt!Gn#!pkcH%wX%;DY@5bZ+QRYK=FQ03KU*$6PVZ5GxiBM z#rx$&0Z!pvn)B*em;RC*we{?{K;h+O0p``Fus&}KaEkZK>jI40eB*rqPT}Q+0Z#FL zd1HW6c-y&mX7i1E2KHK;Z_V&pcpHJjThCFOZ?!=2eyf4PTL~22@+cj|52ZliEd~nj zWT5a)e9OE}H%FmN1XT3NParr|>e4aSAWv7^6Cmx{PC-!pk_uDZGqh zoWjdEMybuCF5?)d@G_2Z3NParr|>e4F=}U;7{@q;mvM|!cp1kyg_m)RQT^WPGLCTy zFXI@e@G_2Z3NParqx!woWgO!aUdAy_;bk1-6kf(LM(s=!;~1y#GLCTyFXI@e@G_1u zs^42(#xYLeWgO!aUdAy_;bk0S)aD!G7^me4aSAWv7^N16cNxbxg_m)RQ+OH2IE9ySj8R+97{@q;mvM|!cp1ky zg_m)RQCrU#$2f(Tag0-V8OJzsP1 zK*ig6Do}Wz%u!v>I!^=&@1a29Js2pwkLReaXPpCq!n;3Ec=rVg@7^5M`PSJJD7=pa z3h(Yf;e9kmb-s0W1q$!ZK;hjHD7=s4sLr>}!-2y4P@wQ`4;0>QqtxPu&Vzx%J0B># z4+IMD)*RLO*4Yv$yqg1s_x?cP-ISv`-#YgN3h&zjh4mzkJtY9Ju~jo?~~KaQ!oy@_!qLl>gf}Sbm!F zf7@S4`G3m)HOiA7aNK#6^8b|o+vg$W|Carv{NKhQ<^MJgDgW0~lPUkVbE0w^w0v)N zexCXNma+Et+OM=f-~L$pJ?*F354LYUzSgy^ z+15#|QuCjiUvGY)`Mb^cHM`9n&D)!oHqU7;YP{C?ZsRMB&ow^Ucz5FoIsJcAV{PNq zMx*|t`rp<6y#DF>hw8sn-&4Q0esz7ienNe+_G;~~Ykyq(xSaied+p)c#@fZT<+Wq0 z|5p7@_2ueks~@R8S3OwWQoXUdx;j;@Reo6chMfHWRON$}cT{#)?yg)}Sy5S9883go z{MGVHsQR_V*77fa8V-c>qKy1#UNX=Uk@QlnW><7TF}|5y(E)`wvn#4Jbp=@Vp1J~T94u>1 zT>+YMA>I$%|HFX8pI9QgFx&qc|be_5bQdfXw@2M-m#=)}IpI28vCeqakx1K9V z^?%tbkcj_3SpQGw|I_*Z|D?nJ^2et0|LOdHI{%-}|I7DQpMAr67FfEGWT&dnzG0)9 zk*D+j_5_M_{@ z|Cg#G#)0SN{8j+OB|JCh6mbd9%^B4lk3P@MIfa+!=A6RIb8}AN<+(YfKlk(hV+%C< zTbrK$m+t>h_y4E+{|EQeTlUpy4^Q;@P5P};pOV-6ke{LPT}S1sdR8^lU>J7;pM4gr||OBu~T??>e#4tZH|wQPP+epdLfS* z+d%1+YdmS}P{x!8jSXsJG%Jb@E6U7e2$pgg(bv${V z*r7OioYQI~vMGfjaVkGKNoD4)Aij#4uLvb<;HK_B5QK&<4 zG6;1jPR5`P#mNxVpw1&kpbo`Z$PFEz|F=e;|F>G7|F^2MBAx&Ld3FC69`Ks_!ajdS zwxgrp#eM#eoXQWrKQ{c)IhCJ$@7WyHWu*5^pz^ctJsqfcdrt)l?~^&IOG)pEK;b4Q;SUV6+QoWjcl!YRDW zAe_R>6vC)3F}*FhpPR4Z@mU*;1|;blVM6kcW&PT^%rVbsQ#IfYYrnN&E1msy2V zc$roxwX~wgyuvBGOe~zj%gn+lyi6^OYDm;$Zs8POCKpcOWp?2dUZxjDZ9QXt;S^pb z7*643hT#-mrWi(TJ-dAPNp%YEWr4!WEW=+5FVhU8ww^K1a0)LI4X5xj({KteQw^iG zo-x;O3NMolr|>e{a0)Ne4WqW6G2d_sFB1-@@G|3Y3NKR*qc-1|b2x>UNrzK-nRPgY zmuZJln{UiJoWje*!zsMXJecbF>3Se)X`6>?K1Y31qv^-5I4ZfG{mUQH|8Nu@qU?zIE9y)h*Nl(iWs%| z#$3cHyi7)%!pm&LDZETajM{u-KH?N!CL~VbWk%u@UZx~QZN4!laSAV!5~uJoD{%@h z(-NaL-fVbqX)zU#H^jGXB-mX|@g7W&G<@yj{k>PQ}}0{Oc56#=k~& ze7lT)ox;ob*D1V=f1Se1_}8e8ZfV zbqX)zU!@j5bQ%9Tg_rTKQ+OHwI)#_F5_RP@G|~&3NPbdr|>fVHLB}bm+`Mtcp3jXg_rTKQ+OHw z8rAiz%lOwRyo`UH&glLBssBIq|NCCWFaH0g{{PhfpZfn(|G#|4rT%}(BQXw)P3)Oy zdhcj@{-50&o}T|Vtz9YU`G4|QYl(r=^Z#b`#-#N8zZtzTDLwyh#-1O{y*aJKNTZUd2|XdE00EPJ!9q3DZH#aI)#^&N2lUVPX(OO z-G=m3K#4%`d0^#HKP7|nWvo0pmCspvDxmDxVH{X_w9#3ux0Esdb&Bz2{A*O>htBQ8 zxWp;Ew*@NmI6W0mZUVtLT$>vmy-nx8$Ws9`k#4&G4-aq~J}Uq}`Anpnp8xlsbo+nm z|F`D~S{JzW@3H^MsjT9u|6kT?j05|hY;<%LPyPRrG0K{j`v2{2*VO+nH#gz)aOE(% z(LPb35=3r&xcv3+EJ{Gw#AU#rSd;PHF1@UuNTw z`u}YlQvbh=gUvVY!a2o!<1U<2%s1}BIhFaA`v0xRBlZ7Vk4NhNw;qo-r2qdDg#%;l ze{6rX{YUMOw|}GEZSQQ~*}kH^qJ4aOvh`1`uebiB^~u%;TJLB**1ETKjXeMF|E&4x=5IFN+1%f}zj;G*b#qy>(fHTKHyeM^c(L(YjprJNQC3V*Ke&~TtBCwaaVg)t1&K zs;^eRR{cWt6V>-u4_9|r@2Os0ovEHwEmwX}`CEAk;O|vFRQct~zRIS`^_5kXsY<>4 zqw?REzf}JH^7G|iDIY9vE#FjLS3bSmmgfL|yYzDD4@w^`{c7o{(zeou(nY1^rG>@U zivLi2rTB-%7mB}De0%ZX;>O~|>HfcT|6jWQFWvv=-S~D7ns#5O`~OA6*ET}2T+`ulZ*o>&dfnZg&K%w z&M^+4I5Wo>2T+`uQ;Y*B&dec3h1zQHaPF^}?*AJOh+afw+jcy)=upO!hZYU$c=F7m zL-`r8aOhBeMywk;6er7u2DNR2RYQm3WYN%}I5+0rymnOInuD6mYzR=CZw*kKw*)Bf z^X5@#?HV@)D9*P8D9#%L6z2^&sI3~;2Pn?#0u(3fggPJ=VPBJbrM78Y9iTX`3Q(Nf zoinHI&RvmvrEV5n9-ufc3s9Vw1}J~`OL9=xrHca;CrgI*mh_q-_Qp9BCtKqT>bk_v zIEUh7W1K^IpX`fsC{Aw38Ps)Yb?$v?U+Ag;#mSyH|IfwAmNpgF27c0q0PhY=CnpPWHb!6eru?4C*{$_nSj;viZ%SINAH= zP@HUiGpO^3oo^1s$;LN_;$+{OLvbz{{nXh*RgMc#oQne#Cwtz^so(Exc{8ZZU%K8)pCEje!o`(6lWzs zah7vX=MlTy><`js4zS71p*Y#&=1`n$aWkm%hO?avF8+C?(8{*ms@*Eb?Z6Z|DW#v&-UqC|K1bB%`59f*QhDo|1bA0 z$(&2~|I58g7>CWdPl-NcB;EfnoqDncr~ChpvhpZfooSshUCcuf8OZ(#p_y8r)Yss8`sZ_NL% z^(UtL|I_{d>HdFjuGjnC2lw|r$^Di6yUv{&+~1qon;THsi|HQ6ezsg1BG|nD7Dtq{)2(SJ0B>#4+IMD)*RLL)c%%0 z;mz#L;Zq_{jDz?7@LG5`<*0TT_3sN5-nRt`FT4D8)TKuWURDT=>fUmn6+)-*vO?$- zURDU5!pjPwQLPa6Z_m9mJ&V%6El_wj1`6-3IjZfd{SATQ{k}C&cy9?5-kWpO<{Nwd z?Qfxb%Y8QeJB61Wa!%o8hn!KHZ>$hH#rtK2&?&s!n{x^;D}+XEzOh2+6kb*cox;ls zp;LHSAv9|9jTJ(t@UlYa6kb*cox;lsp;jyGhS5Gd2^8M51BG{apm@J$<*2P^X9f!I z8G*ukdZ6&0mZP?wof;^-%L0XWDo}WNM8L+()-#?Fa0)LE2{?t9rv#kBdt&ZSv-RwR zK;b<;P(M*@k1$4c#DC;I~gdv6FG|cR+MCNqWvmT z%FEh6Y5%bOYC8Wv`JKs^CqFy+k;&&K4^D2Gym4~%BRW>_s73F{?hmh&m3P+_(|bgg)bLgEIeO$mt3!q z`?avc)zf=1giN|njNm5PUWZB;p%BrSI-VtPp9y5^>hj^S5K$#a`iN-t7nI+ zr&D;jdOC%dtEW?Vxq2G4elf0|PT}S1=@eeBo=)NA>S@&a#khJpg_o057$(u@D>7vcPvoxKAVF&-JS_htWO6h(x(Cx=aV_8 zQ|*ZW#d#<|aUKj%oR8fa1I*KyltY3N4wsDL`?)B|vfB7@#O5k!s*8X8hpaWY7C zC{D(x4#mk3)u7HJMyL+O$pF=%I2oTh6eq(|gF25GojMdJgHwm%WNhkCoD5A3>O5j( z>QI~vOdX1oaj8RbGAuQy^N3NYLvb=Fbtq28qz=W&kkp{gBSxeS#mRuwp*R_jIus|v zQG+^<7>zm|_&Lc*k4#mj;)S);f{u~>-v_CHYyrg@l{PW_@3iSV{`~TAYe{v_6b>^o0 z->R=E|F_QEl>bX;A!~2S|Gh=QHab%NKMa&@K+^qx!vWElBi;X(@_#&+Nf?$&otftSG@e^ zbpwEGt;ny%9oda8$fy^p%c@n46(uy1wXVxLhMjED&v%!F45Ru*(PbsWDGV%SIF)Ul zE^8T1C4cF%m|;{mxw@=oIE9zx45#oio^c8<3mQgsqpQn`hEsT1(r^kdYZ^}BWl_VZ ze%RCff9d`|I|nwco5@{PJM>c`6rVGeJDlQk#(IZSe9l<#FlzIS6%VKIvgF|uUe-LE z!powEQJZh9dN_rbVUJUIS@&=XFAE<=ZN9Pc;S^q$KAghK+J{qkS^O|+^NrOHr|`1; z;S^rhKb*qL0*Kz~V+G_cDAx`0CEyO9jEQT1h@nto{DZDI)IE9z>5U236AfnU?%3W4OoWjeJh*Nl3 z6LAVJiy}s~7}jM~#3{Tii#UasbrGlVvM^#)3u0YXMx4US(uh-dSsQT*FN-5awJ6qQ zbmSCXmPeezEAO*%K(dj|B?v?m*#vG)Hy5b#?^`@6JHs z-4Q6fkL0M%x6Z?X!uwF5@NN$j-fg3F&|mgopzzKI3hx7f!n-v`t-owbpzv-E6yEy- zg?Ceq>U`_m7bv`M3l!da1BLgV9M$=j?*Ef}8*o1z<6m82?B=Kr<6oy3U&g;q|KIk` z&^L-8jN;cOJF|v_1k`(ps zRj$kTa#22)kL7Jym*?ei8Hc;!=WsQA8@>+H@L_m;f2aS0cRc?u7LO)dNZeJ@@5b!A z?PLoP?YqHb3lZ(RL9>OB#xCA$A)-CE*+N8nZnK4mBG(o|8oPM2g@_{879xsVTZkxf zZ6Ty#P0bb}idPq(iy%?X2N8@3=tvB^Yy{upAr~1Bb>Z+d9e|Y}aDL;Sy z-3ny?U-tiH|DUcrMz2-&|1G%=PvOAX|Hrdo_W$v0nEik3hR^;#o(;4Ak7vW||KlC3 p+5flohvVJEIj+QP`-A-rInh45&;CDlT1*dSYZlLP`j7knegl)Mgzx|W