From f7c25afe32d2e7e5c945a8ca5d1aa2b14fdf7661 Mon Sep 17 00:00:00 2001 From: lpetrov02 <71082527+lpetrov02@users.noreply.github.com> Date: Fri, 27 Nov 2020 23:11:08 +0300 Subject: [PATCH 1/9] Add files via upload --- main.py | 236 +++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 167 insertions(+), 69 deletions(-) diff --git a/main.py b/main.py index dc51b8e..b07c1c0 100644 --- a/main.py +++ b/main.py @@ -1,69 +1,167 @@ -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)) +import vk +import datetime +import time +import random + + +class Group: + def __init__(self, group_id, freq): + self.group_id = group_id + self.begin = datetime.datetime.now() + self.frequency = freq + + def fast_online(self): + amount = 0 + online = 0 + your_group = vk_api.groups.getById(group_id=self.group_id, fields='members_count') + number_of_them = your_group[0]['members_count'] + one_more_number_of_them = number_of_them + done = 0 + while done < number_of_them: + group_members_ids = vk_api.groups.getMembers(group_id=self.group_id, offset=done, fields='online') + for x in group_members_ids['items']: + if 'online' in x: + online += x['online'] + else: + one_more_number_of_them -= 1 + done += 1000 + if one_more_number_of_them == 0: + return -1, -1 + else: + return one_more_number_of_them, online + + +token = "65e6efa565e6efa565e6efa54f6593fb1f665e665e6efa53a5c6937a4636b3416a8bd92" +user_id = 'memkn' +session1 = vk.AuthSession(access_token=token) +vk_api = vk.API(session1, v=5.92) + +month_length = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + + +def get_user_last_seen(id): + """ + shows when user was online + :param id: + :return time: + """ + value = vk_api.users.get(user_ids=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(id, done): + """ + shows when user was online + :param id: + :return time: + """ + value = vk_api.groups.getMembers(group_id=id, offset=done) + followers_id = [] + for user in value['items']: + followers_id.append(user) + return followers_id + + +def get_user_followers(id): + """ + shows when user was online + :param id: + :return time: + """ + value = vk_api.users.getFollowers(user_id=id) + followers_id = [] + for user in value['items']: + followers_id.append(user) + return followers_id + + +def delta_time(real_time): + 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 + + tipa_online_time = real_time.replace(minute=(real_time.minute + 2) % 60, + hour=hours, day=days, month=months, year=years) + return tipa_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 count_online(id): + amount = 0 + online = 0 + your_group = vk_api.groups.getById(group_id=id, fields='members_count') + number_of_them = your_group[0]['members_count'] + if number_of_them <= 10000: + done = 0 + while done < number_of_them: + group_members_ids = get_group_followers(id, done) + for x in group_members_ids: + last = get_user_last_seen(str(x)) + if last is not None: + amount += 1 + delta_last = delta_time(last) + online += is_online(delta_last) + done += 1000 + if amount == 0: + return -1 + else: + return amount, online + else: + piece = number_of_them // 100 + for i in range(piece): + group_members_ids = get_group_followers(id, 100 * i) + x = random.randint(0, 100) + last = get_user_last_seen(str(group_members_ids[x])) + if last is not None: + amount += 1 + delta_last = delta_time(last) + online += is_online(delta_last) + number_of_them -= piece * 100 + group_members_ids = get_group_followers(id, piece * 100) + x = random.randint(0, number_of_them - 1) + last = get_user_last_seen(str(group_members_ids[x])) + if last is not None: + amount += 1 + delta_last = delta_time(last) + online += is_online(delta_last) + return amount, online + + +group = Group(user_id, 30) +all_members, online_members = group.fast_online() +# all_members1, online_members1 = count_online(user_id) +percent = online_members / all_members * 100 +# percent1 = online_members1 / all_members1 * 100 + +print("In this group i have got information about " + str(all_members) + " users") +# print("In this group i have got information about " + str(all_members1) + " users") + +print("Online percent in " + user_id + " is " + str(percent)) +vk_api.Messages.message.send(user_id=427479334, message="Online percent in " + user_id + " is " + str(percent)) +# print("Online percent is " + str(percent1)) From d15a54519d8141b24c4aba88874cf770023a10f4 Mon Sep 17 00:00:00 2001 From: lpetrov02 <71082527+lpetrov02@users.noreply.github.com> Date: Sat, 28 Nov 2020 13:02:17 +0300 Subject: [PATCH 2/9] Renamed functions and added working with a period Renamed functions and added working with a period ahahahaha tokeni na githube ne hraniatsia hahahah --- main.py | 141 ++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 102 insertions(+), 39 deletions(-) diff --git a/main.py b/main.py index b07c1c0..0632a3d 100644 --- a/main.py +++ b/main.py @@ -2,69 +2,85 @@ import datetime 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 fast_online(self): + 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 = vk_api.groups.getById(group_id=self.group_id, fields='members_count') - number_of_them = your_group[0]['members_count'] - one_more_number_of_them = number_of_them - done = 0 - while done < number_of_them: - group_members_ids = vk_api.groups.getMembers(group_id=self.group_id, offset=done, fields='online') + 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_them -= 1 - done += 1000 - if one_more_number_of_them == 0: + 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_them, online + return one_more_number_of_members, online token = "65e6efa565e6efa565e6efa54f6593fb1f665e665e6efa53a5c6937a4636b3416a8bd92" -user_id = 'memkn' +group_token = "17e681fbe171945431a04f1abc752d41ff888698288abf74124de4e782c67f36e76484601991870f56b7a" +analyse_group_id = 'memkn' session1 = vk.AuthSession(access_token=token) vk_api = vk.API(session1, v=5.92) month_length = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] -def get_user_last_seen(id): +def get_user_last_seen(profile_id): """ shows when user was online - :param id: + :param profile_id: :return time: """ - value = vk_api.users.get(user_ids=id, fields='last_seen') + 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(id, done): +def get_group_followers(group_page_id, done): """ - shows when user was online - :param id: - :return time: + 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=id, offset=done) + 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 +''' +# probably we will not need this one, but who knows... + def get_user_followers(id): """ shows when user was online @@ -76,9 +92,16 @@ def get_user_followers(id): for user in value['items']: followers_id.append(user) return followers_id +''' -def delta_time(real_time): +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 2 min + """ + hours = real_time.hour days = real_time.day months = real_time.month @@ -98,12 +121,14 @@ def delta_time(real_time): months = 1 years += 1 - tipa_online_time = real_time.replace(minute=(real_time.minute + 2) % 60, - hour=hours, day=days, month=months, year=years) - return tipa_online_time + 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: @@ -112,6 +137,9 @@ def is_online(online_time): return 0 +''' +# it is so long and long-working shit that i don't want to re-write it. I suppose, we have something better + def count_online(id): amount = 0 online = 0 @@ -140,7 +168,7 @@ def count_online(id): last = get_user_last_seen(str(group_members_ids[x])) if last is not None: amount += 1 - delta_last = delta_time(last) + delta_last = approximate_time(last) online += is_online(delta_last) number_of_them -= piece * 100 group_members_ids = get_group_followers(id, piece * 100) @@ -148,20 +176,55 @@ def count_online(id): last = get_user_last_seen(str(group_members_ids[x])) if last is not None: amount += 1 - delta_last = delta_time(last) + delta_last = approximate_time(last) online += is_online(delta_last) return amount, online - - -group = Group(user_id, 30) -all_members, online_members = group.fast_online() -# all_members1, online_members1 = count_online(user_id) +''' + +analyse_group_id = input() +frequency = input() +if frequency == "as frequently as possible": + frequency_number = 0 +elif frequency.isdigit(): + frequency_number = int(frequency) +else: + print("Error") + exit() +n = int(input()) + +group = Group(analyse_group_id, frequency_number) + +t0 = time.time() +all_members, online_members = group.count_online_proportion() percent = online_members / all_members * 100 -# percent1 = online_members1 / all_members1 * 100 - -print("In this group i have got information about " + str(all_members) + " users") -# print("In this group i have got information about " + str(all_members1) + " users") - -print("Online percent in " + user_id + " is " + str(percent)) -vk_api.Messages.message.send(user_id=427479334, message="Online percent in " + user_id + " is " + str(percent)) -# print("Online percent is " + str(percent1)) +percent = math.ceil(percent) + +# print("In this group i have got information about " + str(all_members) + " users") + +# print("Online percent in " + analyse_group_id + " is " + str(percent) + "%") +t0 = time.time() - t0 +if t0 >= group.frequency * 60: + print("Sorry, I can't work so fastly, so I will count statistics as fastly as i can") + for i in range(n): + current_time = datetime.datetime.now() + + all_members, online_members = group.count_online_proportion() + percent = online_members / all_members * 100 + percent = math.ceil(percent) + + print("Time: " + str(current_time) + ". Online percent in " + analyse_group_id + " is " + str(percent) + "%") + print("____________________________________________________________________________________________________") +else: + for i in range(n): + current_time = datetime.datetime.now() + t0 = time.time() + + all_members, online_members = group.count_online_proportion() + percent = online_members / all_members * 100 + percent = math.ceil(percent) + + print("Time: " + str(current_time) + ". Online percent in " + analyse_group_id + " is " + str(percent) + "%") + print("____________________________________________________________________________________________________") + t0 = time.time() - t0 + time.sleep(group.frequency * 60 - t0) +exit() From fe5e52c74089f8275a1e037f8b4a8ee571100cbe Mon Sep 17 00:00:00 2001 From: lpetrov02 <71082527+lpetrov02@users.noreply.github.com> Date: Sat, 28 Nov 2020 19:14:58 +0300 Subject: [PATCH 3/9] Add files via upload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Написал бота. Понимает некоторые штуки, но суть такова: получает формат: "group_id: .....; period: .....", пробелы важны. Возвращает процент онлайн - разово. Вырубается по команде stop --- messages_test.py | 227 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 messages_test.py diff --git a/messages_test.py b/messages_test.py new file mode 100644 index 0000000..8debb51 --- /dev/null +++ b/messages_test.py @@ -0,0 +1,227 @@ +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 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 2 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_): + # some = vk_api2.groups.getLongPollServer(group_id=group_id) + response = requests.get('{server}?act=a_check&key={key}&ts={ts}&wait=25'.format + (server=server_, key=key_, ts=ts_)).json() + # print(response) + if len(response['updates']) > 0: + return response['updates'][0]['object']['body'], response['updates'][0]['object']['user_id'], response['ts'] + return "", -1, response['ts'] + + +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 +while run: + message, current_user_id, current_ts = get_message(my_number_group_id, server, current_ts, key) + + # print(message) + if message.count(';') > 0: + index = message.find(";") + if message[0: 10] == "group_id: " and message[index + 2: index + 10] == "period: ": + analyse_group_id = message[10: index] + frequency = message[index + 10:] + if frequency == "frequently": + frequency_number = 0 + elif frequency.isdigit(): + frequency_number = int(frequency) + else: + print("Error") + exit() + + group = Group(analyse_group_id, frequency_number) + start_time, percent = group.group_analyse() + string = "Online percent in " + group.group_id + " is " + str(percent) + "%" + vk_api2.messages.send(user_id=current_user_id, message=string, random_id=count) + count += 1 + # value = vk_api2.messages.getLongPollHistory(ts=current_ts, group_id=my_number_group_id) + elif message == "stop": + run = 0 + elif message == "hello" or message == "привет" or message == "Hello" or 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) + else: + string = "I understand just such a format: 'group_id: *id*; period: * period time *'. Please, write correctly))" + vk_api2.messages.send(user_id=current_user_id, message=string, random_id=count) + count += 1 From b35e5369c743917091389208c0005c9d67b9c6d5 Mon Sep 17 00:00:00 2001 From: lpetrov02 <71082527+lpetrov02@users.noreply.github.com> Date: Sun, 29 Nov 2020 00:17:36 +0300 Subject: [PATCH 4/9] Add files via upload super bot --- messages_test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/messages_test.py b/messages_test.py index 8debb51..6e565d1 100644 --- a/messages_test.py +++ b/messages_test.py @@ -216,12 +216,16 @@ def get_message(group_id, server_, ts_, key_): # value = vk_api2.messages.getLongPollHistory(ts=current_ts, group_id=my_number_group_id) elif message == "stop": run = 0 + vk_api2.messages.send(user_id=current_user_id, message="Goodbye!", random_id=count) + count += 1 elif message == "hello" or message == "привет" or message == "Hello" or 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) - else: + count += 1 + elif message != "": string = "I understand just such a format: 'group_id: *id*; period: * period time *'. Please, write correctly))" vk_api2.messages.send(user_id=current_user_id, message=string, random_id=count) count += 1 + From e6cb757b5a1d091e779a18a235213dab4fd8cca7 Mon Sep 17 00:00:00 2001 From: lpetrov02 <71082527+lpetrov02@users.noreply.github.com> Date: Mon, 30 Nov 2020 15:24:46 +0300 Subject: [PATCH 5/9] Add files via upload upgraded bot --- messages_test.py | 96 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 77 insertions(+), 19 deletions(-) diff --git a/messages_test.py b/messages_test.py index 6e565d1..c7e173f 100644 --- a/messages_test.py +++ b/messages_test.py @@ -53,6 +53,12 @@ def group_analyse(self): 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() @@ -136,7 +142,7 @@ 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 2 min + :return: real_time plus delta min """ hours = real_time.hour @@ -184,6 +190,39 @@ def get_message(group_id, server_, ts_, key_): return "", -1, response['ts'] +def count_new_time(time_now, 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 + + some = vk_api2.groups.getLongPollServer(group_id=my_number_group_id) current_ts = some['ts'] server = some['server'] @@ -191,30 +230,49 @@ def get_message(group_id, server_, ts_, key_): message = "" run = 1 count = 0 +have_a_task = 0 +group = Group(-1, -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: + current_minutes = time_start.minute + next_time = count_new_time(next_time, group.frequency) + count = group.work_and_print(count, master_id) # print(message) if message.count(';') > 0: index = message.find(";") - if message[0: 10] == "group_id: " and message[index + 2: index + 10] == "period: ": - analyse_group_id = message[10: index] - frequency = message[index + 10:] - if frequency == "frequently": - frequency_number = 0 - elif frequency.isdigit(): - frequency_number = int(frequency) - else: - print("Error") - exit() - - group = Group(analyse_group_id, frequency_number) - start_time, percent = group.group_analyse() - string = "Online percent in " + group.group_id + " is " + str(percent) + "%" - vk_api2.messages.send(user_id=current_user_id, message=string, random_id=count) - count += 1 - # value = vk_api2.messages.getLongPollHistory(ts=current_ts, group_id=my_number_group_id) - elif message == "stop": + if index > 10 and len(message) - index - 10 > 0: + if message[0: 10] == "group_id: " and message[index + 2: index + 10] == "period: ": + analyse_group_id = message[10: index].strip() + frequency = message[index + 10:].strip() + if frequency == "frequently" or frequency.isdigit(): + if frequency == "frequently": + frequency_number = 0 + else: + frequency_number = int(frequency) + have_a_task = 1 + master_id = 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 + else: + print("Error") + elif message == "stop" or message == "Stop": run = 0 vk_api2.messages.send(user_id=current_user_id, message="Goodbye!", random_id=count) count += 1 From 7b05758f02cc1c7a7eaffcac518b6147a751a444 Mon Sep 17 00:00:00 2001 From: lokiplot Date: Mon, 7 Dec 2020 12:37:02 +0300 Subject: [PATCH 6/9] =?UTF-8?q?=D0=BD=D1=83=20=D1=8D=D1=82=D0=BE=20=D0=B2?= =?UTF-8?q?=D0=B0=D1=89=D0=B5=20=D0=BC=D0=B0=D0=B3=D0=B8=D1=8F=20=D1=82?= =?UTF-8?q?=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D1=81=D1=83=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D1=83=D0=B4=D0=BE=D0=B1=D0=BD=D1=8B=D0=B5=20=D1=84=D1=83=D0=BD?= =?UTF-8?q?=D0=BA=D1=86=D0=B8=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D0=B3=D1=80?= =?UTF-8?q?=D0=B0=D1=84=D0=B8=D0=BA=D0=BE=D0=B2=20=D1=82=D1=8B=20=D0=B8?= =?UTF-8?q?=D0=BC=20=D0=BF=D1=80=D0=BE=D1=81=D1=82=D0=BE=20=D1=81=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=80=D1=8C=20=D0=BE=D0=BD=D0=B8=20=D1=82?= =?UTF-8?q?=D0=B5=D0=B1=D0=B5=20=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D1=81=D1=82=D0=BE=20=D0=BE=D0=B1=D0=B0=D0=BB?= =?UTF-8?q?=D0=B4=D0=B5=D1=82=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.gitignore | 3 + .../inspectionProfiles/profiles_settings.xml | 6 ++ .idea/misc.xml | 4 + .idea/modules.xml | 8 ++ .idea/team-project1.iml | 8 ++ .idea/vcs.xml | 6 ++ graphics.py | 89 +++++++++++++++++++ 7 files changed, 124 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/team-project1.iml create mode 100644 .idea/vcs.xml create mode 100644 graphics.py diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..a2e120d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..f9150b2 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/team-project1.iml b/.idea/team-project1.iml new file mode 100644 index 0000000..d0876a7 --- /dev/null +++ b/.idea/team-project1.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/graphics.py b/graphics.py new file mode 100644 index 0000000..9bbdcb2 --- /dev/null +++ b/graphics.py @@ -0,0 +1,89 @@ +import matplotlib.pyplot as plt +from matplotlib.ticker import AutoMinorLocator +import numpy as np +import pylab as pltt +import datetime +import matplotlib.dates as mdates + + +dict_with_data = {datetime.datetime(2020, 12, 7, 1): 53, + datetime.datetime(2020, 12, 7, 2): 14, + datetime.datetime(2020, 12, 7, 3): 15, + datetime.datetime(2020, 12, 7, 4): 12, + datetime.datetime(2020, 12, 7, 5): 35, + datetime.datetime(2020, 12, 7, 6): 31, + datetime.datetime(2020, 12, 7, 7): 2} + +label_of_image = "zsd" + +sample_data = {datetime.datetime(2020, 12, 7): 53, + datetime.datetime(2020, 12, 8): 14, + datetime.datetime(2020, 12, 9): 15, + datetime.datetime(2020, 12, 10): 12, + datetime.datetime(2020, 12, 11): 35, + datetime.datetime(2020, 12, 12): 31, + datetime.datetime(2020, 12, 13): 2} + + +def create_weekday_image(dict_with_data, label_of_image): + if len(dict_with_data) > 7: + return + day_of_week = [0, 1, 2, 3, 4, 5, 6] + online_on_this_weekday = [0, 0, 0, 0, 0, 0, 0] + for date in dict_with_data: + online_on_this_weekday[date.weekday()] = dict_with_data[date] + labels = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] + pltt.rcParams['figure.figsize'] = 7, 5 + pltt.bar(day_of_week, online_on_this_weekday, align='center') + pltt.xticks(day_of_week, labels) + pltt.savefig(label_of_image + '.png') + pltt.close() + + +def create_daily_image(dict_with_data, label_of_image): + day_delta = 0 + prev_key = datetime.datetime.now() + for key in dict_with_data: + day_delta = key - prev_key + prev_key = key + period = day_delta.total_seconds() + period = period // 60 + number_of_dots = int(1440 // period) + y_axis = [0] * int(number_of_dots) + x_axis = [datetime.datetime(2020, 1, 1, 0, 0, 0) + day_delta * i for i in range(number_of_dots)] + for key in dict_with_data: + y_axis[int((key.minute + key.hour * 60) // period)] = dict_with_data[key] + figure, ax = plt.subplots(figsize=(number_of_dots, 10)) + ax.set_title(label_of_image) + ax.set_xlabel("Время", fontsize=14) + ax.set_ylabel("Процент онлайна", fontsize=14) + ax.grid(which="major", linewidth=1.2) + ax.grid(which="minor", linestyle="--", color="gray", linewidth=0.5) + ax.scatter(x_axis, y_axis, c="red") + ax.plot(x_axis, y_axis) + my_fmt = mdates.DateFormatter('%H:%M') + ax.xaxis.set_major_formatter(my_fmt) + figure.savefig("images/" + label_of_image + ".png") + + +def create_graph_image(period, total_time, y_axis, label_of_graph): + number_of_dots = total_time//period + x = np.linspace(1, number_of_dots, number_of_dots) + figure, ax = plt.subplots(figsize=(number_of_dots, 10)) + ax.set_title(label_of_graph, fontsize=16) + ax.set_xlabel("Время", fontsize=14) + ax.set_ylabel("Процент онлайна", fontsize=14) + ax.grid(which="major", linewidth=1.2) + ax.grid(which="minor", linestyle="--", color="gray", linewidth=0.5) + ax.scatter(x, y_axis, c="red") + ax.plot(x, y_axis) + ax.xaxis.set_minor_locator(AutoMinorLocator()) + ax.yaxis.set_minor_locator(AutoMinorLocator()) + return figure + + +#figure1 = create_graph_image(1, 11, [1, 2, 3, 4, 5, 64, 40, 8, 7, 5, 9], "28 ноября") +#figure1.savefig("images/plot.png") +create_weekday_image(sample_data, "21") +plt.imsave +create_daily_image(dict_with_data, label_of_image) \ No newline at end of file From 18c97c5229c733b3fc9bc34eb2a5257eff3adf9e Mon Sep 17 00:00:00 2001 From: lokiplot Date: Mon, 7 Dec 2020 12:43:47 +0300 Subject: [PATCH 7/9] =?UTF-8?q?=D1=81=D1=8D=D0=BC=D0=BF=D0=BB=D1=8B=20?= =?UTF-8?q?=D0=BA=D0=B0=D1=80=D1=82=D0=B8=D0=BD=D0=BE=D0=BA=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 21.png | Bin 0 -> 12534 bytes images/zsd.png | Bin 0 -> 55476 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 21.png create mode 100644 images/zsd.png diff --git a/21.png b/21.png new file mode 100644 index 0000000000000000000000000000000000000000..b75110d9143809804cfd86c7eaa2d554cf1a3942 GIT binary patch literal 12534 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 literal 0 HcmV?d00001 diff --git a/images/zsd.png b/images/zsd.png new file mode 100644 index 0000000000000000000000000000000000000000..b60273e20b01673d68bcd3763b0087be7afa5bb4 GIT binary patch literal 55476 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 literal 0 HcmV?d00001 From d2e7d1bc8488519b598bdde8d7670ef52628c290 Mon Sep 17 00:00:00 2001 From: lokiplot Date: Tue, 15 Dec 2020 21:24:17 +0300 Subject: [PATCH 8/9] =?UTF-8?q?=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D1=8F=D0=B5=D1=82=20=D1=81=D0=BE=D0=BE=D0=B1=D1=89=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D1=81=20=D0=BD=D0=B5=D1=81=D0=BA=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=BA=D0=B8=D0=BC=D0=B8=20=D1=81=D1=82=D1=80=D0=BE=D0=BA?= =?UTF-8?q?=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- messages_test.py | 405 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 338 insertions(+), 67 deletions(-) diff --git a/messages_test.py b/messages_test.py index c7e173f..e1adbaa 100644 --- a/messages_test.py +++ b/messages_test.py @@ -4,17 +4,47 @@ import time import random import math +from collections import deque 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): + def __init__(self, group_id, freq, 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.group_id = group_id self.begin = datetime.datetime.now() - self.frequency = freq + self.master_id = master_id + self.frequency = datetime.timedelta(hours=freq//60, minutes=(freq % 60)) + #making period in datetime format + self.analyses_per_day = 1440 // freq + analyses_per_week = self.analyses_per_day * 7 + #self.percents = [0 for i in range(analyses_per_week)] + self.avg_percents = {} + for i in range(analyses_per_week): + self.avg_percents[self.begin + self.frequency * i] = 0 + self.archive = [[0, 0, 0, 0] for i in range(analyses_per_week)] + self.number = [0 for i in range(analyses_per_week)] + self.index_to_date = [] + for i in range(analyses_per_week): + week_day = i // self.analyses_per_day + time_index = i % self.analyses_per_day * self.frequency + hour_index = time_index // 60 + minute_index = time_index % 60 + s = days_of_the_week[week_day] + ", " + str(hour_index) + ":" + str(minute_index) + if minute_index == 0: + s += '0' + self.index_to_date.append(s) def count_online_proportion(self): """ @@ -43,8 +73,11 @@ def count_online_proportion(self): return one_more_number_of_members, online def group_analyse(self): + """ + counts the online percent + :return: + """ t0 = time.time() - current_time = datetime.datetime.now() all_members, online_members = self.count_online_proportion() percent = online_members / all_members * 100 @@ -53,13 +86,72 @@ def group_analyse(self): t0 = time.time() - t0 return t0, percent - def work_and_print(self, count, current_user_id): + def update_data(self, new_one, cell_to_update): + if self.number[cell_to_update] >= 4: + self.percents[cell_to_update] *= 4 + self.percents[cell_to_update] = self.percents[cell_to_update] - self.archive[cell_to_update][0] + new_one + self.percents[cell_to_update] /= 4 + else: + self.percents[cell_to_update] *= self.number[cell_to_update] + self.percents[cell_to_update] = self.percents[cell_to_update] + new_one + self.number[cell_to_update] += 1 + self.percents[cell_to_update] /= self.number[cell_to_update] + for j in range(3): + self.archive[cell_to_update][j] = self.archive[cell_to_update][j + 1] + self.archive[cell_to_update][3] = new_one + + def recommend_day(self, count): + day = datetime.datetime.now().weekday() + start = day * self.analyses_per_day + finish = (day + 1) * self.analyses_per_day + recommend_message = "Today the best time was " + max_online, best_time = 0, 0 + for i in range(start, finish): + if self.percents[i] > max_online: + max_online = self.percents[i] + best_time = i + recommend_message += self.index_to_date[best_time] + ": " + str(max_online) + "%" + vk_api2.messages.send(user_id=self.master_id, message=recommend_message, random_id=count) + return count + 1 + + def recommend_week(self, count): + recommend_message = "This week the best time was " + max_online, best_time, max_average_during_the_day, best_day = 0, 0, 0, 0 + for j in range(7): + average = 0 + for i in range(self.analyses_per_day * j, self.analyses_per_day * (j + 1)): + average += self.percents[i] + if self.percents[i] > max_online: + max_online = self.percents[i] + best_time = i + if average > max_average_during_the_day: + max_average_during_the_day = average + best_day = j + max_average_during_the_day /= self.analyses_per_day + recommend_message += self.index_to_date[best_time] + ": " + str(max_online) + "%" + vk_api2.messages.send(user_id=self.master_id, message=recommend_message, random_id=count) + 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=(count + 1)) + return count + 2 + + def work_and_print(self, count): + """ + updates data and sends current online percent + :param count - is needed to send messages: + :return: + """ + week_day = datetime.datetime.now().weekday() + t = datetime.datetime.now() + array_cell = self.analyses_per_day * week_day + (t.hour * 60 + t.minute) // self.frequency start_time, percent = self.group_analyse() + self.update_data(percent, array_cell) string = "Online percent in " + self.group_id + " is " + str(percent) + "%" - vk_api2.messages.send(user_id=current_user_id, message=string, random_id=count) + vk_api2.messages.send(user_id=self.master_id, message=string, random_id=count) return count + 1 - def group_analyse1(self): + ''' + def group_analyse_console(self): t0 = time.time() all_members, online_members = self.count_online_proportion() percent = online_members / all_members * 100 @@ -95,6 +187,31 @@ def group_analyse1(self): "_____________________________________________________________________________________________") t0 = time.time() - t0 time.sleep(self.frequency * 60 - t0) + ''' + + def repeat_the_process(self, count, next_time): + """ + :param count - is needed to send messages to the user: + :param next_time - when to analyse again: + :return: + """ + next_time = count_new_time(next_time, self.frequency) + count = self.work_and_print(count) + return next_time, count + + def count_times(self, count): + """ + This function runs in the very beginning. It counts when to start analysing and when to give recommendations + """ + current_time = datetime.datetime.now() + minutes_now = current_time.minute + current_time.hour * 60 + round_current_time = current_time.replace(microsecond=0, second=0) + next_time = count_new_time(round_current_time, self.frequency - minutes_now % self.frequency) + next_recommend = current_time.replace(microsecond=0, second=0, minute=0, hour=0) + next_recommend = count_new_time(next_recommend, 1440) + ok_message = "OK! Starting in " + str(self.frequency - minutes_now % self.frequency) + " minutes!" + vk_api2.messages.send(user_id=self.master_id, message=ok_message, random_id=count) + return count + 1, next_time, next_recommend token = "65e6efa565e6efa565e6efa54f6593fb1f665e665e6efa53a5c6937a4636b3416a8bd92" @@ -109,6 +226,7 @@ def group_analyse1(self): 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_user_last_seen(profile_id): @@ -181,16 +299,17 @@ def is_online(online_time): def get_message(group_id, server_, ts_, key_): - # some = vk_api2.groups.getLongPollServer(group_id=group_id) + # 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() - # print(response) 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: @@ -211,7 +330,7 @@ def count_new_time(time_now, period): if hours > 23: hours %= 24 days += 1 - if days > month_length[time_now.month]: + if days > month_length[time_now.month - 1]: days = 1 months += 1 if months > 12: @@ -223,67 +342,219 @@ def count_new_time(time_now, period): return new_time +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 == "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(): + return -1, "", -1 + elif int(period) < 60 and 60 % int(period) != 0 or int(period) < 15 or 1440 % int(period) != 0: + return 6, "", -1 + 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 + if message == "Recommend: day" or message == "recommend: day": + return 7, "", -1 + if message == "Recommend: week" or message == "recommend: week": + return 8, "", -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 incorrect_period_value(count, current_user_id): + # informs about the mistake + s = "Period should be not less than 15 and should divide 1440!" + vk_api2.messages.send(user_id=current_user_id, message=s, 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'] + something = 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 = "Maaaaaaan! I don't understand you... \n" + string += "You can SAY HELLO: your message should start with 'hello' or 'привет'; \n" + string += "You can give me a task in such a way: 'group_id: 'the_group_id'; period: 'period'. \n" + string += "Period should be pretty and not too small) \n" + string += "You can cancel YOUR task: just send 'stop' \n" + string += "Have a good day!!\n" + # vk_api2.messages.send(user_id=current_user_id, sticker_id=8616, random_id=count) + vk_api2.messages.send(user_id=current_user_id, message=string, attachment="photo-200698416_457239021", + random_id=(count + 1)) + return count + 2 + + +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 + + +def not_available(count, current_user_id): + vk_api2.messages.send(user_id=current_user_id, message="Not available now!", random_id=count) + vk_api2.messages.send(user_id=current_user_id, sticker_id=4331, random_id=(count + 1)) + return count + 2 + + +# 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 = count_new_time(datetime.datetime.now().replace(microsecond=0, second=0), 50) message = "" run = 1 -count = 0 +count = 10000 +master_id = -1 have_a_task = 0 -group = Group(-1, -1) +next_recommend = datetime.datetime.now() +next_time = datetime.datetime.now() + +# the end of 'initialization' block + while run: + # wait for new requests 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: - current_minutes = time_start.minute - next_time = count_new_time(next_time, group.frequency) - count = group.work_and_print(count, master_id) - - # print(message) - if message.count(';') > 0: - index = message.find(";") - if index > 10 and len(message) - index - 10 > 0: - if message[0: 10] == "group_id: " and message[index + 2: index + 10] == "period: ": - analyse_group_id = message[10: index].strip() - frequency = message[index + 10:].strip() - if frequency == "frequently" or frequency.isdigit(): - if frequency == "frequently": - frequency_number = 0 - else: - frequency_number = int(frequency) - have_a_task = 1 - master_id = 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 - else: - print("Error") - elif message == "stop" or message == "Stop": - run = 0 - vk_api2.messages.send(user_id=current_user_id, message="Goodbye!", random_id=count) - count += 1 - elif message == "hello" or message == "привет" or message == "Hello" or 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) - count += 1 - elif message != "": - string = "I understand just such a format: 'group_id: *id*; period: * period time *'. Please, write correctly))" - vk_api2.messages.send(user_id=current_user_id, message=string, random_id=count) - count += 1 + # 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 = count_new_time(change_server, 50) + + # chek if it is time to analyse again + if have_a_task and datetime.datetime.now() >= next_time: + next_time, count = group.repeat_the_process(count, next_time) + + # check if it is time to give recommendations + if have_a_task and datetime.datetime.now() >= next_recommend: + count = group.recommend_day(count) + if datetime.datetime.now().weekday() == 0: + count = group.recommend_week(count) + next_recommend = count_new_time(next_recommend, 1440) + + code, analyse_group_id, frequency_number = process_input_message(message) + + if code == 3: + # this code means that user gave a correct task + if not have_a_task or current_user_id == master_id: + ''' + if the bot is free or is working on the task of the same user, who is giving a new one. In this case + bot receives a new task and forgets about the old one if it did exist + ''' + have_a_task = 1 + # group initialising block + group = Group(analyse_group_id, frequency_number, current_user_id) + master_id = group.master_id + # counting time when to start and when to give a new recommendation + count, next_time, next_recommend = group.count_times(count) + else: + # in the case when the user wants to give a new task while bot is already working on the OTHER user's task. + count = not_available(count, current_user_id) + elif code == 2: + # if the bot has received a secret password which switches it off + run, count = switch_off(count, current_user_id) + elif code == 1: + # if the user WHO GAVE A TASK decided to cancel it with a 'stop' or 'Stop' command + if have_a_task: + del group + have_a_task, count = cancel_the_task(have_a_task, current_user_id, count, master_id) + elif code == 4: + # greeting + count = say_hello(count, current_user_id) + elif code == -1: + # unknown message + count = instruction_message(count, current_user_id) + elif code == 5: + # if the group does not exist + count = incorrect_id(count, current_user_id) + elif code == 6: + ''' + if the period doesn't divide 60 (if it is less than 60) or if it doesn't divide 1440 + (number of minutes in a day) + ''' + count = incorrect_period_value(count, current_user_id) + elif code == 7: + # If the user needs today's best online percent and the time it happened + if have_a_task and current_user_id == master_id: + count = group.recommend_day(count) + else: + count = not_available(count, current_user_id) + elif code == 8: + # If the user needs week's best online percent and the time it happened + if have_a_task and current_user_id == master_id: + count = group.recommend_week(count) + else: + count = not_available(count, current_user_id) From 7f7a78d924e45182b044468c9f048ccac7cd10fd Mon Sep 17 00:00:00 2001 From: lokiplot Date: Mon, 28 Dec 2020 09:40:45 +0300 Subject: [PATCH 9/9] =?UTF-8?q?=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA?= =?UTF-8?q?=D0=B0=20=D1=84=D0=BE=D1=82=D0=BE=D0=B3=D1=80=D0=B0=D1=84=D0=B8?= =?UTF-8?q?=D0=B9,=20=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/misc.xml | 2 +- .idea/modules.xml | 2 +- .idea/team-project.iml | 7 +++++++ main.py | 24 +++++++++++++++++++++++- upload.py | 39 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 .idea/team-project.iml create mode 100644 upload.py diff --git a/.idea/misc.xml b/.idea/misc.xml index a2e120d..10e932d 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index f9150b2..0707604 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/.idea/team-project.iml b/.idea/team-project.iml new file mode 100644 index 0000000..34a1e97 --- /dev/null +++ b/.idea/team-project.iml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/main.py b/main.py index 0632a3d..cae29ca 100644 --- a/main.py +++ b/main.py @@ -42,15 +42,37 @@ def count_online_proportion(self): return one_more_number_of_members, online +our_group_id = "memkn_funclub" token = "65e6efa565e6efa565e6efa54f6593fb1f665e665e6efa53a5c6937a4636b3416a8bd92" group_token = "17e681fbe171945431a04f1abc752d41ff888698288abf74124de4e782c67f36e76484601991870f56b7a" analyse_group_id = 'memkn' session1 = vk.AuthSession(access_token=token) vk_api = vk.API(session1, v=5.92) - +album_id = "278041850" month_length = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] +def upload_picture(picture_path): + r = requests.get('https://api.vk.com/method/photos.getUploadServer', + params={ + 'access_token': token, + 'album_id': album_id, + 'group_id': our_group_id, + }).json() + url = r['response']['upload_url'] + file = {'file1': open(picture_path, 'rb')} + ur = requests.post(url, files=file).json() + result = requests.get('https://api.vk.com/method/photos.save', + params={ + 'access_token': token, + 'album_id': ur['aid'], + 'group_id': ur['gid'], + 'server': ur['server'], + 'photos_list': ur['photos_list'], + 'hash': ur['hash'] + }).json() + print(result) + def get_user_last_seen(profile_id): """ shows when user was online diff --git a/upload.py b/upload.py new file mode 100644 index 0000000..5f70bee --- /dev/null +++ b/upload.py @@ -0,0 +1,39 @@ +import vk +import datetime +import time +import random +import math +import requests +import json + +our_group_id = 200698416 +token = "65e6efa565e6efa565e6efa54f6593fb1f665e665e6efa53a5c6937a4636b3416a8bd92" +group_token = "17e681fbe171945431a04f1abc752d41ff888698288abf74124de4e782c67f36e76484601991870f56b7a" +analyse_group_id = 'memkn' +new_token = "812c2975fc2ac0785252d97e8b5011f45e873a00dfb98b15299aec060ff7b890d06c4822feab0626e198c" +session1 = vk.AuthSession(access_token=new_token) +vk_api = vk.API(session1, v=5.92) +album_id = 278041850 +month_length = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] +version = '5.95' + + +def upload_picture(picture_path): + r = vk_api.photos.getUploadServer(group_id=our_group_id, album_id=album_id) + url = r['upload_url'] + file = {'file1': open(picture_path, 'rb')} + ur = requests.post(url, files=file).json() + result = requests.get('https://api.vk.com/method/photos.save', + params={ + 'access_token': new_token, + 'album_id': ur['aid'], + 'group_id': ur['gid'], + 'server': ur['server'], + 'photos_list': ur['photos_list'], + 'hash': ur['hash'], + 'v': version, + }).json() + return result + + +upload_picture("21.png")