Exemple #1
0
def describe_poi(poi: POI):
    deleted = '' if not poi.delete_reason else ' 🗑️'
    result = [f'<b>{h(poi.name)}</b>{deleted}']
    if poi.description:
        result.append(h(poi.description))

    part2 = []
    if poi.hours:
        if poi.hours.is_24_7:
            part2.append('🌞 ' + tr('open_247'))
        elif poi.hours.is_open():
            closes = poi.hours.next_change()
            open_now = '☀️ ' + tr('now_open', closes.strftime("%H:%M"))
            if (closes - datetime.now()).seconds <= 3600 * 2:
                opens = poi.hours.next_change(closes)
                open_now += ' ' + tr('next_open',
                                     day=relative_day(opens).capitalize(),
                                     hour=opens.strftime("%H:%M").lstrip("0"))
            part2.append(open_now)
        else:
            opens = poi.hours.next_change()
            part2.append('🌒 ' + tr('now_closed',
                                   day=relative_day(opens),
                                   hour=opens.strftime("%H:%M").lstrip("0")))
    if poi.links and len(poi.links) > 1:
        part2.append('🌐 ' + tr('poi_links') + ': {}.'.format(', '.join([
            '<a href="{}">{}</a>'.format(h(link[1]), h(link[0]))
            for link in poi.links
        ])))
    if poi.house_name or poi.address_part:
        address = ', '.join([
            s for s in (poi.house_name, uncap(poi.floor),
                        uncap(poi.address_part)) if s
        ])
        part2.append(f'🏠 {address}.')
    if poi.has_wifi is True:
        part2.append('📶 ' + tr('has_wifi'))
    if poi.accepts_cards is True:
        part2.append('💳 ' + tr('accepts_cards'))
    elif poi.accepts_cards is False:
        part2.append('💰 ' + tr('no_cards'))
    if poi.phones:
        part2.append('📞 {}.'.format(', '.join(
            [re.sub(r'[^0-9+]', '', phone) for phone in poi.phones])))
    if part2:
        result.append('')
        result.extend(part2)

    if poi.comment:
        result.append('')
        result.append(poi.comment)
    return '\n'.join(result)
Exemple #2
0
def describe_poi(poi: POI):
    deleted = '' if not poi.delete_reason else ' 🗑️'
    result = [f'<b>{h(poi.name)}</b>{deleted}']
    if poi.description:
        result.append(h(poi.description))

    part2 = []
    if poi.hours:
        if poi.hours.is_24_7:
            part2.append('🌞 Открыто круглосуточно.')
        elif poi.hours.is_open():
            closes = poi.hours.next_change()
            open_now = f'☀️ Открыто сегодня до {closes.strftime("%H:%M")}.'
            if (closes - datetime.now()).seconds <= 3600 * 2:
                opens = poi.hours.next_change(closes)
                open_now += (f' {relative_day(opens).capitalize()} работает '
                             f'с {opens.strftime("%H:%M").lstrip("0")}.')
            part2.append(open_now)
        else:
            opens = poi.hours.next_change()
            part2.append(f'🌒 Закрыто. Откроется {relative_day(opens)} '
                         f'в {opens.strftime("%H:%M").lstrip("0")}.')
    if poi.links and len(poi.links) > 1:
        part2.append('🌐 Ссылки: {}.'.format(', '.join([
            '<a href="{}">{}</a>'.format(h(link[1]), h(link[0]))
            for link in poi.links
        ])))
    if poi.house_name or poi.address_part:
        address = ', '.join([
            s for s in (poi.house_name, uncap(poi.floor),
                        uncap(poi.address_part)) if s
        ])
        part2.append(f'🏠 {address}.')
    if poi.has_wifi is True:
        part2.append('📶 Есть бесплатный Wi-Fi.')
    if poi.accepts_cards is True:
        part2.append('💳 Можно оплатить картой.')
    elif poi.accepts_cards is False:
        part2.append('💰 Оплата только наличными.')
    if poi.phones:
        part2.append('📞 {}.'.format(', '.join(
            [re.sub(r'[^0-9+]', '', phone) for phone in poi.phones])))
    if part2:
        result.append('')
        result.extend(part2)

    if poi.comment:
        result.append('')
        result.append(poi.comment)
    return '\n'.join(result)
