def save_client(self, client: VKinderClient, force_country_update=False): """ Manual UPSERT of single client in DB """ log(f'[{client.fname} {client.lname}] Saving client\'s info to DB', is_debug_msg=self.debug_mode) client_db = self.__session.query(Clients).filter( Clients.vk_id == client.vk_id).first() if not client_db: client_db = Clients() client_db.vk_id = client.vk_id client_db.fname = client.fname client_db.lname = client.lname client_db.domain = client.domain # preserve country_id and country_name, restore them in case client revisit if not client_db.id or force_country_update: client_db.country_id = client.country_id client_db.country_name = client.country_name elif client_db.id: client.country_id = client_db.country_id client.country_name = client_db.country_name client_db.city_id = client.city_id client_db.city_name = client.city_name client_db.hometown = client.hometown client_db.birth_date = client.birth_date client_db.birth_day = client.birth_day client_db.birth_month = client.birth_month client_db.birth_year = client.birth_year client_db.sex_id = client.sex_id client_db.updated = client.last_contact self.__session.add(client_db) self.__session.commit() # load new id from base if client was just created client.db_id = client_db.id client.searches = self.load_searches(client)
def do_show_next_user(self, client: VKinderClient): self.send_typing_activity(client) client.status = STATUSES['decision_wait'] client.active_user = client.get_next_user() # if we already showed all pairs if not client.active_user: self.do_send_to_start_due_to_reach_end(client) self.do_propose_start_search(client) return client.active_user.photos = self.vk_personal.get_user_photos( client.active_user.vk_id) if not client.active_user.photos: client.active_user.photos = self.vk_personal.get_user_photos( client.active_user.vk_id, album_id='wall') photos = [ f'photo{photo.owner_id}_{photo.id}' for photo in client.active_user.photos ] photos_str = ','.join(photos) log( f'[{client.fname} {client.lname}] Showing user: {client.active_user.fname} ' f'{client.active_user.lname} with {len(client.active_user.photos)} photos', self.debug_mode) age_str = f', возраст: {client.active_user.age}' if client.active_user.age else '' user_info = f'{client.active_user.fname} {client.active_user.lname} ' user_info += f'({client.active_user.city_name}{age_str})' user_info += f'{last_seen(client.active_user.last_seen_time)}\nhttps://vk.com/{client.active_user.domain}' keyboard = self.cmd.kb(['yes', 'no', 'ban', None, 'back', 'quit']) self.send_msg(client, user_info, attachment=photos_str, keyboard=keyboard) self.send_msg(client, PHRASES['do_you_like_it']) self.db.save_photos(client)
def save_search(self, client: VKinderClient): """ Saves customs search, with delete old searches (more than search_history_limit) """ log(f'[{client.fname} {client.lname}] Saving client\'s search to DB', is_debug_msg=self.debug_mode) # pass search saving if it was already loaded from history if client.search.id: return search_history = self.__session.query( Searches.id).filter(Searches.client_id == client.db_id).order_by( Searches.updated.desc()).limit(self.search_history_limit - 1).all() delete_expr = delete(Searches).where( and_(not_(Searches.id.in_(search_history)), Searches.client_id == client.db_id)) self.__session.execute(delete_expr) self.__session.commit() search = Searches(client_id=client.db_id, min_age=client.search.min_age, max_age=client.search.max_age, sex_id=client.search.sex_id, status_id=client.search.status_id, city_id=client.search.city_id, city_name=client.search.city_name, updated=func.now()) self.__session.add(search) self.__session.commit() # load new id from base because new search was just created client.search.id = search.id client.searches.append(client.search)
def load_client_from_db(self, vk_id: str): """ Gets client by its VK id """ log(f'Loading client info from DB', is_debug_msg=self.debug_mode) client = self.__session.query(Clients).filter( Clients.vk_id == vk_id).first() if client: return client.convert_to_ApiUser()
def load_searches(self, client: VKinderClient) -> list[VKinderSearch]: """ Loads all search history parameters """ log(f'[{client.fname} {client.lname}] Loading all client\'s searches from DB', is_debug_msg=self.debug_mode) result = self.__session.query(Searches).filter( Searches.client_id == client.db_id).order_by( Searches.updated.desc()).all() return result
def load_users_from_db(self, client: VKinderClient): """ Gets all rated users by client, using rating as filter """ log(f'[{client.fname} {client.lname}] Loading users from DB with rating {client.rating_filter}', is_debug_msg=self.debug_mode) users = self.__session.query(Users).join(ClientsUsers).filter( ClientsUsers.client_id == client.db_id).filter( ClientsUsers.rating_id == client.rating_filter).all() client.found_users = [] for user in users: client.found_users.append( user.convert_to_ApiUser(client.rating_filter)) log(f'[{client.fname} {client.lname}] Loaded {len(client.found_users)} users from DB', is_debug_msg=self.debug_mode)
def __init__(self, group_token: str, person_token: str, group_id: str, app_id: str, db_name: str, db_login: str, db_password: str, db_driver: str, db_host: str, db_port: int, retry_timeout: int = 1, retry_attempts: int = sys.maxsize, debug_mode=False): self.client_activity_timeout = 300 self.debug_mode = debug_mode self.clients_pool = {} self.group_id = group_id self.cmd = Commands(COMMANDS) # countries received once per application launch (by request), as they almost doesn't changes self.countries = [] self.rebuild_tables = False self.retry_timeout = retry_timeout self.retry_attempts = retry_attempts self.vk_personal = VkApiClient(person_token, app_id, debug_mode=debug_mode) self.db = VKinderDb(db_name, db_login, db_password, db_driver=db_driver, db_host=db_host, db_port=db_port, debug_mode=debug_mode) self.__initialized = self.vk_personal.is_initialized and self.db.is_initialized self.vk_group = vk_api.VkApi(token=group_token) try: self.long_poll = VkBotLongPoll(self.vk_group, self.group_id) self.vk_api = self.vk_group.get_api() except BaseException as e: self.__initialized = False log(f'{type(self).__name__} init failed: {e.error["error_msg"]}', self.debug_mode) if self.__initialized: log(f'{type(self).__name__} initialised successfully', self.debug_mode)
def get_user_photos(self, owner_id: str = None, album_id='profile', rev: bool = True, extended: bool = True, photo_sizes: bool = True, sort_by: str = 'popularity', needed_qty: int = 3) -> list[ApiPhoto]: """ Getting all user photos with sorting and returning limited quantity. For external use. https://vk.com/dev/photos.get :param owner_id: ID of user, if None - your token's account ID will be taken :param album_id: one of album type: wall, profile, saved :param rev: reversed chronological order :param photo_sizes: True, if needed additional info abt photos :param extended: True, if needed likes, comments, tags, reposts :param needed_qty: max quantity to be returned :param sort_by: 'popularity' or 'date' :return: list of ApiPhoto objects or empty list """ result = [] if not self.__initialized: log(f'Error in get_user_photos: {type(self).__name__} not initialized', self.debug_mode) return result owner_id = owner_id if owner_id else self.__user_id offset = 0 count = 1000 log(f'Getting user {owner_id} photos from {album_id}...', self.debug_mode) while True: photos = self.__get_user_photos(count=count, offset=offset, owner_id=owner_id, album_id=album_id, rev=rev, extended=extended, photo_sizes=photo_sizes) if not photos.success: log(f'Loading photos failed: {photos.message}', self.debug_mode) break items_count = len(photos.json_object['items']) log(f'Loaded {items_count} photos more...', self.debug_mode) # if we reached the end if items_count == 0: break result += photos.json_object['items'] # if returned less items than requested, suppose that we reached the end # or if next iteration will return more items than we requested if items_count < count: break offset += count # prevent ban from service time.sleep(self.request_delay) log(f'Loaded totally {len(result)} photos', self.debug_mode) result = self.__process_photos(result, sort_by=sort_by, needed_qty=needed_qty) result = [ApiPhoto(row) for row in result] return result
def __init__(self, token: str, app_id: str, user_id=None, version: str = '5.124', debug_mode=False, base_url=None): super().__init__() self.API_BASE_URL = base_url if base_url else BASE_URL self.debug_mode = debug_mode self.__vksite = 'https://vk.com/' self.token = token self.app_id = app_id self.__version = version self.__required_user_fields = ['sex', 'bdate', 'domain', 'country', 'city', 'last_seen', 'home_town'] self.__headers = {'User-Agent': 'Netology'} self.__params = {'access_token': self.token, 'v': self.__version} self.__img_types = {'s': 1, 'm': 2, 'x': 3, 'o': 4, 'p': 5, 'q': 6, 'r': 7, 'y': 8, 'z': 9, 'w': 10} self.request_delay = 0.33 # below line needed for get_users only self.__initialized = True # try to instantiate user = self.__get_users(user_ids=user_id) is_deactivated = False if user.success: is_deactivated = user.json_object[0].get('deactivated', False) if user.success and not is_deactivated: self.__initialized = True self.__user_id = str(user.json_object[0]['id']) self.__first_name = user.json_object[0]['first_name'] self.__last_name = user.json_object[0]['last_name'] self.__domain = user.json_object[0]['domain'] self.__status = f'{type(self).__name__} initialised with user: {self.__first_name} {self.__last_name} ' \ f'(#{self.__user_id})' else: self.__initialized = False self.__user_id = None self.__first_name = None self.__last_name = None self.__domain = None if is_deactivated: user.message = 'User is deactivated' # error message will be in status self.__status = f'{type(self).__name__} init failed: ' + user.message self.__status += f'\nPls check a personal token via this URL:' \ f'\n{self.get_auth_link(self.app_id, "offline,photos,status,groups")}' log(self.__status, debug_mode)
def __init__(self, db_name, db_login, db_password, db_driver='postgresql', db_host='localhost', db_port=5432, debug_mode=False): self.debug_mode = debug_mode self.__sqlalchemy = sa self.search_history_limit = 10 self.rebuild = self.load_config()['rebuild_tables'] try: self.__engine = sa.create_engine( f'{db_driver}://{db_login}:{db_password}@{db_host}:{db_port}/{db_name}' ) self.__engine.connect().close() self.__session = sessionmaker(bind=self.__engine)() log(f'{type(self).__name__} successfully connected to DB', self.debug_mode) if self.rebuild: log(f'Rebuilding tables...', self.debug_mode) clear_db(sa, self.__engine) Base.metadata.create_all(self.__engine) self.__initialized = True except OperationalError as e: log(f'{type(self).__name__} unable connect to DB: {e}', self.debug_mode) self.__initialized = False self.__session = None
def save_user_rating(self, client: VKinderClient): """ Saves user rating (when client liked/disliked/banned), updates exist rating """ log(f'[{client.fname} {client.lname}] Saving user rating to DB', is_debug_msg=self.debug_mode) client_db = self.__session.query(Clients).filter( Clients.vk_id == client.vk_id).first() user_db = self.__session.query(Users).filter( Users.vk_id == client.active_user.vk_id).first() clients_user = self.__session.query(ClientsUsers).filter( and_(ClientsUsers.client_id == client_db.id, ClientsUsers.user_id == user_db.id)).first() if not clients_user: clients_user = ClientsUsers(client_id=client_db.id, user_id=user_db.id, rating_id=client.active_user.rating_id) else: clients_user.rating_id = client.active_user.rating_id clients_user.updated = func.now() self.__session.add(clients_user) self.__session.commit()
def save_users(self, client: VKinderClient): """ Making manual batch UPSERT of users with relations to search using many-to-many relations """ if not client.found_users: log(f'[{client.fname} {client.lname}] No users to save in DB', is_debug_msg=self.debug_mode) return log(f'[{client.fname} {client.lname}] Saving users info to DB', is_debug_msg=self.debug_mode) search = self.__session.query(Searches).filter( Searches.id == client.search.id).first() vk_ids = [client.vk_id for client in client.found_users] users = self.__session.query(Users).filter( Users.vk_id.in_(vk_ids)).all() matches = {user.vk_id: user for user in users} users_list = [] for found_user in client.found_users: user = matches.get(found_user.vk_id, Users()) user.vk_id = found_user.vk_id user.fname = found_user.fname user.lname = found_user.lname user.domain = found_user.domain user.country_id = found_user.country_id user.country_name = found_user.country_name user.city_id = found_user.city_id user.city_name = found_user.city_name user.hometown = found_user.hometown user.birth_date = found_user.birth_date user.birth_day = found_user.birth_day user.birth_month = found_user.birth_month user.birth_year = found_user.birth_year user.sex_id = found_user.sex_id user.updated = func.now() user.searches.append(search) users_list.append(user) self.__session.add_all(users_list) self.__session.commit()
def save_photos(self, client: VKinderClient): """ Saves users photo information, with clearance of all previously saved photo """ log(f'[{client.fname} {client.lname}] Saving photo\'s info to DB', is_debug_msg=self.debug_mode) # let's clear all previous user photos user_db = self.__session.query(Users).filter( Users.vk_id == client.active_user.vk_id).first() self.__session.query(Photos).filter( and_(Photos.owner_id == user_db.id)).delete() self.__session.commit() photos_list = [] for photo in client.active_user.photos: photo_db = Photos(url=photo.url, likes_count=photo.likes_count, comments_count=photo.comments_count, reposts_count=photo.reposts_count, photo_id=photo.id, owner_db_id=user_db.id) photos_list.append(photo_db) self.__session.add_all(photos_list) self.__session.commit()
def search_users(self, city_id: int = None, sex_id: int = None, love_status_id: int = None, age_from: int = None, age_to: int = None, q: str = None, has_photo: bool = True, hometown: str = None, sort: bool = True) -> list[ApiUser]: """ Search for VK users by different parameters. For external use. https://vk.com/dev/users.search :param city_id: country ID from catalog VK :param sex_id: sex ID from catalog VK :param love_status_id: love status from catalog BK :param age_from: any positive integer :param age_to: any positive integer :param q: search string :param hometown: city name :param has_photo: True or False :param sort: True or False :return: list of ApiUser objects or empty list """ result = [] if not self.__initialized: log(f'Error in search_users: {type(self).__name__} not initialized', self.debug_mode) return result offset = 0 count = 1000 log(f'\nSearching users...', self.debug_mode) while True: users = self.__search_users(count=count, offset=offset, city_id=city_id, sex_id=sex_id, love_status_id=love_status_id, age_from=age_from, age_to=age_to, q=q, has_photo=has_photo, hometown=hometown, sort=sort) if not users.success: log(f'Loading users failed: {users.message}', self.debug_mode) break items_count = len(users.json_object['items']) log(f'Loaded {items_count} users', self.debug_mode) # if we reached the end if items_count == 0: break result += users.json_object['items'] # if returned less items than requested, suppose that we reached the end # or if next iteration will return more items than we requested if items_count < count: break offset += count # prevent ban from service time.sleep(self.request_delay) result = [ApiUser(dict(row)) for row in result] return result
def get_users(self, user_ids=None, fields: [str] = None) -> list[ApiUser]: """ This method receive users info by their ID's. For external use. Description here: https://vk.com/dev/users.get :param fields: list additional fields of users in strings to be requested :param user_ids: list of one or more user IDs in strings form :return: list of ApiUser objects or empty list """ result = [] if not self.__initialized: log(f'Error in get_users: {type(self).__name__} not initialized', self.debug_mode) return result log(f'Getting users...', self.debug_mode) users = self.__get_users(user_ids=user_ids, fields=fields) if not users.success: log(f'Getting users failed: {users.message}', self.debug_mode) return result items_count = len(users.json_object) log(f'Got {items_count} users', self.debug_mode) result += users.json_object result = [ApiUser(dict(row)) for row in result] return result
def search_cities(self, country_id: int = None, city_name: str = None) -> list[ApiCity]: """ Searching all cities by name. For external use. https://vk.com/dev/database.getCities :param city_name: search name (might be partial) :param country_id: country ID from catalog VK :return: list of ApiCity objects or empty list """ result = [] if not self.__initialized: log(f'Error in search_cities: {type(self).__name__} not initialized', self.debug_mode) return result offset = 0 count = 1000 log(f'\nSearching city name: "{city_name}" at country {country_id} ...', self.debug_mode) while True: if city_name: cities = self.__search_cities(count=count, offset=offset, country_id=country_id, q=city_name) else: cities = self.__search_cities(count=count, offset=offset, country_id=country_id, need_all=True) if not cities.success: log(f'Loading cities failed: {cities.message}', self.debug_mode) break items_count = len(cities.json_object['items']) log(f'Loaded {items_count} cities', self.debug_mode) # if we reached the end if items_count == 0: break result += cities.json_object['items'] # if returned less items than requested, suppose that we reached the end # or if next iteration will return more items than we requested if items_count < count: break offset += count # prevent ban from service time.sleep(self.request_delay) result = [ApiCity(dict(row)) for row in result] return result
def get_countries(self, code: str = None) -> list[ApiCountry]: """ Full list of countries or specific country byt its code https://vk.com/dev/database.getCountries :param code: coma separated ISO 3166-1 alpha-2 codes - RU,UA,BY :return: list of ApiCountry objects or empty list """ result = [] if not self.__initialized: log(f'Error in get_countries: {type(self).__name__} not initialized', self.debug_mode) return result offset = 0 count = 1000 log(f'\nRequesting max {count} countries at once from VK...', self.debug_mode) while True: if code: countries = self.__get_countries(count=count, offset=offset, code=code) else: countries = self.__get_countries(count=count, offset=offset, need_all=True) if not countries.success: log(f'Loading countries failed: {countries.message}', self.debug_mode) break items_count = len(countries.json_object['items']) log(f'Loaded {items_count} countries', self.debug_mode) # if we reached the end if items_count == 0: break result += countries.json_object['items'] # if returned less items than requested, suppose that we reached the end # or if next iteration will return more items than we requested if items_count < count: break offset += count # prevent ban from service time.sleep(self.request_delay) result = [ApiCountry(dict(row)) for row in result] return result
def start(self): retries = 0 if not self.__initialized: log(f'Can\'t start: {type(self).__name__} not initialized', self.debug_mode) return while True and retries < self.retry_attempts: try: retries += 1 log( f'Listening for messages in group {self.group_id}...(retry #{retries})', self.debug_mode) self.long_poll.update_longpoll_server() for event in self.long_poll.listen(): if event.type == VkBotEventType.MESSAGE_NEW: client = self.get_client( str(event.object.message['from_id'])) msg = event.object.message['text'] log(f'[{client.fname} {client.lname}] typed "{msg}"', self.debug_mode) msg = msg.lower() # obligatory exit from conversation, works at any page, highest priority, # one exception: if this command not very first one if msg in self.cmd.get( 'quit' ) and client.status != STATUSES['has_contacted']: self.do_say_goodbye(client) continue # imitation of custom search - works at any page, highest priority if msg == 'test': client.reset_search() client.search.sex_id = randrange(0, 2, 1) client.search.status_id = randrange(1, 8, 1) client.search.city_id = 1 client.search.city_name = 'Москва' client.search.min_age = randrange(0, 60, 1) client.search.max_age = randrange( client.search.min_age, 127, 1) client.rating_filter = RATINGS['new'] self.do_users_search(client) continue # if client prints/press something at "Welcome screen" page if client.status in (STATUSES['invited'], STATUSES['has_contacted']) \ and (msg in self.cmd.get('yes') or msg in self.cmd.get('new search')): self.do_start_search_creating(client) elif client.status in (STATUSES['invited'], STATUSES['has_contacted']) \ and msg in self.cmd.get('show history'): self.do_show_search_history(client) elif client.status in (STATUSES['invited'], STATUSES['has_contacted']) \ and (msg in self.cmd.get('liked') or msg in self.cmd.get('disliked') or msg in self.cmd.get('banned')): self.do_show_rated_users(msg, client) elif client.status in (STATUSES['invited'], STATUSES['has_contacted'] ) and msg in self.cmd.get('no'): self.do_say_goodbye(client) elif client.status == STATUSES['has_contacted']: self.do_propose_start_search(client) # if client prints/press something in "Select search history" page elif client.status == STATUSES[ 'search_history_input_wait'] and msg in self.cmd.get( 'back'): self.do_propose_start_search(client) # if client prints/press something in "Select search history" page elif client.status == STATUSES[ 'search_history_input_wait']: self.on_search_history_choose(msg, client) # if client prints/press something in "Search country" page elif client.status == STATUSES[ 'country_input_wait'] and msg in self.cmd.get( 'back'): self.do_start_search_creating(client) # if client prints/press something in "Search country" page elif client.status == STATUSES['country_input_wait']: self.on_country_name_input(msg, client) # if client prints/press something in "Select country" page elif client.status == STATUSES[ 'country_choose_wait'] and msg in self.cmd.get( 'back'): self.do_propose_country_name_input(client) # if client prints/press something in "Select country" page elif client.status == STATUSES['country_choose_wait']: self.on_country_name_choose(msg, client) # if client prints/press something in "Search city" page elif client.status == STATUSES[ 'city_input_wait'] and msg in self.cmd.get( 'back'): self.do_propose_start_search(client) # if client prints/press something in "Search city" page elif client.status == STATUSES[ 'city_input_wait'] and msg in self.cmd.get( 'country'): self.do_propose_country_name_input(client) # if client prints/press something in "Search city" page elif client.status == STATUSES['city_input_wait']: self.do_propose_city_name_choose(msg, client) # if client prints/press something in "Select city" page elif client.status == STATUSES[ 'city_choose_wait'] and msg in self.cmd.get( 'back'): self.do_start_search_creating(client) # if client prints/press something in "Select city" page elif client.status == STATUSES['city_choose_wait']: self.on_city_name_choose(msg, client) # if client prints/press something in "Select sex" page elif client.status == STATUSES[ 'sex_choose_wait'] and msg in self.cmd.get( 'back'): self.do_start_search_creating(client) # if client prints/press something in "Select sex" page elif client.status == STATUSES['sex_choose_wait']: self.on_sex_choose(msg, client) # if client prints/press something in "Select love status" page elif client.status == STATUSES[ 'status_choose_wait'] and msg in self.cmd.get( 'back'): self.do_propose_sex_choose(client) # if client prints/press something in "Select love status" page elif client.status == STATUSES['status_choose_wait']: self.on_status_choose(msg, client) # if client prints/press something in "Min age" page elif client.status == STATUSES[ 'min_age_input_wait'] and msg in self.cmd.get( 'back'): self.do_propose_status_choose(client) # if client prints/press something in "Min age" page elif client.status == STATUSES['min_age_input_wait']: self.on_min_age_enter(msg, client) # if client prints/press something in "Max age" page elif client.status == STATUSES[ 'max_age_input_wait'] and msg in self.cmd.get( 'back'): self.do_propose_min_age_enter(client) # if client prints/press something in "Max age" page elif client.status == STATUSES['max_age_input_wait']: self.on_max_age_enter(msg, client) # if client prints/press something in "User profile view" page elif client.status == STATUSES[ 'decision_wait'] and msg in self.cmd.get( 'back'): if client.rating_filter == RATINGS['new']: self.do_propose_min_age_enter(client) else: self.do_propose_start_search(client) # if client prints/press something in "User profile view" page elif client.status == STATUSES['decision_wait']: self.on_decision_made(msg, client) # if command is unexpected or not recognized else: self.do_inform_about_unknown_command(client) except (requests.exceptions.ConnectionError, requests.exceptions.ReadTimeout, urllib3.exceptions.ReadTimeoutError): if retries < self.retry_attempts: log( f'Error in connection. Retry in {self.retry_timeout} seconds...', self.debug_mode) sleep(self.retry_timeout) else: log(f'Error in connection. Bot shutting down.', self.debug_mode)