def is_cmd_delayed(chat_id: int, cmd: str) -> bool: delayed_key = f'delayed:{cmd}:{chat_id}' delayed = cache.get(delayed_key) if delayed: return True cache.set(delayed_key, True, 5 * 60) # 5 минут return False
def can_use(chat_id: int, from_uid: int) -> bool: key = f'8:{chat_id}:{from_uid}' count = cache.get(key, 0) if count < LIMIT: cache.set(key, count + 1, time=FEW_DAYS) return True return False
def call_cats_vision_api(bot: telegram.Bot, update: telegram.Update, key_media_group: str, img_url=None): """ Используется google vision api: * https://cloud.google.com/vision/ * https://cloud.google.com/vision/docs/reference/libraries * https://googlecloudplatform.github.io/google-cloud-python/latest/vision/index.html """ chat_id = update.message.chat_id # если урл картинки не задан, то сами берем самую большую фотку из сообщения # гугл апи, почему-то, перестал принимать ссылки телеги, нам приходится самим загружать ему фото if img_url is None: from google.cloud.vision import types as google_types file = bot.get_file(update.message.photo[-1].file_id) content = bytes(file.download_as_bytearray()) # noinspection PyUnresolvedReferences image = google_types.Image(content=content) # но если передана ссылка, то и гуглу отдаем только ссылку # чтобы не заниматься самим вопросом загрузки каких-то непонятных ссылок # а если гугл не сможет ее открыть -- ну не судьба else: image = {'source': {'image_uri': img_url}} # noinspection PyPackageRequirements from google.cloud import vision # обращаемся к апи детектирования объектов на фото try: logger.debug(f"[google vision] parse img {img_url}") client = config.google_vision_client response = client.annotate_image({ 'image': image, 'features': [{ 'type': vision.enums.Feature.Type.LABEL_DETECTION, 'max_results': 30 }], }) except Exception as ex: logger.error(ex) return # если на фото кот cat = any( re.search(r"\bcats?\b", label.description, re.IGNORECASE) for label in response.label_annotations) if cat: logger.debug(f"[google vision] cat found") if update.message.media_group_id: if cache.get(key_media_group): return cache.set(key_media_group, True, time=TWO_DAYS) msg_id = update.message.message_id bot.sendMessage(chat_id, CONFIG.get("cat_tag", "Это же кошак 🐈"), reply_to_message_id=msg_id) return logger.debug(f"[google vision] cat not found")
def on_mig_click(cls, bot: telegram.Bot, _: telegram.Message, query: telegram.CallbackQuery, data): uid = query.from_user.id card: Card = cache.get(cls.__get_key(data['card_id'])) if not card: bot.answer_callback_query( query.id, f"Ошибка. Не могу найти открытку #{data['card_id']}", show_alert=True) return if uid != card.to_uid: bot.answerCallbackQuery( query.id, 'Только адресат валентинки может подмигнуть 💔') return if uid in card.mig_uids: if User.get(uid).female: text = 'Подруга, ты уже подмигивала 💆' else: text = 'Дружище, ты уже подмигивал 💆♂️' bot.answerCallbackQuery(query.id, text) return card.mig_uids.append(uid) cache.set(cls.__get_key(card.card_id), card, time=USER_CACHE_EXPIRE) bot.answerCallbackQuery(query.id, 'Подмигивание прошло успешно') Stats.total_migs.incr() Stats.migs_users.add(uid) user = User.get(uid) username = user.get_username_or_link() ReactionNotification.send(bot, card.from_uid, f"{username} подмигивает тебе ❤", card) cls.__set_card_preview_as_done(bot, card)
def message_handler(cls, bot: telegram.Bot, update: telegram.Update) -> None: chat_id = update.message.chat_id msg_id = update.message.message_id user_id = update.message.from_user.id img_url = get_photo_url(bot, update.message) photo = cls.__check(img_url, chat_id, msg_id, user_id) if not photo: return if user_id == photo.user_id: logger.debug( f'same user bayan: {chat_id}:{msg_id}={photo.message_id}') return if update.message.media_group_id: key_media_group = f'{KEY_PREFIX}:media_group_reacted:{chat_id}:{update.message.media_group_id}' if cache.get(key_media_group): return cache.set(key_media_group, True, time=TWO_DAYS) data = { "name": BAYANOMETER_SHOW_ORIG, "type": cls.data_type, "orig_photo": photo, "url": img_url } cls.__send(bot, chat_id, msg_id, photo.date, data)
def edit_message(cls, bot: telegram.Bot, message_id: int, text: str, chat_id: int = chat_id, buttons=None) -> None: if chat_id == 0: return reply_markup = cls.get_reply_markup(buttons) try: bot.edit_message_text(text, chat_id, message_id, reply_markup=reply_markup, parse_mode=telegram.ParseMode.HTML, disable_web_page_preview=True) cache.set(f'{CACHE_PREFIX}:messages:{chat_id}:{message_id}:text', text, time=USER_CACHE_EXPIRE) cache.set( f'{CACHE_PREFIX}:messages:{chat_id}:{message_id}:buttons', buttons, time=USER_CACHE_EXPIRE) except Exception as e: logger.error( f"[{MODULE_NAME}] Can't edit message from {chat_id}. Exception: {e}" )
def get(cls, uid) -> typing.Optional['User']: if not uid: return None if isinstance(uid, ChatUser): uid = uid.uid if isinstance(uid, str): uid = get_int(uid) if uid is None: return None cached = cache.get(cls.__get_cache_key(uid)) if cached: return cached logger.debug(f'get_lock {uid}') # лок, чтобы в редис попало то, что в бд with cls.get_lock: try: user = UserDB.get(uid) if user: cache.set(cls.__get_cache_key(uid), user, time=USER_CACHE_EXPIRE) return user except Exception as e: logger.error(e) return None
def add_stickerset_to_used(stickerset): """ Добавляем пак в список использованных. """ used_stickersets = set(cache.get('pipinder:used_stickersets', [])) used_stickersets.add(stickerset.name) cache.set('pipinder:used_stickersets', used_stickersets, time=YEAR)
def send_message(cls, bot: telegram.Bot, text: str, chat_id: int = chat_id, buttons=None, reply_to_message_id=None) -> Optional[int]: if chat_id == 0: return reply_markup = cls.get_reply_markup(buttons) try: message = bot.send_message(chat_id, text, reply_markup=reply_markup, reply_to_message_id=reply_to_message_id, parse_mode=telegram.ParseMode.HTML, disable_web_page_preview=True, timeout=20) cache.set( f'{CACHE_PREFIX}:messages:{chat_id}:{message.message_id}:text', message.text_html, time=USER_CACHE_EXPIRE) cache.set( f'{CACHE_PREFIX}:messages:{chat_id}:{message.message_id}:buttons', buttons, time=USER_CACHE_EXPIRE) return message.message_id except Exception as e: logger.error( f"[{MODULE_NAME}] Can't send message to {chat_id}. Exception: {e}" ) if str(e) == 'Timed out': raise Exception(e) return None
def add_user(cls, user: telegram.User) -> None: if user.is_bot: return username = user.username fullname = ' '.join([user.first_name or '', user.last_name or '']).strip() uid = user.id old_user = cls.get(uid) old_user_female = False if old_user is None else old_user.female new_user = User(uid=uid, username=username, fullname=fullname, female=old_user_female) # пользователя нужно всегда обновлять в редисе (продлевать кэш, так сказать) # но в базе он меняется редко. поэтому сразу обновляем редис cache.set(cls.__get_cache_key(uid), new_user, time=USER_CACHE_EXPIRE) # и только потом проверяем, нужно ли обновить в базе # если нужно, то __add вызовет блокировку потока if old_user is not None: update = {} if old_user.username != username: update['username'] = username if old_user.fullname != fullname: update['fullname'] = fullname if update: cls.__add(new_user, update) return cls.__add(new_user)
def private_text_handler(bot: telegram.Bot, update: telegram.Update) -> None: """ Обработчик текста в личке бота """ message: telegram.Message = update.message user_id = message.from_user.id from_user = get_vuser(user_id) entities = message.parse_entities().items() mentions = get_mentions(entities) text_html = replace_text_mentions(message.text, entities) answer = command_val(text_html, mentions, from_user, get_random_hearts(user_id)) if isinstance(answer, str): bot.send_message(user_id, answer, parse_mode=HTML) return if isinstance(answer, CardDraftSelectHeart): answer.original_draft_message_id = message.message_id cache.set(f'{CACHE_PREFIX}:draft:card:{user_id}', answer, time=TWO_DAYS) bot.send_message(user_id, answer.get_message_text(), reply_markup=get_reply_markup( answer.get_message_buttons()), parse_mode=HTML)
def draft_heart_button_click_handler(bot: telegram.Bot, _: telegram.Update, query: telegram.CallbackQuery, data) -> None: """ Обработчик кнопки выбора сердечка """ user_id = query.from_user.id draft: Optional[CardDraftSelectHeart] = cache.get( f'{CACHE_PREFIX}:draft:card:{user_id}') if draft is None or not isinstance(draft, CardDraftSelectHeart): query.answer(text='Черновик не найден') query.message.delete() return heart: str = data['heart'] chat_names = { chat.chat_id: get_chat_title(bot, chat.chat_id) for chat in draft.from_user.chats } answer = draft.select_heart(heart, chat_names) answer.original_draft_message_id = draft.original_draft_message_id cache.set(f'{CACHE_PREFIX}:draft:card:{user_id}', answer, time=TWO_DAYS) query.edit_message_text(text=answer.get_message_text(), parse_mode=HTML) query.edit_message_reply_markup( reply_markup=get_reply_markup(answer.get_message_buttons())) query.answer()
def generate_next_name(key: str) -> str: """ Возвращает следующее неиспользованное имя """ # особые дни if key == 'orzik' and is_space_day(): return 'Гагарин' # получаем непустые имена без пробелов stripped_names = [ x for x in (x.strip() for x in CONFIG.get(key, 'Никто').split(',')) if x ] # удаляем дубликаты, сохраняя порядок имен uniq_names = list(OrderedDict((x, True) for x in stripped_names).keys()) # мы отдельно храним последние выбранные имена, чтобы они не повторялись recent_names = cache.get(f'{key}_recent', []) # очередь deq используется, чтобы хранить половину от возможного числа имен half_len = round(len(uniq_names) / 2) deq: deque = deque(maxlen=half_len) deq.extend(recent_names) # типа бесконечный цикл, в течение которого выбирается случайное имя, которое еще не постили for _ in range(1, 1000): name = random.choice(uniq_names) if name not in recent_names: deq.append(name) cache.set(f'{key}_recent', list(deq), time=MONTH) return name return 'Никто'
def orzik_correction(bot: telegram.Bot, update: telegram.Update) -> None: """ Реакция на упоминание орзика """ chat_id = update.message.chat_id today = datetime.today().replace(hour=0, minute=0, second=0, microsecond=0).strftime("%Y%m%d") delayed_key = f'orzik_correction:{today}:{chat_id}' # уже постили. нужно ждать delayed = cache.get(delayed_key) if delayed: return name = get_base_name('orzik') # помимо имен бот еще иногда дает орзику указания ("НЕ постишь селфи") # затруднительно автоматически преобразовывать это к "сегодня он не постит селфи", да и не нужно # зато нужно отличать имена от таких указаний и игнорировать их # обычно имена состоят из одного слова # но даже если имя из двух слов, то обычно оба слова начинаются с больших букв - это и проверяем if len(name.split(' ')) > 1 and not name.istitle(): # если это не имя, то сегодняшний день пропускаем cache.set(delayed_key, True, time=(2 * 24 * 60 * 60)) return cache.set(delayed_key, True, time=(4 * 60 * 60)) # и теперь ждем 4 часа bot.send_message(chat_id, f'Сегодня он {name}', reply_to_message_id=update.message.message_id)
def send_weather_now(bot: telegram.Bot, update: telegram.Update) -> None: chat_id = update.message.chat_id if cache.get(f'weather:{chat_id}:now:start_collect'): return cache.set(f'weather:{chat_id}:now:start_collect', True, 90) cached_key = f'weather:{chat_id}:now:result' cached_result = cache.get(cached_key) if cached_result is not None: bot.send_message(chat_id, cached_result, parse_mode=telegram.ParseMode.HTML, disable_web_page_preview=True) return debug = CONFIG.get('weather_debug') weather_cities = CONFIG.get('weather_cities', {}).get(str(chat_id), []) if len(weather_cities) == 0: bot.send_message(chat_id, 'Забыли города добавить в конфиг') return bot.send_chat_action(chat_id, telegram.ChatAction.TYPING) jsons = make_requests(chat_id, weather_cities, debug=debug) cities = parse_jsons(jsons) poweredby = f"\n<a href='https://yandex.ru/pogoda'>По данным Яндекс.Погоды</a>" cities_joined = "\n".join(cities) result = f"Погода сейчас:\n\n{cities_joined}{poweredby}" cache.set(cached_key, result, 30 * 60) # хранится в кэше 30 минут bot.send_message(chat_id, result, parse_mode=telegram.ParseMode.HTML, disable_web_page_preview=True)
def make_requests(chat_id, weather_cities, debug=False): def make_request(city, debug=False): city_name, city_code, timezone, wu_city_code = city if debug: val = FileUtils.load_json(city_code) else: response = request(city_code) val = response['error_msg'] if response['error'] else response[ 'json'] return city_name, timezone, val if debug: return [make_request(city, debug=True) for city in weather_cities] cached_key = f'weather:{chat_id}:requests' cached = cache.get(cached_key) if cached: return cached num_of_workers = 3 pool = ThreadPool(num_of_workers) results = pool.map(make_request, weather_cities) pool.close() pool.join() cache.set(cached_key, results, 30 * 60) # хранится в кэше 30 минут return results
def __check(cls, url, chat_id, message_id, user_id: int) -> Optional['URL']: hash_value = cls.__hash(url) key = f'{KEY_PREFIX}:url:{chat_id}:{hash_value}' cached = cache.get(key) if cached: return cached bayan = URL(message_id, datetime.datetime.now(), user_id) cache.set(key, bayan, time=YEAR)
def get_random_hearts(user_id: int) -> List[str]: key = f'{CACHE_PREFIX}:draft:hearts:{user_id}' cached = cache.get(key) if cached: return cached hearts = random.choices(all_hearts, k=3) cache.set(key, hearts, time=TWO_DAYS) return hearts
def __incr(self, type: str, uid: int) -> bool: key = f'{self.key_prefix}:{type}' uids: List[int] = cache.get(key, []) if uid in uids: return False uids.append(uid) cache.set(key, uids, time=USER_CACHE_EXPIRE) return True
def set_top_mater(stats_from_cid, users_msg_stats) -> None: try: top_mater = [row['uid'] for row in users_msg_stats[0:3]] cache.set(f'weekgoal:{stats_from_cid}:top_mater_uids', top_mater, time=MONTH) except Exception: pass
def execute(self, bot): reply_markup = self.get_reply_markup(self.buttons) bot.edit_message_reply_markup(FSBDayTelegram.chat_id, self.message_id, reply_markup=reply_markup) cache.set(f'{CACHE_PREFIX}__message_buttons_{self.message_id}', self.buttons, time=USER_CACHE_EXPIRE)
def increase_khaleesi_time(cls, chat_id: int) -> None: key = cls.__get_count_cache_key(chat_id) count = cache.get(key) if not count: count = 0 cache.set(key, count + 1, time=(2 * 24 * 60 * 60)) # и теперь ждем 4 часа cache.set(cls.__get_limited_cache_key(chat_id), True, time=(4 * 60 * 60))
def send_random_sticker_from_stickerset(bot: telegram.Bot, chat_id: int, stickerset_name: str) -> None: key = f'stickerset:{stickerset_name}' stickerset = cache.get(key) if not stickerset: stickerset = bot.get_sticker_set(stickerset_name) cache.set(key, stickerset, time=50) sticker = random.choice(stickerset.stickers) bot.send_sticker(chat_id, sticker)
def cheats_found(chat_id: int, user_id: int, sum_count: int) -> bool: key = cheats_key(chat_id, user_id) sums = cache.get(key, 0) sums += sum_count if sums > 50: return True cache.set(key, sums, time=10 * 60) # 10m return False
def off_all_cmds(bot, update): """ Отключает все команды в указанном чате. """ chat_id = update.message.chat_id cache.set(f'all_cmd_disabled:{chat_id}', True, time=CONFIG['off_delay']) bot.sendMessage( chat_id, 'Все команды выключены на 5 минут.\nСтатистика собирается в школу.')
def send_to_all_chats(bot: telegram.Bot, key_name: str, get_text: Callable[[int], str]) -> None: for chat in get_config_chats(): chat_id = chat.chat_id chat_key = f'{CACHE_PREFIX}:{key_name}:{chat_id}' if cache.get(chat_key, False): continue dsp(send_html, bot, chat_id, get_text(chat_id)) cache.set(chat_key, True, time=TWO_DAYS)
def preview_done_click(cls, bot: telegram.Bot, _: telegram.Message, query: telegram.CallbackQuery, __): uid = query.from_user.id key = cls.__get_key(uid) key_delayed = f'{key}:delayed' if cache.get(key_delayed): bot.answer_callback_query(query.id, 'Ждите…') return cache.set(key_delayed, True, time=60) preview_data = cache.get(key) if not preview_data: bot.answer_callback_query( query.id, 'Произошла ошибка. Отправьте текст валентинки повторно', show_alert=True) cache.delete(key_delayed) return # если валентинка уже отправлена, а пользователь жмет на кнопки прошлых предпросмотров if preview_data['done']: bot.answer_callback_query(query.id) cache.delete(key_delayed) return # пробуем отправить валентинку в чат text = cls.__get_text(preview_data['text'], preview_data['heart_index'], with_header=False, title='') card = Card(bot, preview_data['chat_id'], preview_data['from_uid'], preview_data['to_uid'], text, preview_data['text'], preview_data['preview_message_id'], heart_index=preview_data['heart_index']) if not CardCreator.send_card(bot, card): bot.answer_callback_query( query.id, 'Произошла ошибка. Напишите текст валентинки повторно', show_alert=True) cache.delete(key_delayed) return # если отправилась, то нужно все подчистить bot.answer_callback_query(query.id, 'Успешно отправилось!') cls.__change_preview_title(bot, preview_data, SENT_TITLE) if not preview_data['done']: preview_data['done'] = True cache.set(cls.__get_key(preview_data['from_uid']), preview_data, time=USER_CACHE_EXPIRE) cache.delete(key_delayed)
def update_stickers(_: telegram.Bot, update: telegram.Update) -> None: """ Добавление стикера в использованные """ if not update.message.sticker: return cache_key = f'pipinder:monday_stickersets:{get_current_monday_str()}' monday_stickersets = set(cache.get(cache_key, set())) monday_stickersets.add(update.message.sticker.set_name) cache.set(cache_key, monday_stickersets, time=USER_CACHE_EXPIRE)
def __is_plagiat(text: str) -> bool: text = text.strip() key = f'{CACHE_PREFIX}__texts' texts = cache.get(key) texts = set() if not texts else set(texts) if text in texts: return True texts.add(text) cache.set(key, texts, time=USER_CACHE_EXPIRE) return False
def __edit_preview(cls, bot: telegram.Bot, preview_data) -> None: msg = cls.__get_text(preview_data['text'], preview_data['heart_index']) TelegramWrapper.edit_message(bot, preview_data['preview_message_id'], msg, chat_id=preview_data['from_uid'], buttons=cls.__get_buttons()) cache.set(cls.__get_key(preview_data['from_uid']), preview_data, time=USER_CACHE_EXPIRE)