Exemple #3
0
def format(v, yes=None, no=None, null=None):
    if v is None or v == '':
        return f'<i>{null or tr(("editor", "unknown"))}</i>'
    if isinstance(v, str):
        return h(v)
    if isinstance(v, bool):
        return (yes or tr(('editor', 'bool_yes'))) if v else (no or tr(
            ('editor', 'bool_no')))
    if isinstance(v, (int, float)):
        return v
    if isinstance(v, hoh.OHParser):
        return h(v.field)
    if isinstance(v, Location):
        return f'v.lat, v.lon'
    return str(v)
Exemple #4
0
async def help(message: types.Message, state: FSMContext):
    await state.finish()
    msg = config.RESP['help']
    stats = await db.get_stats()
    for k, v in stats.items():
        msg = msg.replace('{' + k + '}', h(str(v)))
    await message.answer(msg, parse_mode=HTML, disable_web_page_preview=True,
                         reply_markup=get_buttons())
Exemple #5
0
async def print_audit(user: types.User):
    content = 'Последние операции:'
    last_audit = await db.get_last_audit(15)
    for a in last_audit:
        user_name = a.user_name if a.user_id == a.approved_by else str(
            a.user_id)
        if user_name is None:
            user_name = 'Администратор'
        line = f'{a.ts.strftime("%Y-%m-%d %H:%S")} {user_name}'
        if a.approved_by != a.user_id:
            line += f' (подтвердил {a.user_name})'
        if a.field == 'poi':
            line += ' создал' if not a.old_value else ' удалил'
            line += f' «{a.poi_name}» /poi{a.poi_id}'
        else:
            line += (f' изменил у «{a.poi_name}» /poi{a.poi_id} '
                     f'поле {a.field}: "{a.old_value}" → "{a.new_value}"')
        content += '\n\n' + h(line) + '.'
    await bot.send_message(user.id, content, disable_web_page_preview=True)
Exemple #6
0
async def print_audit(user: types.User):
    content = tr(('admin', 'audit')) + ':'
    last_audit = await db.get_last_audit(15)
    for a in last_audit:
        user_name = a.user_name if a.user_id == a.approved_by else str(
            a.user_id)
        if user_name is None:
            user_name = tr(('admin', 'admin'))
        line = f'{a.ts.strftime("%Y-%m-%d %H:%S")} {user_name}'
        if a.approved_by != a.user_id:
            line += ' (' + tr(('admin', 'confirmed_by'), a.user_name) + ')'
        if a.field == 'poi':
            line += ' ' + tr(
                ('admin', 'created' if not a.old_value else 'deleted'))
            line += f' «{a.poi_name}» /poi{a.poi_id}'
        else:
            line += (' ' + tr(('admin', 'modified')) +
                     f' «{a.poi_name}» /poi{a.poi_id} ' + tr(
                         ('admin', 'field')) +
                     f' {a.field}: "{a.old_value}" → "{a.new_value}"')
        content += '\n\n' + h(line) + '.'
    await bot.send_message(user.id, content, disable_web_page_preview=True)
