def get_top_pair(uid: int) -> Optional[User]: replylove__dragon_lovers = CONFIG.get('replylove__dragon_lovers', []) if uid in replylove__dragon_lovers: return User(0, 0, 'drakon', '🐉') replylove__ignore = CONFIG.get('replylove__ignore', []) replylove__ignore_pairs = CONFIG.get('replylove__ignore_pairs', {}).get(str(chat_id), {}).get(str(uid), []) pairs: List[Tuple[str, int]] = sort_dict(db['pair']) for pair, count in pairs: a_uid, b_uid = [get_int(x) for x in pair.split(',')] strast = None if a_uid is None or b_uid is None: continue if count < 5: continue if uid == a_uid and a_uid == b_uid: continue if any(x in replylove__dragon_lovers for x in (a_uid, b_uid)): continue if any(x in replylove__ignore for x in (uid, a_uid, b_uid)): continue if any(x in replylove__ignore_pairs for x in (a_uid, b_uid)): continue if uid == a_uid: strast = User.get(b_uid) if uid == b_uid: strast = User.get(a_uid) if strast: return strast return None
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 get_top(type: str, uid: int) -> Optional[User]: if type not in db: return None if uid not in db[type]: return None replylove__ignore = CONFIG.get('replylove__ignore', []) if uid in replylove__ignore: return None replylove__dragon_lovers = CONFIG.get('replylove__dragon_lovers', []) if uid in replylove__dragon_lovers: return User(0, 0, 'drakon', '🐉') sorted: List[Tuple[int, int]] = sort_dict(db[type][uid]) if len(sorted) == 0: return None replylove__ignore_pairs = CONFIG.get('replylove__ignore_pairs', {}).get(str(chat_id), {}).get(str(uid), []) for result_uid, count in sorted: if count < 5: continue if uid == result_uid: continue if result_uid in replylove__dragon_lovers: continue if result_uid in replylove__ignore: continue if result_uid in replylove__ignore_pairs: continue return User.get(result_uid) return None
def check_for_choosing_key(self, button: Button, label: str): if button.state == ButtonState.RELEASED: in_key_choosing = True while in_key_choosing: for event in pygame.event.get(): if event.type != pygame.KEYDOWN: continue if event.key == pygame.locals.K_ESCAPE: in_key_choosing = False break button.set_label(pygame.key.name(KEYBOARD[event.key]), CONFIG.readable_font) in_key_choosing = False # Change key in configuration, too if label == 'Up': CONFIG.KEY_UP = KEYBOARD[event.key] elif label == 'Down': CONFIG.KEY_DOWN = KEYBOARD[event.key] elif label == 'Right': CONFIG.KEY_RIGHT = KEYBOARD[event.key] elif label == 'Left': CONFIG.KEY_LEFT = KEYBOARD[event.key] CONFIG.save() break
def set_default_logging_format(): default_format = '[%(asctime)s][%(levelname)s][%(name)s] - %(message)s' logging.basicConfig(format=CONFIG.get('logging', {}).get('format', default_format), level=logging.getLevelName( CONFIG.get('logging', {}).get('level', 'INFO').upper()), filename=CONFIG.get('logging', {}).get('file', None))
def forward_to_channel(bot: telegram.Bot, chat_id: int, message_id: int, user_id: int) -> None: """ Форвардит сообщение в канал музкружка """ channel_id = CONFIG.get('muzkruzhok_channel_id', None) if channel_id is None: return if chat_id in CONFIG.get('muzkruzhok_ban_chats', []): return bans = CONFIG.get('muzkruzhok_ban_ids', []) if user_id in bans: return bot.forward_message(channel_id, chat_id, message_id)
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 request_wu(city_code: str): """ Получает у апи погоду по указанному городу через WU. :param str city_code: код города в формате 'Country/City' """ api_key = CONFIG.get('weather_wunderground_api_key') if not api_key: return features = 'conditions/astronomy/forecast/hourly/almanac' url_template = 'http://api.wunderground.com/api/{}/{}/lang:RU/q/{}.json' url = url_template.format(api_key, features, city_code.replace(' ', '%20')) response = requests.get(url) FileUtils.dump_tmp_city( 'wu', city_code, response.text) # сохраняем ответ во временную папку # если в ответе ошибка if response.status_code != requests.codes.ok: return { 'error': True, 'error_msg': "Ошибка какая-то:\n\n{}".format(str(response.status_code)) } # если все ок return {'error': False, 'json': response.json()}
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 request(city_code: str): """ Получает у апи погоду по указанному городу :param str city_code: gps координаты города (55.7507,37.6177) """ api_key = CONFIG.get('weather_yandex_api_key') if not api_key: return # url = f'https://api.darksky.net/forecast/{api_key}/{city_code}?lang=ru&units=si&exclude=minutely,alerts,flags' lat, lon = city_code.split(',') url = f'https://api.weather.yandex.ru/v1/informers?lang=ru_RU&lat={lat}&lon={lon}' headers = {'X-Yandex-API-Key': api_key} response = requests.get(url, headers=headers) FileUtils.dump_tmp_city( 'ya', city_code, response.text) # сохраняем ответ во временную папку # если в ответе ошибка if response.status_code != requests.codes.ok: return { 'error': True, 'error_msg': f"Ошибка какая-то:\n\n{str(response.status_code)}" } # если все ок return {'error': False, 'json': response.json()}
def run_weekly_stats(bot: telegram.Bot, update: telegram.Update) -> None: uid = update.message.chat_id logger.info(f'id {uid} /weekly_stats') if uid != CONFIG.get('debug_uid', None): return from src.modules.weeklystat import weekly_stats weekly_stats(bot, None)
def is_today_ending() -> bool: """ Сегодня 15-е фев? """ # TODO: убрать if CONFIG.get('feb14_debug_end', False): return True return datetime.today().strftime("%m-%d") == '02-15'
def is_day_active() -> bool: """ Сегодня 14-е фев? """ # TODO: убрать if CONFIG.get('feb14_debug_begin', False): return True return datetime.today().strftime( "%m-%d") == '02-14' # месяц-день. Первое января будет: 01-01
def send_to_all_chats_handler(bot: telegram.Bot, update: telegram.Update) -> None: uid = update.message.chat_id logger.info(f'id {uid} /send_to_all_chats') if uid != CONFIG.get('debug_uid', None): return text = f""" """.strip()
def changelog(bot: telegram.Bot, update): text = CONFIG.get('changelog', '') if len(text) == 0: return chat_id = update.message.chat_id bot.send_message(chat_id, text, parse_mode=telegram.ParseMode.HTML, disable_web_page_preview=True)
def can_use_repinder(_: telegram.Bot, __: int, user_id: int) -> bool: """ Этот пользователь может использовать репиндер? """ if user_id in CONFIG.get('repinder_users', []): return True # if check_admin(bot, chat_id, user_id): # return True return False
def get_all_love(cls, chat_id: int, date=None, header='Вся страсть') -> str: def get_no_love_str(no_love_: List) -> str: length = len(no_love_) if length == 0: return '' if length <= 10: return '\n\nБеcстрастные:\n' + '\n'.join( (cls.__format_pair(a) for a in no_love_)) return f'\n\nИ еще {pytils.numeral.get_plural(length, "беcстрастный, беcстрастных, беcстрастных")}' def get_narcissist(narcissist_: List[User]) -> str: if len(narcissist_) == 0: return '' return f'\n\nНарциссы:\n' + '\n'.join( (cls.__format_pair(a) for a in narcissist_)) all_chat_users = ChatUser.get_all(chat_id) all_users = (User.get(chatuser.uid) for chatuser in all_chat_users) all_users = sorted(all_users, key=lambda x: x.fullname) all_love = [(user, ReplyTop.get_user_top_strast(chat_id, user.uid, date)[0]) for user in all_users if user] in_love = [(a, b, ReplyTop.get_user_top_strast(chat_id, b.uid, date)[0]) for a, b in all_love if b] narcissist = [ a for a, _ in all_love if a.uid in CONFIG.get('replylove__narcissist', []) ] no_love = [ a for a, b in all_love if not b and a.uid not in CONFIG.get('replylove__narcissist', []) ] in_love_str = '\n'.join( cls.__format_pair(a, b, b_pair) for a, b, b_pair in in_love) no_love_str = get_no_love_str(no_love) narcissist_str = get_narcissist(narcissist) return f'{header}:\n\n{in_love_str}{narcissist_str}{no_love_str}'
def __format_pair(cls, a: User, b: Optional[User] = None, b_pair: Optional[User] = None) -> str: if not b: return f'<b>{cls.get_fullname_or_username(a)}</b>' if a.uid in CONFIG.get('replylove__dragon_lovers', []): return f'<b>{cls.get_fullname_or_username(a)}</b> ⟷ 🐉' love = ' ❤' if b_pair and b_pair.uid == a.uid else '' return f'<b>{cls.get_fullname_or_username(a)}</b> ⟷ {cls.get_fullname_or_username(b)}{love}'
def prepare(): """ Подготовительный этап """ set_default_logging_format() if 'google_vision_client_json_file' in CONFIG: config.google_vision_client = auth_google_vision( CONFIG['google_vision_client_json_file']) cache.set('pipinder:fav_stickersets_names', set(CONFIG.get("sasha_rebinder_stickersets_names", [])), time=YEAR)
def year(bot: telegram.Bot, update: telegram.Update) -> None: uid = update.message.chat_id logger.info(f'id {uid} /year') if uid != CONFIG.get('debug_uid', None): return from src.models.user_stat import UserStat from src.modules.weeklystat import send_long bot.send_chat_action(uid, telegram.chataction.ChatAction.TYPING) cid = CONFIG.get('anon_chat_id') # cid = -48952907 year = 2017 info = UserStat.get_chat_year(cid, year) msg = f'<b>Rapture {year}</b>\n' \ f'Нас: {info["users_count"]}\n' \ f'Сообщений: {info["msg_count"]}\n' msg += '\n' msg += info['top_chart'].replace('<b>', '').replace('</b>', '') send_long(bot, CONFIG.get('anon_chat_id'), msg)
def send_mylove(bot: telegram.Bot, update: telegram.Update, send_to_cid: int, find_in_cid: int) -> None: def format_love(type: str, b: User, _: bool) -> typing.Optional[str]: if not b: return None b_pair, b_inbound, b_outbound = ReplyTop.get_user_top_strast(find_in_cid, b.uid) mutual_sign = ' ❤' if type == 'pair' and b_pair: mutual = mutual_sign if b_pair.uid == user_id else '' return f'Парная: {ReplyLove.get_fullname_or_username(b)}{mutual}' if type == 'inbound' and b_inbound: mutual = mutual_sign if b_inbound and b_inbound.uid == user_id else '' return f'Входящая: {ReplyLove.get_fullname_or_username(b)}{mutual}' if type == 'outbound' and b_outbound: mutual = mutual_sign if b_outbound and b_outbound.uid == user_id else '' return f'Исходящая: {ReplyLove.get_fullname_or_username(b)}{mutual}' return None bot.sendChatAction(send_to_cid, ChatAction.TYPING) reply_to_msg = update.message.reply_to_message if reply_to_msg: user_id = reply_to_msg.from_user.id else: splitted = update.message.text.split() if len(splitted) == 2: user_id = User.get_id_by_name(splitted[1]) else: user_id = update.message.from_user.id user = User.get(user_id) if not user: bot.send_message(send_to_cid, 'А кто это? Таких не знаю.', reply_to_message_id=update.message.message_id) return pair, inbound, outbound = ReplyTop.get_user_top_strast(find_in_cid, user_id) formats = (format_love('pair', pair, user.female), format_love('inbound', inbound, user.female), format_love('outbound', outbound, user.female)) love_list = [s for s in formats if s] if len(love_list) == 0: result = '🤷♀️🤷♂️ А нет никакой страсти' else: result = '\n'.join(love_list) if user_id in CONFIG.get('replylove__dragon_lovers', []): result = '🐉' bot.send_message(send_to_cid, f'Страсть {user.get_username_or_link()}:\n\n{result}', reply_to_message_id=update.message.message_id, parse_mode=ParseMode.HTML)
def send_gdeleha(bot, chat_id, msg_id, user_id): if user_id in CONFIG.get('leha_ids', []) or user_id in CONFIG.get( 'leha_anya', []): bot.sendMessage(chat_id, "Леха — это ты!", reply_to_message_id=msg_id) return send_random_sticker(bot, chat_id, [ 'BQADAgADXgIAAolibATmbw713OR4OAI', 'BQADAgADYwIAAolibATGN2HOX9g1wgI', 'BQADAgADZQIAAolibAS0oUsHQK3DeQI', 'BQADAgADdAIAAolibATvy9YzL3EJ_AI', 'BQADAgADcwIAAolibATLRcR2Y1U5LQI', 'BQADAgADdgIAAolibAQD0bDVAip6bwI', 'BQADAgADeAIAAolibAT4u54Y18S13gI', 'BQADAgADfQIAAolibAQzRBdOwpQL_gI', 'BQADAgADfgIAAolibASJFncLc9lxdgI', 'BQADAgADfwIAAolibATLieQe0J2MxwI', 'BQADAgADgAIAAolibATcQ-VMJoDQ-QI', 'BQADAgADggIAAolibAR_Wqvo57gCPwI', 'BQADAgADhAIAAolibATcTIr_YdowgwI', 'BQADAgADiAIAAolibARZHNSejUISQAI', 'BQADAgADigIAAolibAS_n7DVTejNhAI', 'BQADAgADnQIAAolibAQE8V7GaofXLgI', ])
def anon(bot: telegram.Bot, update: telegram.Update) -> None: if not CONFIG.get('anon', False): return text = update.message.text if not text: return uid = update.message.from_user.id cid = CONFIG['anon_chat_id'] text = re.sub(r"\s*/\w+", '', text) text = text.strip() if len(text) == 0: return logger.info(f'[anon] from {uid}. text: {text}') bot.send_message(cid, text, disable_web_page_preview=True)
def cmd_mats(cls, bot: telegram.Bot, update: telegram.Update) -> None: uid = update.message.from_user.id # только админ бота может использовать команду if uid != CONFIG.get('debug_uid', None): return # получаем параметры команды (текст после "/mats ") text = update.message.text.partition(' ')[2].strip() if not text: return # получаем мат mat_words = list(word.lower() for word in Antimat.bad_words(text)) if len(mat_words) == 0: return # отправляем мат matshowtime.send(bot, mat_words)
def get_user_chats(cls, uid: int, cids: typing.Optional[typing.List[int]] = None) -> \ typing.List[int]: config_cids = cids if cids else [int(c) for c in CONFIG.get('chats', [])] try: with session_scope() as db: # noinspection PyUnresolvedReferences user_in_chats = db.query(ChatUserDB) \ .filter(ChatUserDB.cid.in_(config_cids)) \ .filter(ChatUserDB.uid == uid) \ .filter(ChatUserDB.left == 0) \ .all() return [chatuser.cid for chatuser in user_in_chats] except Exception as e: logger.error(e) raise Exception(f"Can't get user chats {uid} from DB")
def __ignore_pairs(chat_id, pairs): copy = pairs.copy() replylove__ignore_pairs = CONFIG.get('replylove__ignore_pairs', {}).get(str(chat_id), {}) for uid_str, ignore_uids in replylove__ignore_pairs.items(): str_uids = [str(uid) for uid in ignore_uids] for pair in pairs.keys(): pair_uids = pair.split(',') if uid_str not in pair_uids: continue pair_uids.remove(uid_str) b = pair_uids[0] if b in str_uids: copy.pop(pair, None) return copy
def get_stats(cls, cid, date=None): monday = get_current_monday() if date is None else get_date_monday( date) db = cls.db_helper.get_db(monday, cid) ignore = CONFIG.get('replylove__ignore', []) return { 'to': sort_dict(cls.__remove_uids(db['to'], ignore))[:3], 'from': sort_dict(cls.__remove_uids(db['from'], ignore))[:3], 'pair': sort_dict( cls.__ignore_pairs(cid, cls.__remove_uids(db['pair'], ignore)))[:10] }
def send_end(bot: telegram.Bot) -> None: """ Отправка во все чаты подводящих итог сообщений """ def _get_text(chat_id: int) -> str: stats_text = StatsHumanReporter(stats).get_text(chat_id) return f'Отгремело шампанское, отзвенели бубенцы. Замолили ли мы нашего двукратного дракона великого зеленокожесластного? Такс:\n\n{stats_text}' with StatsRedis() as stats: # отправка админу статы по всем чатам admin_key = f'{CACHE_PREFIX}:end:admin' if not cache.get(admin_key, False): text = StatsHumanReporter(stats).get_text(None) dsp(send_html, bot, CONFIG.get('debug_uid', None), text) cache.set(admin_key, True, time=TWO_DAYS) send_to_all_chats(bot, 'end', _get_text)
def repair_bot(logger=None): """ Запускаем скрипт перезапуска бота """ import time as time_time import sys time_time.sleep(5) if logger: logger.critical('Need bot repair') logger.critical('--') else: print('[CRITICAL] Need bot repair', file=sys.stderr) time_time.sleep(1) if 'pm2_bot_repair' in CONFIG: import subprocess # запускам скрипт перезапуска бота subprocess.Popen([CONFIG.get('pm2_bot_repair')], shell=True) exit()
def callback_handler(cls, bot: telegram.Bot, _, query: telegram.CallbackQuery, data) -> None: uid = query.from_user.id cid = query.message.chat_id button_msg_id = query.message.message_id if uid == CONFIG.get('debug_uid', None): try: reply_msg_id = None if query.message.reply_to_message: reply_msg_id = query.message.reply_to_message.message_id bot.forward_message(uid, cid, message_id=reply_msg_id) except Exception: pass if 'type' not in data or data['type'] == Photo.data_type: Photo.callback_handler(bot, uid, cid, button_msg_id, data, query) return elif data['type'] == URL.data_type: URL.callback_handler(bot, uid, cid, button_msg_id, data, query) return bot.answer_callback_query(query.id)