def execute(self, bot: telegram.Bot): user = User.get(self.uid) old_text = cache.get( f'{CACHE_PREFIX}__message_text_{self.message_id}') if old_text: new_text = re.sub(r"^Подписано\s+[█ ]+$", f'Подписано {user.fullname}', old_text, 0, re.IGNORECASE | re.MULTILINE) buttons = cache.get( f'{CACHE_PREFIX}__message_buttons_{self.message_id}') reply_markup = self.get_reply_markup(buttons) female = 'а' if user.female else '' bot.send_message( FSBDayTelegram.chat_id, f'Какой ужас. Это был{female} {user.get_username_or_link()}', reply_to_message_id=self.message_id, parse_mode=telegram.ParseMode.HTML) return bot.edit_message_text( new_text, FSBDayTelegram.chat_id, self.message_id, parse_mode=telegram.ParseMode.HTML, reply_markup=reply_markup) bot.send_message( FSBDayTelegram.chat_id, f'Не могу исправить само сообщение. Но оно подписано {user.get_username_or_link()}', reply_to_message_id=self.message_id, parse_mode=telegram.ParseMode.HTML)
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 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 check_command_is_off(chat_id, cmd_name): """ Проверяет, отключена ли эта команда в чате. """ all_disabled = cache.get(f'all_cmd_disabled:{chat_id}') if all_disabled: return True disabled = cache.get(f'cmd_disabled:{chat_id}:{cmd_name}') if disabled: return True return False
def __super_stukach_alert( cls, uid: int) -> typing.Union[None, FSBDayTelegram.TelegramExecute]: recent_stucks = cache.get(f'{CACHE_PREFIX}__{uid}_recent_stucks') key_super_stukach_alert = f'{CACHE_PREFIX}__super_stukach_alert' if recent_stucks and recent_stucks >= 3 and not cache.get( key_super_stukach_alert): user = User.get(uid) cache.set(key_super_stukach_alert, True, time=30 * 60) return FSBDayTelegram.SendToChat( f'{user.get_username_or_link()} стучите помедленнее. Я не успеваю записывать.' ) return None
def draft_chat_button_click_handler(bot: telegram.Bot, _: telegram.Update, query: telegram.CallbackQuery, data) -> None: """ Обработчик кнопки выбора чата """ user_id = query.from_user.id draft: Optional[CardDraftSelectChat] = cache.get( f'{CACHE_PREFIX}:draft:card:{user_id}') if draft is None or not isinstance(draft, CardDraftSelectChat): query.answer(text='Черновик не найден') query.message.delete() return key_delayed = f'{CACHE_PREFIX}:delayed:{user_id}' if cache.get(key_delayed): query.answer(text='Отправляй раз в минуту. Жди 👆') return cache.set(key_delayed, True, time=60) chat_id: int = data['chat_id'] card = draft.select_chat(chat_id) card_in_chat_msg = bot.send_message(chat_id, card.get_message_text(), reply_markup=get_reply_markup( card.get_message_buttons()), parse_mode=HTML, disable_web_page_preview=True) card.message_id = card_in_chat_msg.message_id with StatsRedis.lock: with StatsRedis() as stats: stats.add_card(card) query.message.delete() status_message: telegram.Message = bot.send_message( user_id, 'Валентинка отправлена!', reply_to_message_id=draft.original_draft_message_id) card.original_draft_message_id = draft.original_draft_message_id card.status_message_id = status_message.message_id cache.set(f'{CACHE_PREFIX}:card:{chat_id}:{card.message_id}', card, time=TWO_DAYS) clear_random_hearts(user_id) query.answer()
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 callback_last_word(bot: telegram.Bot, _: telegram.Update, query, data): uid = query.from_user.id cid = query.message.chat_id msg_ids = [ result[0] for result in (cache.get(get_last_word_cache_key(cid, _uid)) for _uid in data['leaves_uid']) if result is not None and isinstance(result, tuple) ] if len(msg_ids) == 0: try: bot.sendMessage( uid, 'Увы, у меня не сохранились последние слова этих человеков 😢') except Exception: pass return try: bot.sendMessage(uid, 'Последние слова убывших:') except Exception: pass for msg_id in msg_ids: try: bot.forwardMessage(uid, cid, message_id=msg_id) except Exception: pass
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 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 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 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 callback_handler(bot: telegram.Bot, update: telegram.Update) -> None: query = update.callback_query data = cache.get(f'callback:{query.data}') if not data: return if data['name'] == '/off': bot.answerCallbackQuery(query.id) callback_off(bot, update, query, data) return if data['name'] == 'last_word': bot.answerCallbackQuery(query.id, url=f"t.me/{bot.username}?start={query.data}") callback_last_word(bot, update, query, data) return if data['name'] == 'dayof': DayOfManager.callback_handler(bot, update, query, data) return if data['name'] == 'bayanometer_show_orig': Bayanometer.callback_handler(bot, update, query, data) return if data['name'] == 'spoiler': SpoilerHandlers.callback_handler(bot, update, query, data) return if data['name'] == 'matshowtime': MatshowtimeHandlers.callback_handler(bot, update, query, data) return if data['name'] == 'i_stat': istat_callback_handler(bot, update, query, data) return
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 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 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 __get_count(key: str, plural_forms: str) -> typing.Union[None, str]: count = cache.get(key) if not count: count = 0 if isinstance(count, (set, list, dict)): count = len(count) return get_plural(count, plural_forms)
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 on_show_click(cls, bot: telegram.Bot, _: telegram.Update, query: telegram.CallbackQuery, data) -> None: spoiler: Spoiler = cache.get(cls.__get_key(data['spoiler_id'])) if not spoiler: bot.answer_callback_query( query.id, f"Ошибка. Не могу найти спойлер {data['spoiler_id']}", show_alert=True) return uid = query.from_user.id if len(spoiler.body) <= 200: bot.answer_callback_query(query.id, spoiler.body, show_alert=True) logger.info( f'[spoiler] {uid} show popup spoiler {spoiler.spoiler_id}') return bot.answerCallbackQuery(query.id, url=f"t.me/{bot.username}?start={query.data}") if not spoiler.show(bot, uid): cls.__cant_send(bot, query.message.chat_id, uid, spoiler.spoiler_id) return logger.info( f'[spoiler] {uid} show private spoiler {spoiler.spoiler_id}')
def __get_engage_users_count(cls): users = cache.get(cls.key_engage_users) if not users: return None return get_plural( len(users), 'человек принял участие, человека приняло участие, человек приняло участие' )
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 __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_user_domain_count(cls, monday, uid, cid, domain): user_domains = cache.get( cls.__get_user_domain_cache_key(monday, uid, cid)) if user_domains is None: return 0 if domain in user_domains: return user_domains[domain] return 0
def check_user_is_plohish(update): chat_id = update.message.chat_id user_id = update.message.from_user.id cmd_name = get_command_name(update.message.text) disabled = cache.get(f'plohish_cmd:{chat_id}:{user_id}:{cmd_name}') if disabled: return True return False
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 __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 startup_time(bot: telegram.Bot, update: telegram.Update) -> None: uid = update.message.chat_id logger.info(f'id {uid} /startup_time') cached = cache.get('bot_startup_time') if cached: bot.send_message(uid, cached.strftime('%Y-%m-%d %H:%M')) return bot.send_message(uid, 'В кеше ничего нет (но должно быть)')
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)