Exemple #7
0
async def print_next_queued(user: types.User):
    info = await get_user(user)
    if not info.is_moderator():
        return False

    queue = await db.get_queue(1)
    if not queue:
        # This is done inside print_next_added()
        # await bot.send_message(user.id, tr(('queue', 'empty')))
        await print_next_added(user)
        return True

    q = queue[0]
    poi = await db.get_poi_by_id(q.poi_id)
    if not poi:
        await bot.send_message(user.id, tr(('queue', 'poi_lost_del')))
        await db.delete_queue(q)
        return True

    photo = None
    if q.field == 'message':
        content = tr(('queue', 'message'),
                     user=h(q.user_name),
                     name=h(poi.name))
        content += f'\n\n{h(q.new_value)}'
    else:
        content = tr(('queue', 'field'),
                     user=h(q.user_name),
                     name=h(poi.name),
                     field=q.field)
        content += '\n'
        nothing = '<i>' + tr(('queue', 'nothing')) + '</i>'
        vold = nothing if q.old_value is None else h(q.old_value)
        vnew = nothing if q.new_value is None else h(q.new_value)
        content += f'\n<b>{tr(("queue", "old"))}:</b> {vold}'
        content += f'\n<b>{tr(("queue", "new"))}:</b> {vnew}'
        if q.field in ('photo_in', 'photo_out') and q.new_value:
            photo = os.path.join(config.PHOTOS, q.new_value + '.jpg')
            if not os.path.exists(photo):
                photo = None

    kbd = types.InlineKeyboardMarkup(row_width=3)
    if q.field != 'message':
        kbd.insert(
            types.InlineKeyboardButton('🔍 ' + tr(('queue', 'look')),
                                       callback_data=MSG_CB.new(action='look',
                                                                id=str(q.id))))
        kbd.insert(
            types.InlineKeyboardButton('✅ ' + tr(('queue', 'apply')),
                                       callback_data=MSG_CB.new(action='apply',
                                                                id=str(q.id))))
    else:
        kbd.insert(
            types.InlineKeyboardButton('📝 ' + tr('edit_poi'),
                                       callback_data=POI_EDIT_CB.new(
                                           id=q.poi_id, d='0')))
    kbd.insert(
        types.InlineKeyboardButton('❌ ' + tr(('queue', 'delete')),
                                   callback_data=MSG_CB.new(action='del',
                                                            id=str(q.id))))

    if not photo:
        await bot.send_message(user.id,
                               content,
                               parse_mode=HTML,
                               reply_markup=kbd)
    else:
        await bot.send_photo(user.id,
                             types.InputFile(photo),
                             caption=content,
                             parse_mode=HTML,
                             reply_markup=kbd)
    return True
Exemple #8
0
async def print_poi_list(user: types.User,
                         query: str,
                         pois: List[POI],
                         full: bool = False,
                         shuffle: bool = True,
                         relative_to: Location = None,
                         comment: str = None):
    max_buttons = 9 if not full else 20
    location = (await get_user(user)).location or relative_to
    if shuffle:
        if location:
            pois.sort(key=lambda p: location.distance(p.location))
        else:
            random.shuffle(pois)
            stars = await db.stars_for_poi_list(user.id, [p.id for p in pois])
            if stars:
                pois.sort(key=lambda p: star_sort(stars.get(p.id)),
                          reverse=True)
        pois.sort(key=lambda p: bool(p.hours) and not p.hours.is_open())
    total_count = len(pois)
    all_ids = pack_ids([p.id for p in pois])
    if total_count > max_buttons:
        pois = pois[:max_buttons if full else max_buttons - 1]

    # Build the message
    content = tr('poi_list', query) + '\n'
    for i, poi in enumerate(pois, 1):
        if poi.description:
            content += h(f'\n{i}. {poi.name} — {uncap(poi.description)}')
        else:
            content += h(f'\n{i}. {poi.name}')
        if poi.hours and not poi.hours.is_open():
            content += ' 🌒'
    if total_count > max_buttons:
        if not full:
            content += '\n\n' + tr('poi_not_full', total_count=total_count)
        else:
            content += '\n\n' + tr('poi_too_many', total_count=total_count)
    if comment:
        content += '\n\n' + comment

    # Prepare the inline keyboard
    if len(pois) == 4:
        kbd_width = 2
    else:
        kbd_width = 4 if len(pois) > 9 else 3
    kbd = types.InlineKeyboardMarkup(row_width=kbd_width)
    for i, poi in enumerate(pois, 1):
        b_title = f'{i} {poi.name}'
        kbd.insert(
            types.InlineKeyboardButton(
                b_title, callback_data=POI_LIST_CB.new(id=poi.id)))
    if total_count > max_buttons and not full:
        try:
            callback_data = POI_FULL_CB.new(query=query[:55], ids=all_ids)
        except ValueError:
            # Too long
            callback_data = POI_FULL_CB.new(query=query[:55], ids='-')
        kbd.insert(
            types.InlineKeyboardButton(f'🔽 {config.MSG["all"]} {total_count}',
                                       callback_data=callback_data))

    # Make a map and send the message
    map_file = get_map([poi.location for poi in pois], ref=location)
    if not map_file:
        await bot.send_message(user.id,
                               content,
                               parse_mode=HTML,
                               reply_markup=kbd)
    else:
        await bot.send_photo(user.id,
                             types.InputFile(map_file.name),
                             caption=content,
                             parse_mode=HTML,
                             reply_markup=kbd)
        map_file.close()
Exemple #9
0
async def print_poi(user: types.User,
                    poi: POI,
                    comment: str = None,
                    buttons: bool = True):
    log_poi(poi)
    chat_id = user.id
    content = describe_poi(poi)
    if comment:
        content += '\n\n' + h(comment)

    # Prepare photos
    photos = []
    photo_names = []
    for photo in [poi.photo_in, poi.photo_out]:
        if photo:
            path = os.path.join(config.PHOTOS, photo + '.jpg')
            if os.path.exists(path):
                file_ids = await db.find_file_ids(
                    {photo: os.path.getsize(path)})
                if photo in file_ids:
                    photos.append(file_ids[photo])
                    photo_names.append(None)
                else:
                    photos.append(types.InputFile(path))
                    photo_names.append([photo, os.path.getsize(path)])

    # Generate a map
    location = (await get_user(user)).location
    map_file = get_map([poi.location], location)
    if map_file:
        photos.append(types.InputFile(map_file.name))
        photo_names.append(None)

    # Prepare the inline keyboard
    if poi.tag == 'building':
        kbd = await make_house_keyboard(user, poi)
    else:
        kbd = None if not buttons else await make_poi_keyboard(user, poi)

    # Send the message
    if not photos:
        msg = await bot.send_message(chat_id,
                                     content,
                                     parse_mode=HTML,
                                     reply_markup=kbd,
                                     disable_web_page_preview=True)
    elif len(photos) == 1:
        msg = await bot.send_photo(chat_id,
                                   photos[0],
                                   caption=content,
                                   parse_mode=HTML,
                                   reply_markup=kbd)
    else:
        media = types.MediaGroup()
        for i, photo in enumerate(photos):
            if not kbd and i == 0:
                photo = types.input_media.InputMediaPhoto(photo,
                                                          caption=content,
                                                          parse_mode=HTML)
            media.attach_photo(photo)
        if kbd:
            msg = await bot.send_media_group(chat_id, media=media)
            await bot.send_message(chat_id,
                                   content,
                                   parse_mode=HTML,
                                   reply_markup=kbd,
                                   disable_web_page_preview=True)
        else:
            msg = await bot.send_media_group(chat_id, media=media)
    if map_file:
        map_file.close()

    # Store file_ids for new photos
    if isinstance(msg, list):
        file_ids = [m.photo[-1].file_id for m in msg if m.photo]
    else:
        file_ids = [msg.photo[-1]] if msg.photo else []
    for i, file_id in enumerate(file_ids):
        if photo_names[i]:
            await db.store_file_id(photo_names[i][0], photo_names[i][1],
                                   file_id)
Exemple #10
0
async def print_next_queued(user: types.User):
    info = await get_user(user)
    if not info.is_moderator():
        return False

    queue = await db.get_queue(1)
    if not queue:
        # This is done inside print_next_added()
        # await bot.send_message(user.id, config.MSG['queue']['empty'])
        await print_next_added(user)
        return True

    q = queue[0]
    poi = await db.get_poi_by_id(q.poi_id)
    if not poi:
        await bot.send_message(user.id, 'POI пропал, странно. Удаляю запись.')
        await db.delete_queue(q)
        return True

    photo = None
    if q.field == 'message':
        content = config.MSG['queue']['message'].format(user=h(q.user_name),
                                                        name=h(poi.name))
        content += f'\n\n{h(q.new_value)}'
    else:
        content = config.MSG['queue']['field'].format(user=h(q.user_name),
                                                      name=h(poi.name),
                                                      field=q.field)
        content += '\n'
        vold = '<i>ничего</i>' if q.old_value is None else h(q.old_value)
        vnew = '<i>ничего</i>' if q.new_value is None else h(q.new_value)
        content += f'\n<b>Сейчас:</b> {vold}'
        content += f'\n<b>Будет:</b> {vnew}'
        if q.field in ('photo_in', 'photo_out') and q.new_value:
            photo = os.path.join(config.PHOTOS, q.new_value + '.jpg')
            if not os.path.exists(photo):
                photo = None

    kbd = types.InlineKeyboardMarkup(row_width=3)
    if q.field != 'message':
        kbd.insert(
            types.InlineKeyboardButton(config.MSG['queue']['look'],
                                       callback_data=MSG_CB.new(action='look',
                                                                id=str(q.id))))
        kbd.insert(
            types.InlineKeyboardButton(config.MSG['queue']['apply'],
                                       callback_data=MSG_CB.new(action='apply',
                                                                id=str(q.id))))
    else:
        kbd.insert(
            types.InlineKeyboardButton('📝 Поправить',
                                       callback_data=POI_EDIT_CB.new(
                                           id=q.poi_id, d='0')))
    kbd.insert(
        types.InlineKeyboardButton(config.MSG['queue']['delete'],
                                   callback_data=MSG_CB.new(action='del',
                                                            id=str(q.id))))

    if not photo:
        await bot.send_message(user.id,
                               content,
                               parse_mode=HTML,
                               reply_markup=kbd)
    else:
        await bot.send_photo(user.id,
                             types.InputFile(photo),
                             caption=content,
                             parse_mode=HTML,
                             reply_markup=kbd)
    return True
Exemple #11
0
async def print_edit_options(user: types.User,
                             state: FSMContext,
                             comment=None):
    poi = (await state.get_data())['poi']
    lines = []
    m = tr(('editor', 'panel'))
    lines.append(f'<b>{format(poi.name)}</b>')
    lines.append('')
    lines.append(
        f'/edesc <b>{m["desc"]}:</b> {format(poi.description, null=m["none"])}'
    )
    lines.append(f'/ekey <b>{m["keywords"]}:</b> {format(poi.keywords)}')
    lines.append(f'/etag <b>{m["tag"]}:</b> {format(poi.tag)}')
    lines.append(f'/ehouse <b>{m["house"]}:</b> {format(poi.house_name)}')
    lines.append(f'/efloor <b>{m["floor"]}:</b> {format(poi.floor)}')
    lines.append(f'/eaddr <b>{m["addr"]}:</b> {format(poi.address_part)}')
    lines.append(f'/ehour <b>{m["hours"]}:</b> {format(poi.hours_src)}')
    lines.append(f'/eloc <b>{m["loc"]}:</b> '
                 '<a href="https://zverik.github.io/latlon/#18/'
                 f'{poi.location.lat}/{poi.location.lon}">'
                 f'{m["loc_browse"]}</a>')
    lines.append(
        f'/ephone <b>{m["phone"]}:</b> {format("; ".join(poi.phones))}')
    lines.append(f'/ewifi <b>{m["wifi"]}:</b> {format(poi.has_wifi)}')
    lines.append(f'/ecard <b>{m["card"]}:</b> {format(poi.accepts_cards)}')
    if poi.links:
        links = ', '.join(
            [f'<a href="{l[1]}">{h(l[0])}</a>' for l in poi.links])
    else:
        links = f'<i>{m["none"]}</i>'
    lines.append(f'/elink <b>{m["links"]}:</b> {links}')
    lines.append(
        f'/ecom <b>{m["comment"]}:</b> {format(poi.comment, null=m["none"])}')
    if poi.photo_out and poi.photo_in:
        photos = m['photo_both']
    elif poi.photo_out:
        photos = m['photo_out']
    elif poi.photo_in:
        photos = m['photo_in']
    else:
        photos = m['none']
    lines.append(f'<b>{m["photo"]}:</b> {photos} ({m["photo_comment"]})')
    if poi.id:
        if poi.delete_reason:
            lines.append(
                f'<b>{m["deleted"]}:</b> {format(poi.delete_reason)}. '
                f'{m["restore"]}: /undelete')
        else:
            lines.append(f'🗑️ {m["delete"]}: /delete')
        lines.append(f'✉️ {m["msg"]}: /msg')

    content = '\n'.join(lines)
    if comment is None:
        comment = tr(('new_poi', 'confirm2'))
    if comment:
        content += '\n\n' + h(comment)
    reply = await bot.send_message(user.id,
                                   content,
                                   parse_mode=HTML,
                                   reply_markup=new_keyboard(),
                                   disable_web_page_preview=True)
    await state.update_data(reply=reply.message_id)